性能优化框架-LeakCanary使用方法总结

LeakCanary

2019-2-16 20:11 - QG2017移动组 - 张艺隽

LeakCanary是一个用于检测内存泄露的框架,最近被app的崩溃折磨到头皮发麻,特地前来学习。

  • 来自官方的说明:

A memory leak detection library for Android and Java.

“A small leak will sink a great ship.” - Benjamin Franklin

本杰明·佛兰克林:千里之堤,溃于蚁穴

在app开发中,内存泄露经常会被我忽视,但在最近制作的app中需要频繁进行网路访问,十多个小时的长时间运行和数千次的频繁网路访问造成了app的oom问题。

0. 参考资料

LeakCanary,30分钟从入门到精通

LeakCanary中文说明

1. 开始使用

  1. 导入依赖
dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
  // Optional, if you use support library fragments:
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}
  1. Application中加载LeakCanary
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.
        LeakCanary.install(this);
    }
    // Normal app init code...
  }
}

2. watch()

LeakCanary.install(Application application);

调用这个方法后,所有Activity都会列入到被观测内存泄露的范围,但除了Activity之外,还有很多对象需要使用watch()单独进行观测:

LeakCanary.installedRefWatcher().watch(this);

3. 分析结果

  1. 引用链中类WXApiImplV10的静态属性activityCb引用了WXApiImplV10$ActivityLifecycleCb的context,导致Context对象无法被gc回收。

  2. 引用链末端即无法被回收,正在占用内存的对象。

4. 解决内存泄露

1. 单例模式导致的内存泄露:
public class Config{

    private Context context;
    
    private static final Config Instance = new Config();
    
}

将会引起编译器提示:

Do not place Android context classes in static fields (static reference to Config which has field context pointing to Context); this is a memory leak (and also breaks Instant Run)

单例对象的生命周期和整个app的生命周期一样长,如果Context引用的对象不是Application(例如Activity)的话,那么这个对象将被持续占用而无法被gc回收.

  • 在给单例传入Context的时候,最好使用这种写法:
public void setContext(Context context){
    this.context = context.getApplicationContext();
}
2. 内部类导致的内存泄露:

android-内部类导致的内存泄漏实战解析

在使用Thread、TimerTask、AsyncTask等经常用到耗时操作的类时需要格外注意内存泄露的问题:

性能优化框架-LeakCanary使用方法总结_第1张图片

MainActivity的内部类LeakThread引用了LeakThread的this$0this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是引用链末端的MainActivity的实例,由此导致MainActivity无法被回收,造成内存泄露。

解决办法有两种:

  • 更改为静态内部类
new Thread(new MyRunnable()).start();

private static class MyRunnable implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(15000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  • 使用弱/软引用
private static class MyRunnable implements Runnable {

    //gc正常回收弱引用对象
    WeakReference weakRef;

    MyRunnable(MainActivity mainActivity){
        weakRef = new WeakRerence(mainActivity);
    }

    @Override
    public void run() {
        try {
            Thread.sleep(15000);
            
            //使用弱引用,当activity被回收时就不用执行
            MainActivity activity = waekRef.get();
            if(activity != null){
                activity.doSomeThing();
            }
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
3. 资源未关闭造成的内存泄露:

Cursor、BroadcastReceiver、TypedArray、File、Stream、Bitmap等使用完毕后没有及时关闭或反注册等导致的内存泄露。

5. 上传到服务器

特殊情况下需要将内存泄露的的泄露轨迹和堆转储发送到服务器。需要两步完成配置:

1. 编写Service继承自DisplayLeakService

public class LeakUploadService extends DisplayLeakService {

  @Override protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
    if (!result.leakFound || result.excludedLeak) {
      return;
    }
    if (result.leakFound) {
      uploadLeakToServer(result, leakInfo);
    }
  }

  private void uploadLeakToServer(AnalysisResult result, String leakInfo) {
    Client bugsnagClient = new Client(getApplication(), "YOUR_BUGSNAG_API_KEY", false);
    bugsnagClient.setSendThreads(false);
    bugsnagClient.beforeNotify(error -> {
      // Bugsnag does smart grouping of exceptions, which we don't want for leak traces.
      // So instead we rely on the SHA-1 of the stacktrace, which has a low risk of collision.
      String stackTraceString = Logs.getStackTraceString(error.getException());
      String uniqueHash = Strings.createSHA1Hash(stackTraceString);
      error.setGroupingHash(uniqueHash);
      return true;
    });

    MetaData metadata = new MetaData();
    metadata.addToTab("LeakInfo", "LeakInfo", leakInfo);
    bugsnagClient.notifyBlocking(result.leakTraceAsFakeException(), Severity.ERROR, metadata);
  }
}

其中一个方法:

/* 方法:将需要的泄露信息转为异常,以结合其他crash上报工具/框架将信息传到后台 */
AnalysisResult.leakTraceAsFakeException()

2. 指定LeakCanary的监听服务类:

public class DebugExampleApplication extends ExampleApplication {
  @Override protected void installLeakCanary() {
    RefWatcher refWatcher = LeakCanary.refWatcher(this)
      .listenerServiceClass(LeakUploadService.class);
      .buildAndInstall();
  }
}

6. 其他

忽略指定的引用造成的内存泄露、忽略指定Activity的检测、运行在Instrumentation Test中……这些自定义功能可以在官方wiki文档中找到

Customizing LeakCanary


结语:还是有好多内存泄露的地方看不懂原因是什么
ActivityThread$ApplicationThread.this 0 T o a s t 0 Toast 0ToastTN.mNextView
最后干脆让app运行10个小时自动重启一遍算了……

#LeakCanary - 完()

你可能感兴趣的:(性能优化框架-LeakCanary使用方法总结)