Android Profiler
注意:只支持5.0以后的Android设备
-
运行程序后,点击AS下方的工具栏
中的Android Profiler
-
直接使用Android上方菜单栏的
运行Android Profiler
打开MEMORY工具
-
在Android Profiler界面选择你的应用的包名
-
点击打开MEMORY工具
- ① 强制执行垃圾收集事件的按钮。
- ② 捕获堆转储的按钮。
- ③ 记录内存分配的按钮。
- ④ 放大时间线的按钮。
- ⑤ 跳转到实时内存数据的按钮。
- ⑥ 事件时间线显示活动状态、用户输入事件和屏幕旋转事件。
- ⑦ 内存使用时间表,其中包括以下内容:
* 每个内存类别使用多少内存的堆栈图,如左边的y轴和顶部的颜色键所示。
* 虚线表示已分配对象的数量,如右侧y轴所示。
* 每个垃圾收集事件的图标。
分析Heap Dump
-
点击图②按钮得到一个时间点的Heap Dump,如图
-
选择按照包名排序
-
这里只关心你的包名下的类
导出hprof文件并在Android Studio中分析
-
点击Heap Dump的导出图标
导出hprof文件
-
将hprof文件拖到AS中查看,如图
-
点击右边的Analyzer Tasks按钮,分析是否内存泄露
使用hprof-conv转换成MAT能打开的文件
MAT(Memory Analyzer Tool)的下载地址 https://www.eclipse.org/mat/
hprof-conv这个工具在Andorid sdk的platform-tools文件夹下,因为这个文件夹下的工具经常有使用到,可以加入到系统环境变量中
在命令和那个中执行以下命令:
成功后会得到MAT能打开的文件
leak_mat.hprof
使用MAT查看调用栈定位内存泄露的原因
- 在MAT中打开文件
leak_mat.hprof
,会显示饼图如下: - 点击上图中的红色圈中的按钮,查看直方图,它默认直接显示当前内存中各种类型对象的数量及这些对象的shallow heap和retained heap。
-
搜索内存泄露的类,并过滤掉软弱虚引用
-
查看调用链,找到内存泄露的原因
常见的内存泄露原因
具体代码详见:https://github.com/cl9/zero_performance_optimization
集合引起的内存泄露
-
经过下列操作
-
在AS中分析的结果
-
从AS中分析的结果可以看出,SetActivityXX对象会有好几个,在MAT中查看其调用链
从调用链可以看出来,
ActivityStackManager
中的instance
对象中的集合activityStack
对象持有SetActivityXX的对象,结合代码来看,在SetActivityXX销毁后,并没有将集合activityStack
中的activity移除
解决内存泄露
-
ActivityStackManager
中集合activityStack
对象保存的是Activity的强引用,可以考虑使用弱引用
private Stack<Activity> activityStack;
替换成private Stack<WeakReference<Activity>> activityStack;
,这样就能保证在发生GC后,回收分配给Activity的内存 - 在基类
BaseSetActivity
中,每次销毁Activity的时候,将其从集合activityStack
中移除
@Override
protected void onDestroy() {
super.onDestroy();
SolutionActivityStackManager.getAppManager().finishActivity(this);
}
-
修改后,重新检测在MAT中如图:
,一般会可能会出现两种结果,一种是上图,所有的SetActivityXX在内存中的对象是0,还有一种是直方图中搜索不到SetActivityXX了
单例引起的内存泄露
- 类似集合引起的内存泄露的操作
-
在AS中分析的结果
-
从AS中分析的结果可以看出,SingletonActivity对象会有好几个,在MAT中查看其调用链
从调用链可以看出来,
ToastManager
中的sInstance
对象中持有SingletonActivity的对象,结合代码来看,在SingletonActivity销毁后,由于ToastManager
的单例对象一直持有SingletonActivity的对象,所以SingletonActivity的对象不能正常回收引起内存泄露
解决内存泄露
-
ToastManager
中的单例对象持有的是Context的强引用,可以考虑使用弱引用
private Context mContext;
替换成private WeakReference<Context> wrContext;
,这样就能保证在发生GC后,回收分配给Context的内存 - 修改后,重新检测在MAT中如图:
注意:这样做会出现一个问题,就是当退出SingletonActivity后,再次进入SingletonActivity,点击弹出Toast就会没反应,因为当SingletonActivity销毁后,弱引用得到的Context就为null了,就不会再弹出Toast
补充方案 —— SolutionToastManager2
- 将
ToastManager
中的单例对象持有的mContext = context;
替换成mContext = context.getApplicationContext();
这样修改后就能同时解决内存泄露和上个方案出现的问题了
Handler引起的内存泄露
- 类似集合引起的内存泄露的操作
-
在AS中分析的结果
-
从AS中分析的结果可以看出,HandlerActivity对象会有好几个,在MAT中查看其调用链
从调用链可以看出来,消息队列中等待处理的消息持有HandlerActivity的对象,结合代码来看,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当HandlerActivity退出时,消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有HandlerActivity的引用,所以导致HandlerActivity的内存资源无法及时回收,引发内存泄漏
解决内存泄露
- 对Handler持有的对象使用弱引用,创建Handler时不要使用匿名类或者内部类,应该使用静态内部类
public static class MyHandler extends Handler {
WeakReference<Context> wrContext;
public MyHandler(Context context) {
this.wrContext = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Context context = wrContext.get();
if (context != null) {
Toast.makeText(context, "成功获取到数据", Toast.LENGTH_SHORT).show();
}
}
}
- 在Activity销毁后,移除消息队列中所有消息和所有的Runnable
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
-
修改后,重新检测后的结果:
,内存中已经没有HandlerActivity对象了
Thread引发的内存泄露
这个和Handler引发的内存泄露类似