前言
上一篇我们主要上了一个实例来把读者带进自定义 ViewGroup 的大门,只是带进大门,自定义 View 的内容还有很多,我之后碰到一些好的自定义 View 的话一定还来这里分享。本篇内容我们来分析 App 运行过程中出现的内存泄漏及如何解决。
内存泄漏概念及其影响
内存泄漏通俗的讲是一个本该被回收的对象却因为某些原因导致其不能回收。我们都知道对象是有生命周期的,从生到死,当对象的任务完成之后,由 Android 系统进行垃圾回收。我们知道 Android 系统为某个 App 分配的内存是有限的(这个可能根据机型的不同而不同),当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,最终导致 OOM(OutOfMemory) 使程序崩溃。
内存泄漏检查工具介绍
早在使用 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...
- }
- }
就是如此简单,那么下面我们就来用一下把 结合下面的内存泄漏场景应用。
常见的内存泄漏
在我们平时的开发中可能已经造成了内存泄漏而不自知,下面就罗列其中几种,看看你的程序里是不是有这样的代码。
- 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 运行过程中所有的内存泄漏,上面例子中得到的内存泄漏信息如下图所示
上面的内存泄漏太明显,估计大家都不会这样写,但是单例模式就不一样了,我们往往会忽略掉错误使用单例模式而造成的泄漏。比如说我们常在开发中用到的 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 的使用建议使用静态内部类的原因。
对于使用 Android 的 WebView 造成的内存泄漏。我在此建议使用 https://github.com/delight-im/Android-AdvancedWebView,使用这个优化后的 WebView,按照提示进行操作。
我在我的项目中使用了 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。
- @Overrideprotected void onDestroy() { super.onDestroy();
- mHandler.removeCallbacksAndMessages(null);
- mHandler = null;
- }
造成内存泄漏的原因有很多,我们这里只是列举了其中比较典型的几种,当然还有好多原因会造成内存泄漏,比如资源开启但是未关闭、多线程等等等等。但是我们有 LeakCanary 这个利器哈。
本篇总结
本篇只是稍微介绍了下 LeakCanary 以及几种常见的内存泄漏,内存泄漏以及内存性能优化是个持久的过程。我这里只是向你们介绍其中一种方法。编程无止境,性能优化也是。
来源: http://blog.csdn.net/c6E5UlI1N/article/details/78831770