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问题。
LeakCanary,30分钟从入门到精通
LeakCanary中文说明
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'
}
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...
}
}
LeakCanary.install(Application application);
调用这个方法后,所有Activity都会列入到被观测内存泄露的范围,但除了Activity之外,还有很多对象需要使用watch()单独进行观测:
LeakCanary.installedRefWatcher().watch(this);
引用链中类WXApiImplV10的静态属性activityCb引用了WXApiImplV10$ActivityLifecycleCb的context,导致Context对象无法被gc回收。
引用链末端即无法被回收,正在占用内存的对象。
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回收.
public void setContext(Context context){
this.context = context.getApplicationContext();
}
android-内部类导致的内存泄漏实战解析
在使用Thread、TimerTask、AsyncTask等经常用到耗时操作的类时需要格外注意内存泄露的问题:
MainActivity的内部类LeakThread引用了LeakThread的this$0,this$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();
}
}
}
Cursor、BroadcastReceiver、TypedArray、File、Stream、Bitmap等使用完毕后没有及时关闭或反注册等导致的内存泄露。
特殊情况下需要将内存泄露的的泄露轨迹和堆转储发送到服务器。需要两步完成配置:
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();
}
}
忽略指定的引用造成的内存泄露、忽略指定Activity的检测、运行在Instrumentation Test中……这些自定义功能可以在官方wiki文档中找到
Customizing LeakCanary
结语:还是有好多内存泄露的地方看不懂原因是什么
ActivityThread$ApplicationThread.this 0 T o a s t 0 Toast 0ToastTN.mNextView
最后干脆让app运行10个小时自动重启一遍算了……
#LeakCanary - 完()