Android性能优化之常见的内存泄漏官方澳门新永利下载:,Android内存泄漏

经验总结—-来自Bugly

  • 对 Activity 等组件的引用应该控制在 Activity
    生命周期之内;如果不能就考虑使用 getApplicationContext 或者
    getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。
  • 在代码复审的时候关注长生命周期对象:全局性的集合、单例模式的使用、类的
    static 变量等等。
  • 尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context
    ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
  • Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空
    Handler 里面的消息。比如在 Activity onStop 或者 onDestroy
    的时候,取消掉该 Handler 对象的 Message和
    Runnable,removeCallbacks(Runnable r) 或removeMessages,或
    removeCallbacksAndMessages等。
  • 线程 Runnable 执行耗时操作,注意在页面返回时及时取消或者把
    Runnable 写成静态类。a)
    如果线程类是内部类,改为静态内部类。b)
    线程内如果需要引用外部类对象如 context,需要使用弱引用
  • 在 Java
    的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋空,如清空对图片等资源有直接引用或者间接引用的数组(使用
    array.clear() ; array = null),最好遵循谁创建谁释放的原则。
    public MyAsyncTask(Context context) {



        weakReference = new WeakReference<>(context);



   }

Toooooooooools

  1. MAT(有点麻烦,无爱,就不说了)
  2. LeakCanary

下面说下LeakCanary的简单使用,真的很简单

前言
对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我们可以借助LeakCanary、MAT等工具来检测应用程序是否存在内存泄漏,MAT是一款强大的内存分析工具,功能繁多而复杂,而LeakCanary则是由Square开源的一款轻量第三方内存泄漏检测工具,当它检测到程序中有内存泄漏的产生时,它将以最直观的方式告诉我们该内存泄漏是由谁产生的和该内存泄漏导致谁泄漏了而不能回收,供我们复查。
内存泄漏
为什么会产生内存泄漏?
当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
内存泄漏对程序的影响?
内存泄漏是造成应用程序OOM的主要原因之一!我们知道Android系统为每个应用程序分配的内存有限,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。
Android中常见的内存泄漏汇总
单例造成的内存泄漏
单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏,由于单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
如下这个典例:

Heads up:

本文主要记录本人开发中所遇到内存泄漏情况,然后介绍相关内存泄漏的检测方法,并搜集了一些大神的理论分析,从而由浅到深、实际结合理论,尽可能减少内存泄漏的出现,并提醒大家和我自己写代码时多注意内存泄漏的情况。

使用方法
  1. 在build.gradle中加依赖

