Android 内存泄漏分析与解决

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽可能按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深刻理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其余的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能须要有必定Android开发基础和项目经验的同窗才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。android


前言

上一篇咱们主要上了一个实例来把读者带进自定义ViewGroup的大门,只是带进大门,自定义View的内容还有不少,我以后碰到一些好的自定义View的话必定还来这里分享。本篇内容咱们来分析App运行过程当中出现的内存泄漏及如何解决。git


内存泄漏概念及其影响

内存泄漏通俗的讲是一个本该被回收的对象却由于某些缘由致使其不能回收。咱们都知道对象是有生命周期的,从生到死,当对象的任务完成以后,由Android系统进行垃圾回收。咱们知道Android系统为某个App分配的内存是有限的(这个可能根据机型的不一样而不一样),当一个应用中产生的内存泄漏比较多时,就不免会致使应用所须要的内存超过这个系统分配的内存限额,最终致使OOM(OutOfMemory)使程序崩溃。github

内存泄漏检查工具介绍

早在使用Eclipse的时候咱们就知道了MAT性能分析工具,使用MAT固然能检查内存泄漏,不过使用稍微有些麻烦,我这里介绍另外一个工具,同时呢,咱们也抛弃了Eclipse,拥抱Android Studio。这个工具名叫LeakCanary。为何要使用这个工具呢,固然由于其简单,傻瓜式操做。这个工具是在Github开源的,是Square公司出品的,不是有一句话嘛,Square出品必属精品,https://github.com/square/leakcanary咱们能够方便的引用它编程

In your build.gradle:性能优化

dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
 }

In your Application class:多线程

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

就是如此简单,那么下面咱们就来用一下把 结合下面的内存泄漏场景应用。app

常见的内存泄漏

在咱们平时的开发中可能已经形成了内存泄漏而不自知,下面就罗列其中几种,看看你的程序里是否是有这样的代码。ide

静态变量形成的内存泄漏

public class MainActivity extends Activity{
    private static final String TAG = "MainActivity";

    private static Context sContext;
       
    private static View sView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //这里直接把当前Activity赋值给了静态变量sContext
        sContext = this;
        //这种写法和上面的相似
        sView = new View(this);

    }
}

上面这种方法估计小学生都知道会形成内存泄漏,缘由是当MainActivity对象完成任务须要回收时,却有一个静态变量引用它(静态变量的生命周期与Application相同),形成内存泄漏。咱们使用LeakCanary分析就是以下图工具

当咱们的App发生内存泄漏时会在通知栏显示通知,点击该通知可获得内存泄漏的详细信息,或者点击上图中的Leaks图标得到App运行过程当中全部的内存泄漏,上面例子中获得的内存泄漏信息以下图所示oop

单例模式形成的内存泄漏

上面的内存泄漏太明显,估计你们都不会这样写,可是单例模式就不同了,咱们每每会忽略掉错误使用单例模式而形成的泄漏。好比说咱们常在开发中用到的dp转px,px转dp等每每会封装成一个单例类。以下

public class DisplayUtils {
    private static volatile DisplayUtils instance = null;
    private Context mContext;
    private DisplayUtils(Context context){
        this.mContext = context;
    }
    public static DisplayUtils getInstance(Context context){
        if (instance != null){
            synchronized (DisplayUtils.class){
                if (instance !=null){
                    instance = new DisplayUtils(context);
                }
            }
        }

        return instance;
    }

    public int dip2px(float dpValue) {
        final float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}

而后咱们去调用它

public class SingleActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_single);
        //这里咱们把当前SingleActivity传入
        DisplayUtils.getInstance(this).dip2px(5);
    }
}

就这样内存泄漏产生了,咱们能够看图。

这个图和上面的内存泄漏的图很相像。可是咱们经常忽略了这种内存泄漏,是由于咱们没有直接使用静态变量指向传递进来的参数,解决办法要保证Context和AppLication的生命周期同样,修改后代码以下:

public class DisplayUtils {
    private static volatile DisplayUtils instance = null;
    private Context mContext;
    private DisplayUtils(Context context){
        //这里变化了,把当前Context指向个应用程序的Context
        this.mContext = context.getApplicationContext();
    }
    public static DisplayUtils getInstance(Context context){
        if (instance != null){
            synchronized (DisplayUtils.class){
                if (instance !=null){
                    instance = new DisplayUtils(context);
                }
            }
        }

        return instance;
    }

    public int dip2px(float dpValue) {
        final float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}

非静态内部类建立静态实例形成的内存泄漏

咱们在程序中基本上不能避免使用ListView或者RecyclerView,谈到这些列表展现的类,那么咱们的Adapter基本上也是不可缺乏,咱们在优化ListView的Adapter的时候会使用ViewHolder(RecyclerView自己已经作了优化),咱们在使用ViewHolder的使用建议使用静态内部类。那么为何会由此建议呢?这就是咱们下面要谈到的。非静态内部类建立静态实例可能形成的内存泄漏

public class NonStaticActivity extends AppCompatActivity {
    private static Config sConfig;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_non_static);
        //Config类并非静态类,
        sConfig = new Config();
    
    }
    
    class Config {
    
    }

}

形成内存泄漏的缘由是内部类会隐式持有外部类的引用,这里的外部类是NonStaticActivity,然而内部类sConfig又是static静态变量其生命周期与Application生命周期同样,因此在NonStaticActivity关闭的时候,内部类静态实例依然持有对NonStaticActivity的引用,致使NonStaticActivity没法被回收释放,引起内存泄漏。
解决办法就是把内部类生命为静态内部类,与外部类解耦。,这也是在使用ViewHolder的使用建议使用静态内部类的缘由。

WebView形成的内存泄漏

对于使用Android的WebView形成的内存泄漏。我在此建议使用https://github.com/delight-im/Android-AdvancedWebView,使用这个优化后的WebView,按照提示进行操做。

Handler形成的内存泄漏

我在个人项目中使用了handler,此时mHandler会隐式地持有一个外部类对象引用这里就是MainActivity,当执行postDelayed方法时,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,MessageQueue是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,因此致使该Activity的内存资源没法及时回收,引起内存泄漏。

public class HandlerActivity extends AppCompatActivity {
    private Handler mHandler = new Handler();
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        mTextView = (TextView) findViewById(R.id.text);//模拟内存泄露
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("test");
            }
        }, 5 * 60 * 1000);

    }


}

用LeakCanary能够看到相似下图

解决办法是 在HandlerActivity onDestroy里面移除消息队列中全部消息和全部的Runnable。

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
    mHandler = null;
}

其余缘由形成的内存泄漏

形成内存泄漏的缘由有不少,咱们这里只是列举了其中比较典型的几种,固然还有好多缘由会形成内存泄漏,好比资源开启可是未关闭、多线程等等等等。可是咱们有LeakCanary这个利器哈。

本篇总结

本篇只是稍微介绍了下LeakCanary以及几种常见的内存泄漏,内存泄漏以及内存性能优化是个持久的过程。我这里只是向大家介绍其中一种方法。编程无止境,性能优化也是。

下篇预告

好了,咱们下一篇介绍正篇Android的消息机制Looper、Handler、MessageQueue,Message


此致,敬礼

相关文章
相关标签/搜索