a、怎样在coding过程中避免内存泄露?

b、怎样检测内存泄露?

        怎样避免就不赘述了,网上很多答案。

       工具呢,当然也有很多,比如DDMS、MAT等,但是怎样在我们编码过程中植入内存检测代码,让我们程序在开发调试阶段就能发现内存泄露呢?好了,现在该大名鼎鼎的LeakCanary出场了,它是Square公司的一个内存探测开源项目。下面就介绍下怎样使用.

1、配置gradle依赖:    


1. debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'  
2. releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'


2、初始化Watcher



1. package com.micky.leakcanarysamples;;  
2.   
3. import android.app.Application;  
4.   
5. import com.squareup.leakcanary.LeakCanary;  
6. import com.squareup.leakcanary.RefWatcher;  
7.   
8. /**
9. Application
10.  */  
11. public class BaseApplication extends Application {  
12.   
13. private static BaseApplication instance;  
14.   
15. private RefWatcher mRefWatcher;  
16.   
17. @Override  
18. public void onCreate() {  
19. super.onCreate();  
20. this;  
21. this) : RefWatcher.DISABLED;  
22.     }  
23.   
24. public static BaseApplication getInstance() {  
25. return instance;  
26.     }  
27.   
28. public static RefWatcher getRefWatcher() {  
29. return getInstance().mRefWatcher;  
30.     }  
31. }


3、在Activity或Fragment中添加检测


1. package com.micky.leakcanarysamples;  
2.   
3. import android.app.Activity;  
4. import android.support.v7.app.AppCompatActivity;  
5.   
6. /**
7.  * Activity基类
8.  */  
9. public class BaseActivity extends AppCompatActivity {  
10.   
11. @Override  
12. protected void onDestroy() {  
13. super.onDestroy();  
14. this);  
15.     }  
16. }


4、测试


1. package com.micky.leakcanarysamples;;  
2.   
3. import android.os.Bundle;  
4. import android.os.Handler;  
5.   
6. /**
7. 应用 LeakCanaryTest
8.  */  
9. public class TestActivity extends BaseActivity {  
10.   
11. private final Handler mHandler = new Handler();  
12.   
13. @Override  
14. protected void onCreate(Bundle savedInstanceState) {  
15. super.onCreate(savedInstanceState);  
16. //模拟内存泄露  
17. new Runnable() {  
18. @Override  
19. public void run() {  
20.   
21.             }  
22. 3 * 60 * 1000);  
23.         finish();  
24.     }  
25. }


5、测试结果

a、Toast显示(大概10秒左右显示)


Android如何定位内存泄漏 android 内存泄漏检测工具_Android内存泄露

b、通知显示


Android如何定位内存泄漏 android 内存泄漏检测工具_提示_02

c、桌面自动添加的图表


Android如何定位内存泄漏 android 内存泄漏检测工具_Android内存泄露_03

d、内存泄露列表


Android如何定位内存泄漏 android 内存泄漏检测工具_Android内存泄露_04

e、内存泄露详细


Android如何定位内存泄漏 android 内存泄漏检测工具_自动检测内存_05

LogCat可以看到日志日下(hprof文件可以用MAT打开进行分析):


1. 01-04 11:49:41.815 12967-13004/com.micky.leakcanarysamples I/dalvikvm: hprof: dumping heap strings to "/storage/emulated/0/Download/leakcanary/suspected_leak_heapdump.hprof".  
2. 01-04 11:49:42.020 12967-13004/com.micky.leakcanarysamples I/dalvikvm: hprof: heap dump completed (28850KB)

查看自动生成的AndroidManifest文件,LeakCanarySamples/app/build/intermediates/manifests/full/debug/AndroidManifest.xml




