1. 问题描述
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 在无设置为静态类时, 为什么会造成内存泄露呢?
2. 原因讲解
2.1 储备知识
主线程的 Looper 对象的生命周期 = 该应用程序的生命周期
在 Java 中, 非静态内部类 & 匿名内部类都默认持有 外部类的引用
2.2 泄露原因描述
从上述示例代码可知:
上述的 Handler 实例的消息队列有 2 个分别来自线程 1,2 的消息(分别 为延迟 1s,6s)
在 Handler 消息队列 还有未处理的消息 / 正在处理消息时, 消息队列中的 Message 持有 Handler 实例的引用
由于 Handler = 非静态内部类 / 匿名内部类(2 种使用方式), 故又默认持有外部类的引用(即 MainActivity 实例), 引用关系如下图
上述的引用关系会一直保持, 直到 Handler 消息队列中的所有消息被处理完毕
示意图
在 Handler 消息队列 还有未处理的消息 / 正在处理消息时, 此时若需销毁外部类 MainActivity, 但由于上述引用关系, 垃圾回收器 (GC) 无法回收 MainActivity, 从而造成内存泄漏. 如下图:
示意图
2.3 总结
当 Handler 消息队列 还有未处理的消息 / 正在处理消息时, 存在引用关系: "未被处理 / 正处理的消息 -> Handler 实例 -> 外部类"
若出现 Handler 的生命周期> 外部类的生命周期 时 (即 Handler 消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁时), 将使得外部类无法被垃圾回收器(GC) 回收, 从而造成 内存泄露
3. 解决方案
从上面可看出, 造成内存泄露的原因有 2 个关键条件:
存在 "未被处理 / 正处理的消息 -> Handler 实例 -> 外部类" 的引用关系
Handler 的生命周期> 外部类的生命周期
即 Handler 消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁
解决方案的思路 = 使得上述任 1 条件不成立 即可.
解决方案 1: 静态内部类 + 弱引用
原理
静态内部类 不默认持有外部类的引用, 从而使得 "未被处理 / 正处理的消息 -> Handler 实例 -> 外部类" 的引用关系 的引用关系 不复存在.
具体方案
将 Handler 的子类设置成 静态内部类
同时, 还可加上 使用 WeakReference 弱引用持有 Activity 实例
原因: 弱引用的对象拥有短暂的生命周期. 在垃圾回收器线程扫描时, 一旦发现了只具有弱引用的对象, 不管当前内存空间足够与否, 都会回收它的内存
解决代码
- 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;
- }
- }
- }
- }
解决方案 2: 当外部类结束生命周期时, 清空 Handler 内消息队列
原理
不仅使得 "未被处理 / 正处理的消息 -> Handler 实例 -> 外部类" 的引用关系 不复存在, 同时 使得 Handler 的生命周期(即 消息存在的时期) 与 外部类的生命周期 同步
具体方案
当 外部类(此处以 Activity 为例) 结束生命周期时(此时系统会调用 onDestroy()), 清除 Handler 消息队列里的所有消息(调用 removeCallbacksAndMessages(null))
具体代码
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mHandler.removeCallbacksAndMessages(null);
- // 外部类 Activity 生命周期结束时, 同时清空消息队列 & 结束 Handler 生命周期
- }
使用建议
为了保证 Handler 中消息队列中的所有消息都能被执行, 此处推荐使用解决方案 1 解决内存泄露问题, 即 静态内部类 + 弱引用的方式
4. 总结
本文主要讲解了 Handler 造成 内存泄露的相关知识: 原理 & 解决方案
实际中我们使用 rxjava 更方便.
来源: https://juejin.im/post/5c6fe06d518825640d1dbc53