安卓使用LeakCanary检测代码内存泄漏和BlockCanary优化代码结构

使用LeakCanary检测代码的内层泄漏

首先我们看下面的代码
public class MainActivity extends AppCompatActivity {    
     private Button btn_load;   
     private Handler mHandler = new Handler() {        
            @Override        
            public void handleMessage(Message msg) {    
                   if(msg.what == 0) {                                                    
                      Log.i("handleMessage", "got datas");    
                   }  
             }  
      };    
      @Override    
      protected void onCreate(Bundle savedInstanceState) {                               
                super.onCreate(savedInstanceState); 
                setContentView(R.layout.activity_main);        
                btn_load = (Button)findViewById(R.id.btn_load);
                btn_load.setOnClickListener(new View.OnClickListener() {    
                         Override    
                         public void onClick(View v) { 
                                Log.i("btn_load", "loading datas"); 
                                loadData(); 
                         }
                });
       private void loadData() {    
               new Thread(new Runnable() {        
                   @Override        
                  public void run() {            
                         //do sonething            
                         SystemClock.sleep(10000);            
                        //发送消息            
                       mHandler.sendEmptyMessageDelayed(0, 20000);      
                  }    
              }).start();}
  • 开启界面后, 立即关闭,等待一段时间后,出现泄漏,检查LeakCanary,获取以下的结果:
安卓使用LeakCanary检测代码内存泄漏和BlockCanary优化代码结构_第1张图片
MainActivity泄漏流程
  • 首先在我们的安卓程序中引入LeakCanary:
    • 在对应安卓模块的build.gradle文件中导入以下的语句引入相应的库,并保证leak检测只在代码debug模式下可用,上线后失效
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
  • 创建一个MyApp 类继承Application,在onCreate()方法中安装LeakCanary,不要忘了在清单文件中注册MYApp
    具体操作如下:
public class MyApp extends Application {   
              @Override   
              public void onCreate() {        
                       super.onCreate();      
                       LeakCanary.install(this);    
              }
}
  • MainActivity泄漏流程分析
  • 触发按钮点击时间后, loadData()开始执行,loadData()方法中开启了一个子线程,创建了一个匿名的线程对象。当我们在该对象的run方法没有执行完之前就关闭了界面(MainActivity),因为线程对象是一个内部类对象,默认持有外部类(MainActivity)对象的引用,从而导致MainActivity关闭后无法被gc回收从而造成泄漏
  • 解决方法
    我们自建一个内部静态类继承Thread,静态内部类不持有外部类的引用,从而可以避免以上问题,代码如下
private static class  MyThread extends  Thread {    
          private WeakReference weak;        
          public MyThread(MainActivity activity) {        
                 weak = new WeakReference(activity);   
          }    
          @Override    
          public void run() {        
                 //do sonething        
                 SystemClock.sleep(100);        
                //发送消息       
                if(null != weak && null != weak.get()) { 
                    weak.get().mHandler.sendEmptyMessageDelayed(0, 20000);       
                }
          }                
} 

loadData()中修改如下

new MyThread(this).start();

开启界面后, 立即关闭,等待一段时间后,又出现泄漏,检查LeakCanary,获取以下的结果:


安卓使用LeakCanary检测代码内存泄漏和BlockCanary优化代码结构_第2张图片
MainActivity泄漏流程

究其原因是和上述线程是一样的,只不过这次泄漏的是Handler对象。
所以,我们再定义一个Handler的静态内部类,代码如下:

private static class  MyHandler extends Handler {    
          private WeakReference weak;    
          public MyHandler(MainActivity activity) {        
                 weak = new WeakReference(activity);   
          }    
          @Override    
          public void handleMessage(Message msg) {       
                 if(msg.what == 0) {            
                    Log.i("handleMessage", "got datas");            
                   if(null != weak && null != weak.get()) {    
                        weak.get().textView.setText("goodbye world");           
                   }       
                 }  
          }
}

再次运行程序将不会产生泄漏问题

  • 进一步优化
  • 但界面不可见时, 我们最好把消息队列中的message清空,代码如下:
@Override
protected void onDestroy() {    
         super.onDestroy();    
         mHandler.removeCallbacksAndMessages(null);
}
  • 当界面关闭时,我们的子线程还在运行,可以通过观察LogCat打印日志看出,实际上,我们在关闭主线程是同时关闭子线程,可以如下操作:
    • 定义一个全局boolbean型变量来控制MyThread的开关,在MyThread提供一个关闭线程的方法close(), 当界面关闭时,调用该方法mt.close(), 代码如下:
      定义的全局变量
private MyThread mt;
private boolean isClose;

提供的方法

public void close() {    
       if(null != weak && null != weak.get()) {      
              weak.get().isClose = true;    
       }
}

修改run() 的逻辑

if(null != weak && null != weak.get()) {  
      if(weak.get().isClose) {        
          //直接返回      
           return;   
       }
}

onDestroy()调用

mt.close();
  • 还想提及的内容
    • 我们在两个自定义的内部类中都有这样的代码段
 private WeakReference weak; 

其作用是为了然我们的静态内部类可以调用外部类的非静态的字段和方法,从而只有一个外部类对象的引用,但这样做就又回到导致我们的代码泄漏的最初的原因,怎么办呢,于是弱引用横空出世了。弱引用的特点是一旦被gc扫描到就会被立即回收,而不管是否被引用,这也是为什么每次我们使用时都要判断其是否为null的原因。与之对应的还有软引用(SoftReference), 强引用, 虚引用, 相关的详细说明大家自行搜索啊。

  • 最后, 这里就泄漏的问题就举了一个例子, 大家想要了解更多可以参考这篇博文:
    http://www.jianshu.com/p/4a45f3ecc288

使用BlockCanary优化代码的结构

  • 当我们完成我们的app后发现使用起来卡顿特别严重,于是需要对代码进行优化,可是面对动辄几千行、几万行的代码,让人无法下手,于是BlockCanary出现了。接下来,我为大家演示BlockCanary的用法
  • 第一步, 获取对应的库
    在相应的Module的build.gradle中导入如下的语句引入对应的库
compile 'com.github.moduth:blockcanary-android:1.2.1'
// 仅在debug包启用BlockCanary进行卡顿监控和提示的话,可以这么用 
debugCompile 'com.github.moduth:blockcanary-android:1.2.1' 
   releaseCompile 'com.github.moduth:blockcanary-no-op:1.2.1'
  • 新建一个类继承BlockContextCanary
    实现各种上下文,像是卡慢报告阈值,log的保存位置,网络类型等
public class AppBlockCanaryContext extends BlockCanaryContext {    
          // override to provide context like app qualifier, uid,     network type, block threshold, log save path    
          // this is default block threshold, you can set it by phone's performance    
          @Override    
          public int getConfigBlockThreshold() {       
                return 500;   
          }    
          // if set true, notification will be shown, else only write log file 
          @Override   
          public boolean isNeedDisplay() {       
             return BuildConfig.DEBUG;  
          }   
         // path to save log file (在SD卡目录下)   
        @Override   
         public String getLogPath() {     
             return "/blockcanary/performance";   
         }
}
  • MyApp中开启检测, 不要忘了在manifest清单文件中注册MyApp
public class MyApp extends Application {    
      @Override    
      public void onCreate() {        
           super.onCreate();       
           BlockCanary.install(this, new AppBlockCanaryContext()).start();  
      }
}  
  • LeakDemo为例我们来检测代码的卡顿情况,结果如下:
安卓使用LeakCanary检测代码内存泄漏和BlockCanary优化代码结构_第3张图片
卡顿检测结果图

结果显示第34行有卡顿情况,我们找到这一行:


卡顿的地方

我们还可以查看更详细的信息

安卓使用LeakCanary检测代码内存泄漏和BlockCanary优化代码结构_第4张图片
卡顿的详细信息

获取到卡顿的代码位置,我们就可以着手修改代码和重构了


  • 最后附上
  • LeakCanary源码:
    https://github.com/square/leakcanary
  • BlockCanary源码
    https://github.com/markzhai/AndroidPerformanceMonitor
  • 笔者的源码
    https://github.com/CwugsChen18/leakdemo

  • 后话
    笔者花了一晚上终于完成了自己的第一篇博文,希望大家多多支持,笔者还会继续努力,为大家奉上更多有趣,有用的文章的,下次再见咯!!

你可能感兴趣的:(安卓使用LeakCanary检测代码内存泄漏和BlockCanary优化代码结构)