1. <?xml version="1.0" encoding="utf-8"?>  
2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
3. package="com.micky.leakcanarysamples"  
4. android:versionCode="1"  
5. android:versionName="1.0" >  
6.   
7. <uses-sdk  
8. android:minSdkVersion="10"  
9. android:targetSdkVersion="23" />  
10.   
11. <!-- To store the heap dumps and leak analysis results. -->  
12. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  
13.   
14. <android:uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />  
15.   
16. <application  
17. android:name="com.micky.leakcanarysamples.BaseApplication"  
18. android:allowBackup="true"  
19. android:icon="@mipmap/ic_launcher"  
20. android:label="@string/app_name"  
21. android:supportsRtl="true"  
22. android:theme="@style/AppTheme" >  
23. <activity  
24. android:name="com.micky.leakcanarysamples.MainActivity"  
25. android:label="@string/app_name"  
26. android:theme="@style/AppTheme.NoActionBar" >  
27. <intent-filter>  
28. <action android:name="android.intent.action.MAIN" />  
29.   
30. <category android:name="android.intent.category.LAUNCHER" />  
31. </intent-filter>  
32. </activity>  
33. <activity android:name="com.micky.leakcanarysamples.TestActivity" />  
34.   
35. <service  
36. android:name="com.squareup.leakcanary.internal.HeapAnalyzerService"  
37. android:enabled="false"  
38. android:process=":leakcanary" />  
39. <service  
40. android:name="com.squareup.leakcanary.DisplayLeakService"  
41. android:enabled="false" />  
42.   
43. <activity  
44. android:name="com.squareup.leakcanary.internal.DisplayLeakActivity"  
45. android:enabled="false"  
46. android:icon="@drawable/__leak_canary_icon"  
47. android:label="@string/__leak_canary_display_activity_label"  
48. android:taskAffinity="com.squareup.leakcanary"  
49. android:theme="@style/__LeakCanary.Base" >  
50. <intent-filter>  
51. <action android:name="android.intent.action.MAIN" />  
52.   
53. <category android:name="android.intent.category.LAUNCHER" />  
54. </intent-filter>  
55. </activity>  
56. </application>  
57.   
58. </manifest>


如上所示LeakCanary给我们自动添加了两个Service和一个Activity,并添加了对SD卡的读写权限


It 's so simple.

注: 

1、如果在Release模式下请使用RefWatcher.DISABLED

 2、在Activity或Fragment 的 Destroy方法中添加检测(很好理解,就是判断一个Activity或Fragment想要被销毁的时候,是否还有其他对象持有其引用导致Activity或Fragment不能被回收,从而导致内存泄露)

源码地址:https://github.com/mickyliu945/LeakCanarySample   点击打开链接

-------------------------------------

为什么我应该使用LeakCanary?

问得好!我们正好写了个博客回答这个问题。 ps:博客在本站的中文地址: LeakCanary:检测所有的内存泄漏

那怎么使用它呢?

使用一个RefWatcher观察引用什么时候应该被GC:

1. RefWatcher refWatcher = {...};
2.  
3. // We expect schrodingerCat to be gone soon (or not), let's watch it.
4. refWatcher.watch(schrodingerCat);

LeakCanary.install() 返回一个先前配置的RefWatcher,它也安装一个ActivityRefWatcher以便在Activity.onDestroy()被调用后自动检测Activity是否出现泄露。

1. public class ExampleApplication extends Application {
2.  
3.   public static RefWatcher getRefWatcher(Context context) {
4.     ExampleApplication application = (ExampleApplication) context.getApplicationContext();
5.     return application.refWatcher;
6.   }
7.  
8.   private RefWatcher refWatcher;
9.  
10.   @Override public void onCreate() {
11.     super.onCreate();
12.     refWatcher = LeakCanary.install(this);
13.   }
14. }


你可以使用RefWatcher观察Fragment的内存泄露

1. public abstract class BaseFragment extends Fragment {
2.  
3.   @Override public void onDestroy() {
4.     super.onDestroy();
5.     RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
6.     refWatcher.watch(this);
7.   }
8. }


How does it work?

1.RefWatcher.watch()创建一个KeyedWeakReference去检测对象;
2.接着,在后台线程,它将会检查是否有引用在不是GC触发的情况下需要被清除的;
3.如果引用引用仍然没有被清除,将会转储堆到.hprof文件到系统文件中(it them dumps the heap into a .hprof file stored on the app file system.)
4.HeapAnalyzerService是在一个分离的进程中开始的,HeapAnalyzer通过使用HAHA解析heap dump;
5.由于一个特殊的引用key和定位的泄露引用,HeapAnalyzer可以在heap dump中找到KeyedWeakReference;
6.如果有一个泄露,HeapAnalyzer计算到GC Roots的最短的强引用路径,然后创建造成泄露的引用链;
7.结果在app的进程中传回到DisplayLeakService,并展示泄露的通知消息;

怎样拷贝leak trace?

你可以在Logcat上看leak trace:


