内容简介:那么,该从上述示例代码可知:上述的引用关系会一直保持,直到
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更方便.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 可能会导致.NET内存泄露的8种行为
- Node 案发现场揭秘:文件句柄泄露导致进程假死
- 错误配置 Firebase 数据库导致 3000 多应用数据泄露
- 任何安全保护措施的MongoDB导致2.02亿用户数据泄露
- 导致数据库凭据泄露:详细分析Jenkins Swarm、Ansible、GitLab插件信息泄露漏洞(CVE-2019-10309/10...
- CNCERT:关于MongoDB数据库不当配置导致信息泄露风险情况通报
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
How to Solve It
Zbigniew Michalewicz、David B. Fogel / Springer / 2004-03-01 / USD 59.95
This book is the only source that provides comprehensive, current, and detailed information on problem solving using modern heuristics. It covers classic methods of optimization, including dynamic pro......一起来看看 《How to Solve It》 这本书的介绍吧!