Handler 消息机制以及内存泄漏
1. 消息机制
1.1 post
系列
通过查看源码可知,post(Runnable r)
、postDelayed(Runnable r, long delayMillis)
最终调用的都是sendMessageDelayed
方法:
// post public final boolean post(Runnable r){ return sendMessageDelayed(getPostMessage(r), 0); } // postDelayed public final boolean postDelayed(Runnable r, long delayMillis){ return sendMessageDelayed(getPostMessage(r), delayMillis); }
1.2 postAtTime
postAtTime(Runnable r, long uptimeMillis)
最终调用的是sendMessageAtTime
方法:
// postAtTime public final boolean postAtTime(Runnable r, Object token, long uptimeMillis){ return sendMessageAtTime(getPostMessage(r, token), uptimeMillis); }
这里面都有一个共同的方法getPostMessage
:
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
m.callback = r
这句可以看出:getPostMessage
就是把传入的 Runnable 赋值给 Message 对象的 callback 属性。
1.3 sendEmptyMessage
sendEmptyMessage
最终指向sendEmptyMessageDelayed
函数:
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
msg.what = what
这句可以看出:sendEmptyMessageDelayed
就是把 what 赋值给 Message 的 what 属性。
1.4 sendMessage(msg : Message)
至于常用的sendMessage(msg : Message)
就不用细说了,这是直接传入 Message 类型的参数。
综合以上这几点来说,各种发送消息的方法最终都是把消息赋值给 Message 对象(或者 Message 的属性),而且这些方法最终调用的都是 MessageQueue 中的
enqueueMessage
方法,就是把消息加入消息队列
1.5 enqueueMessage
方法
方法较长,我们看看关键的几行:
Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg;
用一个无限循环将消息加入到消息队列中(链表的形式),到这里把消息发出去并加入队列这两步算是完成了,接下来就是取出并处理消息。
1.6 Looper 取出消息
Looper 中有一个死循环(Looper.loop()
)用来不断从队列中取出消息:
public static void loop() { final Looper me = myLooper(); final MessageQueue queue = me.mQueue; for (;;) { Message msg = queue.next(); ...代码省略 msg.target.dispatchMessage(msg); ...代码省略 msg.recycleUnchecked(); } }
queue.next()
每次取出一条 Message 消息,然后交由msg.targer.dispatchMessage(msg)
处理,从上篇文章中可以知道,msg.targer
就是发出消息的 Handler,所以我们只需要关注dispatchMessage(msg)
。
1.7 dispatchMessage(msg)
处理消息
dispatchMessage(msg)
在 Handler 类中
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
- msg 的 callback 不为空,调用
handleCallback
方法(message.callback.run()) - mCallback 不为空,调用
mCallback.handleMessage(msg)
- 最后如果其他都为空,执行 Handler 自身的
handleMessage(msg)
方法
第 1 点就是上面的 【1.1
post
系列】 和 【1.2postAtTime
】,第 3 点就是我们最常见的handleMessage
方法。需要注意一下就是callback.run()
这里直接调用线程的 run 方法,相当于普通方法调用,不会开启新的线程。
现在谈谈第 2 点,Handler 有很多种构造方法,除了上一篇文章提到的public Handler(Looper looper)
和Handler()
等,还有一个:
public Handler(Looper looper, Callback callback) { this(looper, callback, false); }
Callback 是这样的:
public interface Callback { public boolean handleMessage(Message msg); }
需要重写handleMessage
,这不就是 Handler 里面的handleMessage
吗?其实两者是有区别的:
// Handler public void handleMessage(Message msg) {} // Callback public boolean handleMessage(Message msg);
Callback 里面的handleMessage
返回值是 Boolean 类型的,那么接下来分别返回 true 和 false 看看效果吧:
class MainActivity : AppCompatActivity() { var handler: Handler? = null var looper: Looper? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) looper = Looper.getMainLooper() val callback = object: Handler.Callback{ override fun handleMessage(msg: Message?): Boolean { Log.e("abc","--- Callback:threadName ---" + Thread.currentThread().name ) return true } } val thread = object : Thread() { override fun run() { super.run() handler = object : Handler(looper, callback) { override fun handleMessage(msg: Message?) { super.handleMessage(msg) Log.e("abc","--- handleMessage:threadName ---" + Thread.currentThread().name ) } } } } thread.start() myBtn.setOnClickListener { handler?.sendEmptyMessage(4) } } } // Log 打印情况 --- Callback:threadName ---main
如果返回值类型改成 false:
val callback = object: Handler.Callback{ override fun handleMessage(msg: Message?): Boolean { Log.e("abc", "--- Callback:threadName ---" + Thread.currentThread().name ) return false } } // Log 打印情况 --- Callback:threadName ---main --- handleMessage:threadName ---main
所以,Callback 中的handleMessage
返回 true 就不继续执行 Handler 中的handlerMessage
了,反之则两个handleMessage
都执行。其实这些从dispatchMessage
方法中可以看出来(返回 true 则 return 终止,否则继续执行 handleMessage):
if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg);
总结
上面主要讲了消息的发送和取出,大概知道了 Handler 消息机制的工作流程:
- Handler 对象通过
post
(postDelayed
、postAtTime
)或者sendMessage
(sendEmptyMessage
)把消息(Message)交给 MessageQueue -
MessageQueue.enqueueMessage
方法将 Message 以链表的形式放入消息队列中 -
Looper.loop()
循环调用 MessageQueue 的next()
取出消息,交给 Handler 的dispatchMessage
方法处理消息 -
dispatchMessage()
中分别判断msg.callback
和构造函数传入的mCallback
是否为空,不为空则执行它们的回调,为空则执行 Handler 自身的handlerMessage
方法。
2. Handler 内存泄漏问题
2.1 引起内存泄漏的原因
下面这样写会有内存泄漏风险:
val mHandler = object : Handler() { override fun handleMessage(msg: Message?) { super.handleMessage(msg) } }
Android Studio 会标黄警告,鼠标放在 handler 代码块部分还会弹出提示,大概意思就是建议你用静态模式或者弱引用。上面这种写法相当于定义了一个匿名内部类,非静态的匿名内部类默认是持有外部类(对应 Activity 等)引用的。如果发消息的 handler 所在线程还在执行,当前 Activity 就被 finish 了,那么该 Handler 的匿名内部类持有 Activity 的引用,所以 Activity 对象是无法被 GC 机制回收的。即:执行了 finish 代码,但 Activity 对象还在内存中(内存泄漏)。这种对象如果越来越多,就会有 OOM(内存溢出)的可能。
2.2 解决办法
kotlin 中没有静态类这个概念,这里用 java 静态内部类举例:
static class MyHandler extends Handler { WeakReference<MyActivity> weakActivity; MyHandler(MyActivity activity) { weakActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); MyActivity activity = weakActivity.get(); // activity.text = "......" } }
- 静态内部类不持有外部类引用,所以不会导致 Activity 对象泄漏(java 中 「静态的」等于「类的」,静态内部类如果能持有外部类引用,那说明外部类的引用就是内部「类的」,这不符合逻辑,这样写编译都不通过)。
- 但该静态内部类必须使用外部类的引用(比如操作 UI),此时就可以用弱引用的方式。上面代码用的是把 Activity 的弱引用在 Handler 构造函数中初始化,这样如果需要操作 UI,可以使用
activity.text = "test"
这种方式。
参考文章:
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
【云栖号在线课程】本周重磅推荐:湖畔大学执行教育长陈龙 讲述疫情下的全球经济
云栖号在线课堂,及时了解行业动态!阿里云推出疫情专题方案,为企业业务护航,让你足不出户了解行业动态。 在这里可以走近阿里云基础产品,了解更多应用方案,还能遇见大咖分享洞见及故事!也可以通过视频的形式让你高效、生动的了解场景化的上云最佳实践。 本周重磅推荐 标题:罗汉堂秘书长讲述:疫情下的全球经济 简介:新冠疫情已经蔓延至全球。继中国付出沉痛代价以后,欧美成为新的重灾区,很多国家在佛系抗疫和经济停摆之间摇摆,美国股市十天经历了史无前例的四次熔断。疫情会把世界带往何方?过载的各类判断和信息充斥在巨大的不定性和焦虑之中。 此次分享围绕疫情、经济、全球三个关键词,从宏观经济的角度,探讨疫情的持续性、蔓延势头以及对全球经济、金融格局和供应链等影响。 观看直播 标题:从数字化到数智化:盒马新零售操作系统的建设 简介:企业面对数字化转型,线上线下一体化的背景下,做为CIO 和产研团队,我们所要面对的主要挑战是什么?如何重新定位自身的角色?我们会有什么机会呢?大少做为新零售领军企业盒马的产研负责人,他是如何思考的,又有什么最佳实践呢。 观看直播 标题:阿里云新品发布会第88期:分析型数据库Analyt...
- 下一篇
Qt编写地图综合应用4-仪表盘
一、前言 仪表盘在很多汽车和物联网相关的系统中很常用,最直观的其实就是汽车仪表盘,这个以前主要是机械的仪表,现在逐步改成了智能的带屏带操作系统的仪表,这样美观性和拓展性功能性大大增强了,上了操作系统的话,除了基本的仪表指示以外,还可以听歌导航接电话等,这应该也是目前汽车领域发展的一个大趋势,Qt在这方面还单独搞了个3D studio设计的,可以让美工直接设计好效果图,设置一些动画效果之类的,可以说这些年Qt公司也在不断谋求新的发展,探索新的机遇,找到新的增长点和突破口。 用Qt开发仪表盘控件非常方便,无论是用widget的painter还是qml,尤其是qml,内置的那些动画效果非常适合做这类的应用,这次不讨论如何用qt开发仪表盘,而是直接用echart内置的仪表盘控件,做的也挺好的,不知道echart这么小的一个文件,还能有如此多的效果,连仪表盘都有,这个非常震惊,仪表盘的使用在官网非常详细,与Qt的结合难点可能就在如何交互,Qt中无论是webkit也还还是webengine,都提供了runJavaScript或者evaluateJavaScript函数类来执行js函数,只需要在ht...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS6,CentOS7官方镜像安装Oracle11G
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- 设置Eclipse缩进为4个空格,增强代码规范
- Mario游戏-低调大师作品
- MySQL8.0.19开启GTID主从同步CentOS8
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果