1. In com.example.leakcanary:1.0:1 com.example.leakcanary.MainActivity has leaked:
2. * GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #1')
3. * references com.example.leakcanary.MainActivity$3.this$0 (anonymous class extends android.os.AsyncTask)
4. * leaks com.example.leakcanary.MainActivity instance
5.  
6. * Reference Key: e71f3bf5-d786-4145-8539-584afaecad1d
7. * Device: Genymotion generic Google Nexus 6 - 5.1.0 - API 22 - 1440x2560 vbox86p
8. * Android Version: 5.1 API: 22
9. * Durations: watch=5086ms, gc=110ms, heap dump=435ms, analysis=2086ms


你也可以分享leak trace和heap dump文件通过action bar的菜单。

My leak is caused by the SDK implementation!

随着时间过去越来越多熟知的内存泄露问题被制造商在android开源项目中修复。当这样一个泄露发生时,你能作为一个应用程序开发员来修复它。出于这个原因,LeakCanary有一个内置Android泄露的列表AndroidExcludedRefs.java来监测它,如果你找到一个新的泄露,请用leaktrace创建一个issue,标明设备和Android版本。如果你提供一个heap dump的文件链接就更好了。

这是对于新发布的Android版本来说是特别重要的。你有机会更早地帮助检测新的内存泄露,这有益于整个Android社区。
开发版快照可以通过Sonatype's snapshots repository找到。

Beyond the leak trace

有时leak trace不够清晰,你需要使用MATYourKit深入研究heap dump。这里教你怎样在head dump找到泄露的实例:

1.找出包com.squareup.leakcanary.KeyedWeakReference下所有实例;
2.对于每个实例,考虑它的key域;
3.找到 KeyedWeakReference 有一个key域等于被LeakCanary报出的引用的key;
4.KeyedWeakReference的referent域是程序中内存泄露的对象;
5.从那时起,问题就转到你的手上了。一个好的开始是找到最短的GC roots的路径(排除弱引用)

自定义Customizing

图标和标签 Icon and label

leak_canary_icon和R.string.leak_canary_display_activity_label来修改:


1. res/
2.   drawable-hdpi/
3.     __leak_canary_icon.png
4.   drawable-mdpi/
5.     __leak_canary_icon.png
6.   drawable-xhdpi/
7.     __leak_canary_icon.png
8.   drawable-xxhdpi/
9.     __leak_canary_icon.png
10.   drawable-xxxhdpi/
11.     __leak_canary_icon.png

1. <?xml version="1.0" encoding="utf-8"?>
2. <resources>
3.   <string name="__leak_canary_display_activity_label">MyLeaks</string>
4. </resources>


储存leak traces

DisplayLeakActivity可以在你的app目录保存7个heap dumps和leak traces,你可以在app中通过提供R.integer.__leak_canary_max_stored_leaks的值改变这个数量:


1. <?xml version="1.0" encoding="utf-8"?>
2. <resources>
3.   <integer name="__leak_canary_max_stored_leaks">20</integer>
4. </resources>


上传到服务器

你可以改变默认的行为去上传leak trace并heap dump到你选择的服务器。
创建你自己的AbstractAnalysisResultService,最容易的方式是在你debug的源码中继承DefaultAnalysisResultService:


1. public class LeakUploadService extends DefaultAnalysisResultService {
2.   @Override protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
3.     if (!result.leakFound || result.excludedLeak) {
4.       return;
5.     }
6.     myServer.uploadLeakBlocking(heapDump.heapDumpFile, leakInfo);
7.   }
8. }


确定在你正式发布的Application类中使RefWatcher失效:


1. public class ExampleApplication extends Application {
2.  
3.   public static RefWatcher getRefWatcher(Context context) {
4.     ExampleApplication application = (ExampleApplication) context.getApplicationContext();
5.     return application.refWatcher;
6.   }
7.  
8.   private RefWatcher refWatcher;
9.  
10.   @Override public void onCreate() {
11.     super.onCreate();
12.     refWatcher = installLeakCanary();
13.   }
14.  
15.   protected RefWatcher installLeakCanary() {
16.     return RefWatcher.DISABLED;
17.   }
18. }


在你的debug的Application类创建一个定制的RefWatcher:


1. public class DebugExampleApplication extends ExampleApplication {
2.   protected RefWatcher installLeakCanary() {
3.     return LeakCanary.install(app, LeakUploadService.class);
4.   }
5. }


不要忘记了在你debug的manifest中注册service:

1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3.     xmlns:tools="http://schemas.android.com/tools"
4.     >
5.   <application android:name="com.example.DebugExampleApplication">
6.     <service android:name="com.example.LeakUploadService" />
7.   </application>
8. </manifest>


