//…
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
//…
}
//…
//回收 message, 见【3.5】
msg.recycleUnchecked();
}
}
loop()
里调用了 MessageQueue.next()
:
//MessageQueue
Message next() {
//…
for (;😉 {
//…
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//…
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
}
// Run the idle handlers. 关于 IdleHandler 自行了解
//…
}
}
还调用了 msg.target.dispatchMessage(msg)
,msg.target 就是发送该消息的 Handler,这样就回调到了 Handler 那边去了:
//Handler
public void dispatchMessage(Message msg) {
//msg.callback 是 Runnable ,如果是 post方法则会走这个 if
if (msg.callback != null) {
handleCallback(msg);
} else {
//callback 见【3.4】
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//回调到 Handler 的 handleMessage 方法
handleMessage(msg);
}
}
注意:dispatchMessage() 方法针对 Runnable 的方法做了特殊处理,如果是 ,则会直接执行 Runnable.run()
。
分析:Looper.loop() 是个死循环,会不断调用 MessageQueue.next() 获取 Message ,并调用 msg.target.dispatchMessage(msg)
回到了 Handler 来分发消息,以此来完成消息的回调。
注意:loop()方法并不会卡死主线程,见【6】。
那么线程的切换又是怎么回事呢?
很多人搞不懂这个原理,但是其实非常简单,我们将所涉及的方法调用栈画出来,如下:
Thread.foo(){
Looper.loop()
-> MessageQueue.next()
-> Message.target.dispatchMessage()
-> Handler.handleMessage()
}
显而易见,Handler.handleMessage() 所在的线程最终由调用 Looper.loop() 的线程所决定。
平时我们用的时候从异步线程发送消息到 Handler,这个 Handler 的 handleMessage()
方法是在主线程调用的,所以消息就从异步线程切换到了主线程。
2.3 图解原理
文字版的原理解析到这里就结束了,如果你看到这里还是没有懂,没关系,我特意给你们准备了些图,配合着前面几个章节,再多看几遍,一定可以吃透。
图片来源见【6】
2.4 小结
Handler 的背后有着 Looper 以及 MessageQueue 的协助,三者通力合作,分工明确。
尝试小结一下它们的职责,如下:
- Looper :负责关联线程以及消息的分发在该线程下**从 MessageQueue 获取 Message,分发给 Handler ;
- MessageQueue :是个队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message ;
- Handler : 负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节。
对【2】章节提出的问题用一句话总结:
Handler 发送的消息由 MessageQueue 存储管理,并由 Loopler 负责回调消息到 handleMessage()。
线程的转换由 Looper 完成,handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定。
3. Handler 的延伸
Handler 虽然简单易用,但是要用好它还是需要注意一点,另外 Handler相关 还有些鲜为人知的知识技巧,比如 IdleHandler。
由于 Handler 的特性,它在 Android 里的应用非常广泛,比如: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。
这些我会讲解一些,我没讲到的可以自行搜索相关内容进行了解。
3.1 Handler 引起的内存泄露原因以及最佳解决方案
Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。
这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。
解决该问题的最有效的方法是:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并及时移除所有消息。
示例代码如下:
private static class SafeHandler extends Handler {
private WeakReference ref;
public SafeHandler(HandlerActivity activity) {
this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
HandlerActivity activity = ref.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
并且再在 Activity.onDestroy()
前移除消息,加一层保障:
@Override
protected void onDestroy() {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
这样双重保障,就能完全避免内存泄露了。
注意:单纯的在 onDestroy
移除消息并不保险,因为 onDestroy
并不一定执行。
3.2 为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?
前面我们提到了每个Handler 的线程都有一个 Looper ,主线程当然也不例外,但是我们不曾准备过主线程的 Looper 而可以直接使用,这是为何?
注意:通常我们认为 ActivityThread 就是主线程。事实上它并不是一个线程,而是主线程操作的管理者,所以吧,我觉得把 ActivityThread 认为就是主线程无可厚非,另外主线程也可以说成 UI 线程。
在 ActivityThread.main() 方法中有如下代码:
//android.app.ActivityThread
public static void main(String[] args) {
//…
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//…
Looper.loop();
throw new RuntimeException(“Main thread loop unexpectedly exited”);
}
Looper.prepareMainLooper(); 代码如下:
/**
- Initialize the current thread as a looper, marking it as an
- application’s main looper. The main looper for your application
- is created by the Android environment, so you should never need
- to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException(“The main Looper has already been prepared.”);
}
sMainLooper = myLooper();
}
}
可以看到在 ActivityThread 里 调用了 Looper.prepareMainLooper() 方法创建了 主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接使用 Handler 了。
注意:Looper.loop()
是个死循环,后面的代码正常情况不会执行。
3.3 主线程的 Looper 不允许退出
如果你尝试退出 Looper ,你会得到以下错误信息:
Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
at android.os.MessageQueue.quit(MessageQueue.java:415)
at android.os.Looper.quit(Looper.java:240)
why? 其实原因很简单,主线程不允许退出,退出就意味 APP 要挂。
3.4 Handler 里藏着的 Callback 能干什么?
在 Handler 的构造方法中有几个 要求传入 Callback ,那它是什么,又能做什么呢?
来看看 Handler.dispatchMessage(msg)
方法:
public void dispatchMessage(Message msg) {
//这里的 callback 是 Runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到 Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg)
方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。
这个就很有意思了,这有什么作用呢?
我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息!
场景:Hook ActivityThread.mH , 在 ActivityThread 中有个成员变量 mH
,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。
3.5 创建 Message 实例的最佳方式
由于 Handler 极为常用,所以为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗。
方法有二:
- 通过 Message 的静态方法
Message.obtain();
获取; - 通过 Handler 的公有方法
handler.obtainMessage();
。
3.6 子线程里弹 Toast 的正确姿势
当我们尝试在子线程里直接去弹 Toast 的时候,会 crash :
java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
本质上是因为 Toast 的实现依赖于 Handler,按子线程使用 Handler 的要求修改即可(见【2.1】),同理的还有 Dialog。
正确示例代码如下:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(HandlerActivity.this, “不会崩溃啦!”, Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();
3.7 妙用 Looper 机制
我们可以利用 Looper 的机制来帮助我们做一些事情:
- 将 Runnable post 到主线程执行;
- 利用 Looper 判断当前线程是否是主线程。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
结尾
最后,针对上面谈的内容,给大家推荐一个Android资料,应该对大家有用。
首先是一个知识清单:(对于现在的Android及移动互联网来说,我们需要掌握的技术)
泛型原理丶反射原理丶Java虚拟机原理丶线程池原理丶
注解原理丶注解原理丶序列化
Activity知识体系(Activity的生命周期丶Activity的任务栈丶Activity的启动模式丶View源码丶Fragment内核相关丶service原理等)
代码框架结构优化(数据结构丶排序算法丶设计模式)
APP性能优化(用户体验优化丶适配丶代码调优)
热修复丶热升级丶Hook技术丶IOC架构设计
NDK(c编程丶C++丶JNI丶LINUX)
如何提高开发效率?
MVC丶MVP丶MVVM
微信小程序
Hybrid
Flutter
接下来是资料清单:(敲黑板!!!)
1.数据结构和算法
2.设计模式
3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记
4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)
不论遇到什么困难,都不应该成为我们放弃的理由!共勉~
如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
9764961)]
3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记
[外链图片转存中…(img-pwgFOe4Z-1711859764962)]
4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)
[外链图片转存中…(img-9u3Xu2Be-1711859764962)]
不论遇到什么困难,都不应该成为我们放弃的理由!共勉~
如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。
[外链图片转存中…(img-JyXGo2BQ-1711859764962)]