目录: 1. 内存泄漏定义 2.Handler 造成内存泄漏的原因 3. 优化方案
1. 内存泄漏定义
首先我们需要了解 Java 中的常见内存分配, 包括静态存储区(方法区), 栈和堆等.
静态存储区: 存储的是静态方法和全局的 static 数据和常量等, 该区域的内存在程序编译时已经分配完成, 在程序运行的整个过程都存在. 栈区: 在执行方法时, 方法体内的局部变量 (包括基础数据类型和对象的引用等) 都在栈上创建, 在方法执行结束后, 该区域局部变量所持有的内存会自动释放. 栈内存分配运算内置于处理器的指令集中, 效率高, 但该区域的容量有限. 堆区: 又称动态分配区, 通常是存储 new 出来的对象的实例, 该部分内存在没有引用时会由 GC 回收.
因此, 我们通常说的内存泄漏是指: 在堆区不断的创建对象, 在该对象已经使用结束, 不会再使用该对象时, 但是还存在别的对象 (生命周期较长) 引用, 导致该对象无法及时被 GC 回收, 导致堆区可使用的内存越来越少, 导致内存泄漏的产生, 最终的后果就是 OOM. 其实 Android 中的内存泄漏的原因与 Java 中类似: 生命周期较长的对象持有生命周期较短的对象的引用.
2.Handler 造成内存泄漏的原因
在 Android 中的跨线程交互时, 尤其是子线程与 UI 线程交互时, 通过 Handler 在子线程中发送现象, Handler 中做更新 UI 的操作, 如下所示:
- private Hand = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- switch (msg.what){
- case UPDATE_UI:
- // 更新 UI 操作
- break;
- }
- }
- };
那么, 通过 Handler 为何可以在子线程发送消息, 在 handleMessage 中可以执行更新 UI 的操作? 我们知道通常只有在主线程 (通常指 UI 线程) 可以执行更新 UI 的操作, 因此最终还是调用 UI 线程更新. 调用流程分析如下:
2.1 创建 Handler 对象
查看源码可以看到如下解释, 当我们创建 Handler 对象时, 就与该线程和该线程的消息队列相绑定, 如果未与当前线程和线程队列绑定就无法正常执行事件的分发处理. 我们在主线程创建 Handler, 因此就与主线程相绑定, Handler 对象隐式的持有外部对象的引用, 该外部对象通常是指 Activity.
- When you create a new Handler, it is bound to the thread /* message queue of the thread that is creating it
- 2.2 消息队列处理
- 子线程执行处理结束后, 通过 Handler 将消息发送处理, 但如果此时当前界面已经销毁(Activity 销毁), 正常情况下, 如果 Activity 不在使用, 就可能被 GC 回收, 但由于子线程仍未处理结束, 仍持有 Handler 的引用(否则无法正常发送 Handler 消息), 而该 Handler 持有外部 Activty 的引用, 导致该 Activity 无法正常回收, 导致内存的泄漏. 该引用的链如下: MessageQueue -> Message -> Handler -> Activity
- 3. 优化方案
- Activity 销毁时及时清理消息队列;
- 自定义静态 Handler 类 + 软引用.
- 3.1 Activity 销毁时及时清理消息队列
- 在 Activity 销毁时, 调用 removeCallbacksAndMessages 清除 Message 和 Runnable.
- mHandler.removeCallbacksAndMessages(null);
- /* * Remove any pending posts of callbacks and sent messages whose
- * <var>obj</var> is <var>token</var>. If <var>token</var> is null,
- * all callbacks and messages will be removed.
- */
- public final void removeCallbacksAndMessages(Object token) {
- mQueue.removeCallbacksAndMessages(this, token);
- }
3.2 自定义静态 Handler 类 + 弱引用
- static class MyHandler extends Handler {
- WeakReference<Activity> mWeakReference;
- private MyHandler(WeakReference<Activity> mWeakReference){
- this.mWeakReference = mWeakReference;
- }
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- if(mWeakReference!=null){
- Activity activity = mWeakReference.get();
- if(activity!=null){
- //handler 消息处理
- }
- }
- }
- }
由 3.1 的分析中可以看出, Handler 造成内存泄漏的主要原因是持有当前 Activty 的强引用, 造成 Activity 无法及时被回收, 我们知道 GC 处理弱引用的机制为当对象销毁时, 即使有弱引用存在, 也会将其回收.
4. 总结
避免使用 Handler 造成内存泄漏的方法如下:
在 Activity 的 onDestory()方法中及时清理 handler 的消息队列;
自定义静态 Handelr 类, 避免非静态内部类造成内存泄漏;
使用弱引用, 使引用对象可以及时回收.
通过以上三种方式结合使用, 可以有效的避免使用 Handler 不当, 造成内存泄漏的情况.
来源: https://juejin.im/post/5c831e2a6fb9a049a97a8381