--------------------------------------------------------------


    git地址(不用下载,使用gradle 引入便可): https://github.com/square/leakcanary



OOMcrash 无法获得相应的结果, 当前可正常使用的是 1.3版本。 (如果有相应问题的朋友建议使用1.3)



OutOfMemory Error thrown during Leak Analysis



     这个项目是在应用相应的回退之后分析是否存在内存泄漏,如果存在内存泄漏,将进行相应的分析并处理,若没有则不会,不能做到MAT或者studio中相应的实时查看内存状态的,并且检测具有很大的延时,最少10s。



 



    二、如何使用



    测试demo可以采用官方的例子,地址为: https://github.com/square/leakcanary/tree/master/leakcanary-sample/src/main/java/com/example/leakcanary



    具体操作如下:



    step 1、配置build



     在相应的build.gradle中加入如下代码



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



    【错误1:】截图如下: Failed to resolve: com.squareup.leakcanary:leakcanary-android


     

Android如何定位内存泄漏 android 内存泄漏检测工具_自动检测内存_06



clean Build。



      



1. allprojects {     
2.      repositories {         
3.           jcenter()  
4.      }  
5.   }



   





如果还是出现上述问题,那么替换为如下的代码:


allprojects {   
 
 
          repositories {       
 
 
               jcenter() 
   
 
 
               mavenCentral()   
 
 
          }
 
 
       }



     



     



    step 2、写入启动代码



      在相应的application中加入(也可以在其他地方,但是传入install的context必须是application)



1. onCreate(){  
2. super.onCreate();  
3. this);  
4.  }







     step3、应用安装



     安装应用,在debug版本的apk安装后,会出现如下两个图标:左边的是自己应用的图标,右边是启动应用后退出,自动安装的leakCancayDe图标。



 

Android如何定位内存泄漏 android 内存泄漏检测工具_Android如何定位内存泄漏_07


     


     【错误2】:但是有的人没有相应的图标,怎么办?


      这个时候查看自己工程的库的引用,见下图:

      

Android如何定位内存泄漏 android 内存泄漏检测工具_Android如何定位内存泄漏_08


【解决当前问题,但是不提倡】


     1、debug 和 release 引用相同的lib


dependencies {
  
             debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
  
             releaseCompile '   com.squareup.leakcanary:leakcanary-android:1.3   '
  
       }


     2、使用compile 不再1区分debug 和 release

dependencies {
  
             compile 'com.squareup.leakcanary:leakcanary-android:1.3'
  
       }


      step4、检测内存泄漏


     采用官方demo,点击相应的测试按钮,会启动一个 AsyncTask,人为制造内存泄漏,之后back出去,等一段时间,看情况,一般会大于10s,有些会更长(不清楚具体的触发条件,什么时候开始检测,作者也没具体说明)如果是1.4以上,会有相应的一个启动提示,而1.3没有,这个时候可以看 studio中是否存在如下图的进程,并根据其相应的日志就才能知道是否启动了。


Android如何定位内存泄漏 android 内存泄漏检测工具_Android如何定位内存泄漏_09


 


      step5、如何知道检测到内存泄漏


     一旦检测到内存泄漏,将会有相应的通知栏的提示(也就是说,如果你的应用不存在相应的泄漏,则不会提示)

             

Android如何定位内存泄漏 android 内存泄漏检测工具_Android内存泄露_10


     step6、查看内存泄漏信息


     点击通知栏图标或者点击应用icon都可以,可以得到如下的信息,根据这个信息就可以知道哪里出现了内存泄漏。


Android如何定位内存泄漏 android 内存泄漏检测工具_提示_11


    


     此外,还可以测试具体的Fragment,可以使用如下代码:


     

1. public abstract class BaseFragment extends Fragment {  
2.   
3. @Override public void onDestroy() {  
4. super.onDestroy();  
5.     RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());  
6. this);  
7.   }  
8. }




这时候,application修改为:


 

1. public class ExampleApplication extends Application {  
2.   
3. public static RefWatcher getRefWatcher(Context context) {  
4.     ExampleApplication application = (ExampleApplication) context.getApplicationContext();  
5. return application.refWatcher;  
6.   }  
7.   
8. private RefWatcher refWatcher;  
9.   
10. @Override public void onCreate() {  
11. super.onCreate();  
12. if (LeakCanary.isInAnalyzerProcess(this)) {  
13. // This process is dedicated to LeakCanary for heap analysis.  
14. // You should not init your app in this process.  
15. return;  
16.     }  
17. this);  
18.   }  
19. }





    git地址(不用下载,使用gradle 引入便可): https://github.com/square/leakcanary


