稀土 2018-01-25
Android
开发中,内存泄露 十分常见Handler
中发生的内存泄露阅读本文前,建议先阅读文章:Android开发:Handler异步通信机制全面解析(包含Looper、Message Queue)
Handler
的一般用法 = 新建Handler
子类(内部类) 、匿名Handler
内部类/** * 方式1:新建Handler子类(内部类) */ public class MainActivity extends AppCompatActivity { public static final String TAG = "carson:"; private Handler showhandler; // 主线程创建时便自动创建Looper & 对应的MessageQueue // 之后执行Loop()进入消息循环 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //1. 实例化自定义的Handler类对象->>分析1 //注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue showhandler = new FHandler(); // 2. 启动子线程1 new Thread() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 1;// 消息标识 msg.obj = "AA";// 消息存放 // b. 传入主线程的Handler & 向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); // 3. 启动子线程2 new Thread() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 2;// 消息标识 msg.obj = "BB";// 消息存放 // b. 传入主线程的Handler & 向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); } // 分析1:自定义Handler子类 class FHandler extends Handler { // 通过复写handlerMessage() 从而确定更新UI的操作 @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.d(TAG, "收到线程1的消息"); break; case 2: Log.d(TAG, " 收到线程2的消息"); break; } } } } /** * 方式2:匿名Handler内部类 */ public class MainActivity extends AppCompatActivity { public static final String TAG = "carson:"; private Handler showhandler; // 主线程创建时便自动创建Looper & 对应的MessageQueue // 之后执行Loop()进入消息循环 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //1. 通过匿名内部类实例化的Handler类对象 //注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue showhandler = new Handler(){ // 通过复写handlerMessage()从而确定更新UI的操作 @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.d(TAG, "收到线程1的消息"); break; case 2: Log.d(TAG, " 收到线程2的消息"); break; } } }; // 2. 启动子线程1 new Thread() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 1;// 消息标识 msg.obj = "AA";// 消息存放 // b. 传入主线程的Handler & 向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); // 3. 启动子线程2 new Thread() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 2;// 消息标识 msg.obj = "BB";// 消息存放 // b. 传入主线程的Handler & 向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); } }
测试结果
上述例子虽可运行成功,但代码会出现严重警告:
Handler
类由于无设置为 静态类,从而导致了内存泄露Handler
类的外部类:MainActivity
类那么,该Handler
在无设置为静态类时,为什么会造成内存泄露呢?
Looper
对象的生命周期 = 该应用程序的生命周期Java
中,非静态内部类 & 匿名内部类都默认持有 外部类的引用从上述示例代码可知:
Handler
实例的消息队列有2个分别来自线程1、2的消息(分别 为延迟1s
、6s
)Handler
消息队列 还有未处理的消息 / 正在处理消息时,消息队列中的Message
持有Handler
实例的引用Handler
= 非静态内部类 / 匿名内部类(2种使用方式),故又默认持有外部类的引用(即MainActivity
实例),引用关系如下图上述的引用关系会一直保持,直到Handler
消息队列中的所有消息被处理完毕
Handler
消息队列 还有未处理的消息 / 正在处理消息时,此时若需销毁外部类MainActivity
,但由于上述引用关系,垃圾回收器(GC)
无法回收MainActivity
,从而造成内存泄漏。如下图:Handler
消息队列 还有未处理的消息 / 正在处理消息时,存在引用关系: “未被处理 / 正处理的消息 -> Handler
实例 -> 外部类”Handler
的生命周期 > 外部类的生命周期 时(即 Handler
消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁时),将使得外部类无法被垃圾回收器(GC)
回收,从而造成 内存泄露从上面可看出,造成内存泄露的原因有2个关键条件:
Handler
实例 -> 外部类” 的引用关系Handler
的生命周期 > 外部类的生命周期即 Handler
消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁
解决方案的思路 = 使得上述任1条件不成立 即可。
原理静态内部类 不默认持有外部类的引用,从而使得 “未被处理 / 正处理的消息 -> Handler
实例 -> 外部类” 的引用关系 的引用关系 不复存在。
具体方案将Handler
的子类设置成 静态内部类
public class MainActivity extends AppCompatActivity { public static final String TAG = "carson:"; private Handler showhandler; // 主线程创建时便自动创建Looper & 对应的MessageQueue // 之后执行Loop()进入消息循环 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //1. 实例化自定义的Handler类对象->>分析1 //注: // a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue; // b. 定义时需传入持有的Activity实例(弱引用) showhandler = new FHandler(this); // 2. 启动子线程1 new Thread() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 1;// 消息标识 msg.obj = "AA";// 消息存放 // b. 传入主线程的Handler & 向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); // 3. 启动子线程2 new Thread() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 2;// 消息标识 msg.obj = "BB";// 消息存放 // b. 传入主线程的Handler & 向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); } // 分析1:自定义Handler子类 // 设置为:静态内部类 private static class FHandler extends Handler{ // 定义 弱引用实例 private WeakReference<Activity> reference; // 在构造方法中传入需持有的Activity实例 public FHandler(Activity activity) { // 使用WeakReference弱引用持有Activity实例 reference = new WeakReference<Activity>(activity); } // 通过复写handlerMessage() 从而确定更新UI的操作 @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.d(TAG, "收到线程1的消息"); break; case 2: Log.d(TAG, " 收到线程2的消息"); break; } } } }
原理不仅使得 “未被处理 / 正处理的消息 -> Handler
实例 -> 外部类” 的引用关系 不复存在,同时 使得 Handler
的生命周期(即 消息存在的时期) 与 外部类的生命周期 同步
具体方案当 外部类(此处以Activity
为例) 结束生命周期时(此时系统会调用onDestroy()
),清除 Handler
消息队列里的所有消息(调用removeCallbacksAndMessages(null)
)
具体代码
@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); // 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期 }
为了保证Handler
中消息队列中的所有消息都能被执行,此处推荐使用解决方案1解决内存泄露问题,即 静态内部类 + 弱引用的方式
Handler
造成 内存泄露的相关知识:原理 & 解决方案Android
开发中关于内存泄露的知识,有兴趣可以继续关注Carson_Ho的安卓开发笔记