Android 检测内存泄露神器-leakcanary

y516783369 7年前
   <p>Android 检测内存泄漏,必须使用方便强大到灭绝人性的 <a href="/misc/goto?guid=4958874766191309432" rel="nofollow,noindex">leakcanary</a> 。</p>    <p>leakcanary 是 <a href="/misc/goto?guid=4959550706928908690" rel="nofollow,noindex">square</a> 公司开发的,square 拥有众多强大的 Android 开源项目,如,OkHttp、retrofit、otto、picasso,简直撑起了Android 开发的半边天。</p>    <p>一行代码就可以捕找到已经泄漏的内存泄漏,并且显示出出现内存泄漏的变量或线程、泄漏时的引用路径和出现泄漏的地方。</p>    <h2>使用</h2>    <p>1.添加依赖</p>    <pre>  <code class="language-java">dependencies {     debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'     releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'     testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'   }</code></pre>    <p>2.初始化 leakcanary</p>    <pre>  <code class="language-java">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...    }  }</code></pre>    <h2>用例</h2>    <p>写一段内存泄露的代码。</p>    <p>MainActivity.java</p>    <pre>  <code class="language-java">public class MainActivity extends AppCompatActivity {        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);      }        public void onClick(View view) {          test();          finish();          startActivity(new Intent(MainActivity.this, Main2Activity.class));      }      // 这里会发生内存泄漏      public void test() {          new Thread(new Runnable() {              @Override              public void run() {                  while (true) {                      try {                          Thread.sleep(1000);                      } catch (InterruptedException e) {                          e.printStackTrace();                      }                  }              }          }).start();      }  }</code></pre>    <p>MainActivity 中点击按钮后,'test()' 方法内部匿名内部类执行了耗时任务,并且同时 finish() 掉 MainActivity,但是此匿名内部类依然在运行任务,并且隐式的持有 MainActivity 引用,导致 MainActivity 不能及时被 GC 回收,导致内存泄露。</p>    <p>LeakCanary 检测出内存泄露后,会在状态栏显示一条通知,点进去就可以看到详细信息。如下图:</p>    <p><img src="https://simg.open-open.com/show/b737e9ab22b3e95ab8c86cde89692924.png"></p>    <p>leak.png</p>    <p>含义:</p>    <p>标题栏显示内存泄露的类和泄露的内存大小,菜单栏提供分享出更详细的信息,包括堆栈信息或者 .hprof 文件。蓝色栏显示包名,第一行显示出现泄露的线程,下面几行显示所有的引用,最后一行显示泄露的类。</p>    <p>MainActivity$1.this$0 的含义:</p>    <p>符号 “$” 代表后者是前者的内部类,“.”就是对象调用方法那个点。</p>    <p>用 “.” 分为两部分,前面整体代表 MainActivity 的一个匿名内部类,用 1 表示,在这里代表 Runnable 匿名类,后面部分 this$0 整体代表外部类。</p>    <p>看到这个内存泄露信息,首先定位到 MainActivity 中,同时可以看得出是 MainActivity 的实例出现的内存泄露,并且发生在子线程中,看到代码,我们就可以确认肯定是在 Runnale 匿名内部类中隐式的引用了 MainActivity 导致的内存泄露。</p>    <p>在这里打一个断点:</p>    <p><img src="https://simg.open-open.com/show/ad375f0792263b4e2abe3242f39edea9.png"></p>    <p>break.png</p>    <p>可以看到匿名类内部存在一个外部 MainActivity 的引用。</p>    <p>找到原因就好办了,静态化匿名内部类就解决问题了:</p>    <pre>  <code class="language-java">// 静态  public static void test() {      // ...   }</code></pre>    <p><img src="https://simg.open-open.com/show/0c51c295894fbe8c7b0375bd99a0afd8.png"></p>    <p>break2.png</p>    <p>静态化之后,发现该匿名内部类中不在持有外部类 MainActivity 对引用,也就不会在 MainActivity 销毁后,出现内存泄露了。</p>    <p>  </p>   <p>来自:https://juejin.im/post/58cba375128fe1006c84fc41</p>    <p></p>    <p> </p>