OOMcrash 无法获得相应的结果, 当前可正常使用的是 1.3版本。 (如果有相应问题的朋友建议使用1.3)


OutOfMemory Error thrown during Leak Analysis


     这个项目是在应用相应的回退之后分析是否存在内存泄漏,如果存在内存泄漏,将进行相应的分析并处理,若没有则不会,不能做到MAT或者studio中相应的实时查看内存状态的,并且检测具有很大的延时,最少10s。


 


    二、如何使用


    测试demo可以采用官方的例子,地址为: https://github.com/square/leakcanary/tree/master/leakcanary-sample/src/main/java/com/example/leakcanary


    具体操作如下:


    step 1、配置build


     在相应的build.gradle中加入如下代码


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


    【错误1:】截图如下: Failed to resolve: com.squareup.leakcanary:leakcanary-android

     

Android如何定位内存泄漏 android 内存泄漏检测工具_自动检测内存_06


clean Build。


      

1. allprojects {     
2.      repositories {         
3.           jcenter()  
4.      }  
5.   }

   



如果还是出现上述问题,那么替换为如下的代码:


allprojects {   
  
          repositories {       
  
               jcenter()    
  
               mavenCentral()   
  
          }
  
       }


     


     


    step 2、写入启动代码


      在相应的application中加入(也可以在其他地方,但是传入install的context必须是application)


      

1. onCreate(){  
2. super.onCreate();  
3. this);  
4.  }




     step3、应用安装


     安装应用,在debug版本的apk安装后,会出现如下两个图标:左边的是自己应用的图标,右边是启动应用后退出,自动安装的leakCancayDe图标。


 

Android如何定位内存泄漏 android 内存泄漏检测工具_Android如何定位内存泄漏_07


     


     【错误2】:但是有的人没有相应的图标,怎么办?


      这个时候查看自己工程的库的引用,见下图:

      

Android如何定位内存泄漏 android 内存泄漏检测工具_Android如何定位内存泄漏_08


【解决当前问题,但是不提倡】


     1、debug 和 release 引用相同的lib


  

dependencies {
  
             debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
  
             releaseCompile '   com.squareup.leakcanary:leakcanary-android:1.3   '
  
       }


     2、使用compile 不再1区分debug 和 release


dependencies {
  
             compile 'com.squareup.leakcanary:leakcanary-android:1.3'
  
       }


      step4、检测内存泄漏


     采用官方demo,点击相应的测试按钮,会启动一个 AsyncTask,人为制造内存泄漏,之后back出去,等一段时间,看情况,一般会大于10s,有些会更长(不清楚具体的触发条件,什么时候开始检测,作者也没具体说明)如果是1.4以上,会有相应的一个启动提示,而1.3没有,这个时候可以看 studio中是否存在如下图的进程,并根据其相应的日志就才能知道是否启动了。


Android如何定位内存泄漏 android 内存泄漏检测工具_Android如何定位内存泄漏_09


 


      step5、如何知道检测到内存泄漏


     一旦检测到内存泄漏,将会有相应的通知栏的提示(也就是说,如果你的应用不存在相应的泄漏,则不会提示)

             

Android如何定位内存泄漏 android 内存泄漏检测工具_Android内存泄露_10


     step6、查看内存泄漏信息


     点击通知栏图标或者点击应用icon都可以,可以得到如下的信息,根据这个信息就可以知道哪里出现了内存泄漏。


Android如何定位内存泄漏 android 内存泄漏检测工具_提示_11


    


     此外,还可以测试具体的Fragment,可以使用如下代码:


     

1. public abstract class BaseFragment extends Fragment {  
2.   
3. @Override public void onDestroy() {  
4. super.onDestroy();  
5.     RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());  
6. this);  
7.   }  
8. }




这时候,application修改为:


 


1. public class ExampleApplication extends Application {  
2.   
3. public static RefWatcher getRefWatcher(Context context) {  
4.     ExampleApplication application = (ExampleApplication) context.getApplicationContext();  
5. return application.refWatcher;  
6.   }  
7.   
8. private RefWatcher refWatcher;  
9.   
10. @Override public void onCreate() {  
11. super.onCreate();  
12. if (LeakCanary.isInAnalyzerProcess(this)) {  
13. // This process is dedicated to LeakCanary for heap analysis.  
14. // You should not init your app in this process.  
15. return;  
16.     }  
17. this);  
18.   }  
19. }