dependencies { debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2' // release 中的版本里面就9个空方法,放心大胆的去用吧,用proguard这些就会被剥掉为0,更不用担心了 releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2' testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2' }
  1. 在Application中添加如下代码,别忘了在AndroidManifest.xml中使用

public class ExampleApplication extends Application { public static RefWatcher getRefWatcher(Context context) { ExampleApplication application = (ExampleApplication) context.getApplicationContext(); return application.refWatcher; } private RefWatcher refWatcher; @Override public void onCreate() { super.onCreate(); refWatcher = LeakCanary.install; }}
  1. 在需要监控的地方加如下代码(以Fragment为例)

public abstract class BaseFragment extends Fragment { @Override public void onDestroy() { super.onDestroy(); RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity; refWatcher.watch; }}

总之,注意引用对象的生命周期,注意引用对象的生命周期,注意引用对象的生命周期这个是重点

private MyHandler mHandler = new MyHandler(this);



private TextView mTextView ;



private static class MyHandler extends Handler {



    private WeakReference<Context> reference;



    public MyHandler(Context context) {



        reference = new WeakReference<>(context);



   }



    @Override



    public void handleMessage(Message msg) {



        MainActivity activity = (MainActivity) reference.get();



        if(activity != null){



            activity.mTextView.setText("");



       }



   }

遇到过泄漏的场景:

==小注:我是用LeakCanary来检测的,下面会介绍 ==

1. Volley网络请求中的泄漏

代码如下:

public class ScrollingActivity extends AppCompatActivity { private String mUrl = "http://www.jianshu.com/users/dd2b86a1f116/latest_articles"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_scrolling); // do a volley request RequestQueue queue = Volley.newRequestQueue; // 内部类Listener的对象持有外部类实例的引用 StringRequest request = new StringRequest(Request.Method.GET, mUrl, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.i("Log", "onResponse: " + response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }); queue.add; }}

检测到泄漏,如图:

官方澳门新永利下载 1volley_leak
by keith

原因分析:a.
在Activity或Fragment中进行网络请求,在宿主生命周期结束时未对相应的request进行cancel,由于volley中的listener对象仍然持有宿主一些属性的引用,使得GC不会回收,从而导致leakb.
在官方的volley包中,NetworkDispatcher中的run方法中,当请求队列里面无请求时,本地request仍然持有最后一个请求的引用,使得GC不会回收,导致leak

//NetworkDispatcherpublic void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while  { long startTimeMs = SystemClock.elapsedRealtime(); Request<?> request; try { // Take a request from the queue request = mQueue.take(); //PriorityBlockingQueue } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if  { return; } continue; } ...------------------------------------// PriorityBlockingQueuepublic E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); E result; try { //这是关键,当request==null的时候,当前线程处于等待状态, //也就造成了NetworkDispatcher的run方法中的局部request变量一直 //带有最后一个request的引用,直到有新的请求进来或者线程被打断 while ( (result = dequeue == null) notEmpty.await(); } finally { lock.unlock(); } return result;}

解决方案: a. 及时调用volley quene的cancel方法b.
使用改良版的volley包,如mcxiaoke

public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Request<?> request; while  { long startTimeMs = SystemClock.elapsedRealtime(); // release previous request object to avoid leaking request object when mQueue is drained. // 这个就是关键的制空 request = null; try { // Take a request from the queue. request = mQueue.take(); } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if  { return; } continue; }

2. 单例Dialog导致的泄露
这个是我自己写的一个ProgressDialog工具类,主要用在网络请求时的等待显示,我觉得挺好用,直到检查内存泄漏,真是too
naive了,代码如下:

public class ProgressUtils { public static ProgressDialog progressDialog = null; public static void dismissProgressDialog() { if (null != progressDialog && progressDialog.isShowing { progressDialog.dismiss(); } } public static void showProgressDialog(Context context, String message) { if (null != progressDialog && progressDialog.isShowing { return; } if (progressDialog == null || progressDialog.getContext() != context) { progressDialog = new ProgressDialog; } progressDialog.setMessage; progressDialog.show(); }}

检测到泄漏,如图

官方澳门新永利下载 2progress_dialog_singleton_leak
by keith

原因分析:由于在Activity生命周期结束之后,单例Dialog内部的变量mContext中的mBase仍然持有该Activity对像的引用,虽然下次其他Activity来调用Dialog时上个Activity对象的引用会被踢掉,但是还是会有一个Activity对象的泄漏,简单的代码分析如下:

// 1. ProgressUtilsprogressDialog = new ProgressDialog;// 2. 一路点击构造方法下去,会到Dialog的这个方法中(createContextThemeWrapper为true)Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (themeResId == 0) { final TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true); themeResId = outValue.resourceId; } // 走这一步 mContext = new ContextThemeWrapper(context, themeResId); } else { mContext = context; } ··· }// 3. 接着是mContext指向的对象的创建,会走到ContextWrapper中public class ContextWrapper extends Context { //就是这个拿着Activity的引用 Context mBase; public ContextWrapper(Context base) { mBase = base; } ···

解决方案:由于Dialog比较特别,只能用Activity才能启动(当然有一些用其他context也可以,但是要申请权限什么的,不适用于一般app),所以不要这种单例ProgressDialog工具类,在Activity或Fragment的生命中控制dialog的show和dismiss;

官方澳门新永利下载 3

注: 图片来自reference-2

3. 单例Toast导致的泄露(同单例dialog) 使用全局ApplicaitonContext

4. 百度地图LocationClient定位服务内存泄漏 使用全局ApplicaitonContext

5. Handler内存泄漏
原因就不说,也是内部引用的没有跟引用对象的生命周期同步,具体可以看reference中大神分析;解决方案:

  1. 用静态Handler和WeakReference,例如:

//子类继承这个类,然后正常用就好了public abstract class WeakReferenceHandler<T> extends Handler { private WeakReference<T> mReference; public WeakReferenceHandler(T reference) { mReference = new WeakReference<T>(reference); } @Override public void handleMessage(Message msg) { if (mReference.get() == null) { return; } handleMessage(mReference.get; } protected abstract void handleMessage(T reference, Message msg);}
  1. 在引用对象生命周期结束时,调用Handler的removeCallbacksAndMessages()或removeCallbacks()或removeMessages();

5. Thread造成的内存泄漏 也是一样,注意引用对象的生命周期

Reference

  1. Android 内存泄漏总结
  2. Android性能优化之常见的内存泄漏
  3. 内存泄露从入门到精通三部曲之基础知识篇
  4. 内存泄露从入门到精通三部曲之排查方法篇
  5. 内存泄露从入门到精通三部曲之常见原因与用户实践

}

@Override



protected void onDestroy() {



    super.onDestroy();



    mHandler.removeCallbacksAndMessages(null);
private static AppManager instance;

private Context context;
private AppManager(Context context) {
   this.context = context;

}

}

}

1

1

    @Override



    protected Void doInBackground(Void... params) {



        SystemClock.sleep(10000);



        return null;



   }

}

使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。
线程造成的内存泄漏
对于线程造成的内存泄漏,也是平时比较常见的,如下这两个示例可能每个人都这样写过:

}

private Handler mHandler = new Handler() {



    @Override



    public void handleMessage(Message msg) {



        //...



   }
new Thread(new MyRunnable()).start();


new MyAsyncTask(this).execute();

}

private void loadData() {



    //...request



    Message message = Message.obtain();



    mHandler.sendMessage(message);
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {



    private WeakReference<Context> weakReference;

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
1、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长
2、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。
所以正确的单例应该修改为下面这种方式:

@Override



protected void onCreate(Bundle savedInstanceState) {



    super.onCreate(savedInstanceState);


    setContentView(R.layout.activity_main);



    mTextView = (TextView)findViewById(R.id.textview);



    loadData();

1

}

}

}

这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext
Handler造成的内存泄漏
Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例:

    @Override



    protected void onPostExecute(Void aVoid) {


        super.onPostExecute(aVoid);

        MainActivity activity = (MainActivity) weakReference.get();

        if (activity != null) {


            //...



       }



   }

//——————test1

}

public class MainActivity extends AppCompatActivity {

1

    new AsyncTask<Void, Void, Void>() {



        @Override



        protected Void doInBackground(Void... params) {



            SystemClock.sleep(10000);



            return null;



       }



   }.execute();

//——————test2

发表评论

电子邮件地址不会被公开。 必填项已用*标注