首先大致介绍一下用mat分析内存的方法
在我们怀疑有内存泄漏的时候,dump heap file.(比如退出某一个activity的时候,怀疑activity未被回收)
此时我们拿到的是一个android studio上能识别的hprof 文件,如下图
目前android studio上分析内存功能有限,一般使用eclipse mat。
可以使用android/platform-tools目录下的工具hprof-conv 进行转换
如 hprof-conv android.hprof mat.hprof
点击这个tab后,可以输入类名进行搜索,如下图
搜索到类后,我们需要看看是否有强引用,也就是除弱引用与软引用外的reference,如下图
最终发现有两个强引用,也就是退出activity后,出现了内存泄漏,如下图
经检查,发现ConnectivityManager中sInstance hold了 StartupActivity,其中SystemServiceRegistry
也是用于获取系统Manager的类实际上是调用SystemServiceRegistry中getService方法。因此推断出,是获取ConnectivityManager时,传递了StartupActivity。
leakcanary是一个帮我们分析内存泄漏的工具,非常方便
源码和使用说明见github:
https://github.com/square/leakcanary
现在有了leakcanary,我们能方便的监控activity的内存泄漏了。例如上面的内存泄漏,leakcanary也能帮我们捕获到,如下图:
如果要了解详细的泄漏信息,可以点标题栏上的menu,将 dump file发送到电脑,然后利用mat进行上面的分析。
leakcanary 是如何监控activity内存泄漏的呢?主要原理是watch一个即将要销毁的对象,例如这个对象是activity的话,那么在Activity的onDestroy之后,leakcanary将会监控这个activity。
这里注意一下,leakcanary只在application onCreate时调用了LeakCanary.install(this);它是怎么监控activity的呢?主要靠的 application.registerActivityLifecycleCallbacks(listener),app中的每个activity的每一个生命周期都会回调listener中相关方法,从而leakcanary在生命周期相关方法中能取到Activity。
leakcanary 的watch流程是在主线程空闲时,delay 5秒后检查对象有没有被回收。其中空闲时执行check是用的IdleHandler。判断一个对象有没有被回收是用的WeakReference与ReferenceQuene, 如使用WeakReference(T referent, ReferenceQueue queue) 构造方法构造WeakReference时,如果这个WeakReference连接的对象被回收的话,系统会将WeakReference put进 ReferenceQuene,因此,如果ReferenceQuene中有哪个WeakReference,则表示这个WeakReference弱链接的对象已被回收。
如leakcanary 源码中watch对象代码如下:
空闲时check是否被回收代码如下:
判断内存是否被回收,没有被回收GC一次后再判断是否被回收,任然没有被回收则会通过Debug.dumpHprofData() 生成heap file,然后在service中通过haha(com.squareup.haha) 开源工具类进行文件分析,分析到强应用最短路径后,发送到UI线程弹出对话框。部分代码如下:
AppsFly是海外最流行的App安装与事件追踪最流行的平台之一,主要是被许多主流广告平台所认可。
官网: https://support.appsflyer.com/
由于分析之前的内存泄漏发现,除了增加了一个AppsFly初始化的操作外,并没有创建ConnectivityManager,而leakcanary 和 mat分析都表明有这个内存泄漏,因此计划了解一下AppsFly到底做了哪些操作。
初始化代码如下:
private void initAppsFlyerSDK() {
AppsFlyerLib.getInstance().setCollectAndroidID(false);
AppsFlyerLib.getInstance().setCollectIMEI(false);
AppsFlyerLib.getInstance().setDebugLog(AppEnvConfig.DEBUG);
AppsFlyerLib.getInstance().setAndroidIdData(AppEnvUtils.getAndroidID());
AppsFlyerLib.getInstance().setImeiData(AppEnvUtils.getIMEI(this.getApplicationContext()));
AppsFlyerLib.getInstance().startTracking(getApplication(), APPSFLYER_DEV_KEY);
}
显然没有传activity给AppsFly,继续进入AppsFlyerLib,直接搜索ConnectivityManager,找到了相关代码:
![这里写图片描述](https://img-blog.csdn.net/20160824003509606)
第1点不会出现内存泄漏,第2点,由于将Activity当Context传递给getNetwork方法。导致获取ConectivityManager后,系统没有及时释放activity。
这里注意一下,AppsFly初始化时也没有传activity,而查代码发现AppsFly也是通过注册生命周期回调application.registerActivityLifecycleCallbacks(listener),在回调中处理各个Activity的统计。
解决办法:由于问题原因是getSystemService是调用的Activity的getSystemService,而不是ApplicationContext导致的内存泄漏,因此我们可以在BaseActivity中重写getSystemService方法。除了LayoutInflater是有可能需要Activity上下文去加载View的,获取其他SystermManager都可以用ApplicationContext. 修改后测试,再没遇到Appsfly的内存泄漏
@Override
public Object getSystemService(@NonNull String name) {
if (AppEnv.DEBUG) {
OLog.d(getClass().getSimpleName(), "getSystemService name:" + name);
}
if (!Context.LAYOUT_INFLATER_SERVICE.equals(name)) {
return getApplicationContext().getSystemService(name);
}
return super.getSystemService(name);
}