每日一问:Android 消息机制,我有必要再讲一次,事件分发源码

本文详细剖析了Android中Looper、MessageQueue和Handler的工作流程,重点解释了Handler发送消息、MessageQueue存储和轮询以及Looper如何通过ThreadLocal协调线程间的通信,揭示了Android消息处理的核心机制。
摘要由CSDN通过智能技术生成

// While calling an idle handler, a new message could have been delivered

// so go back and look again for a pending message without waiting.

nextPollTimeoutMillis = 0;

}

}

next() 方法其实很长,不过我们仅仅贴了极少的一部分,可以看到,里面不过是有一个 for (;? 的无限循环,循环体内部调用了一个 nativePollOnce(long, int) 方法。这是一个 Native 方法,实际作用是通过 Native 层的 MessageQueue 阻塞当前调用栈线程 nextPollTimeoutMillis 毫秒的时间。

下面是 nextPollTimeoutMillis 取值的不同情况的阻塞表现:

1.小于 0,一直阻塞,直到被唤醒;

2.等于 0,不会阻塞;

3.大于 0,最长阻塞 nextPollTimeoutMillis 毫秒,期间如被唤醒会立即返回。

可以看到,最开始 nextPollTimeoutMillis 的初始化值是 0,所以不会阻塞,会直接去取 Message 对象,如果没有取到 Message 对象数据,则直接会把 nextPollTimeoutMillis 置为 -1,此时满足小于 0 的条件,会被一直阻塞,直到其他地方调用另外一个 Native 方法 nativeWake(long) 进行唤醒。如果取到值的话,会直接把得到的 Message 对象进行返回。

原来,nativeWake(long) 方法在前面的 MessageQueue#enqueueMessage 方法有个调用,调用时机是在 MessageQueue 入队消息的过程中。

现在已经知道:Handler 发送了 Message,消息用 MessageQueue 进行存储,使用 MessageQueue#enqueueMessage 方法进行入队,使用 MessageQueue#next 方法进行轮训消息。这就不免抛出了一个问题,MessageQueue#next 方法是谁调用的?没错,就是 Looper。

Looper


Looper 在 Android 的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从 MessageQueue 通过 next() 查看是否有新消息,如果有新消息就立刻处理,否则就任由 MessageQueue 阻塞在那里。

我们直接看看 Looper 最重要的方法:loop():

public static void loop() {

final Looper me = myLooper();

if (me == null) {

throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);

}

// …

for (;😉 {

Message msg = queue.next(); // might block

if (msg == null) {

// No message indicates that the message queue is quitting.

return;

}

//…

try {

// 分发消息给 handler 处理

msg.target.dispatchMessage(msg);

dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;

} finally {

// …

}

// …

}

}

方法省去了大量的代码,只保留了核心逻辑。可以看到,首先会通过 myLooper() 方法得到 Looper 对象,如果这个 Looper 返回为空的话,则直接抛出异常。否则进入到一个 for (;? 循环中,调用 MessageQueue#next() 方法进行轮训获取 Message 对象,如果获取的 Message 对象为空,则直接退出 loop() 方法。否则直接通过 msg.target 拿到 Handler 对象,并调用 Handler#dispatchMessage() 方法。

我们先来看看Handler#dispatchMessage() 方法实现:

public void dispatchMessage(Message msg) {

if (msg.callback != null) {

handleCallback(msg);

} else {

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

handleMessage(msg);

}

}

private static void handleCallback(Message message) {

message.callback.run();

}

代码比较简单,如果 Message 设置了 callback 则,直接调用 message.callback.run(),否则判断是否初始化了 `m

再来看看 myLooper() 方法:

public static @Nullable Looper myLooper() {

return sThreadLocal.get();

}

看看 sThreadLocal 是什么:

static final ThreadLocal sThreadLocal = new ThreadLocal();

这个 ThreadLocal 是什么呢?

ThreadLocal


关于 ThreadLocal,我们直接采取 严振杰文章 中的内容。

看到 ThreadLocal 的第一感觉就是该类和线程有关,确实如此,但是要注意它不是线程,否则它就该叫 LocalThread 了。

ThreadLocal 是用来存储指定线程的数据的,当某些数据的作用域是该指定线程并且该数据需要贯穿该线程的所有执行过程时就可以使用 ThreadnLocal 存储数据,当某线程使用 ThreadnLocal 存储数据后,只有该线程可以读取到存储的数据,除此线程之外的其他线程是没办法读取到该数据的。

一些读者看完上面这段话应该还是不理解 ThreadLocal 的作用,我们举个栗子:

ThreadLocal local = new ThreadLocal<>();

// 设置初始值为true.

local.set(true);

Boolean bool = local.get();

Logger.i(“MainThread读取的值为:” + bool);

new Thread() {

@Override

public void run() {

Boolean bool = local.get();

Logger.i(“SubThread读取的值为:” + bool);

// 设置值为false.

local.set(false);

}

}.start():

// 主线程睡1秒,确保上方子线程执行完毕再执行下面的代码。

Thread.sleep(1000);

Boolean newBool = local.get();

Logger.i(“MainThread读取的新值为:” + newBool);

代码没什么好说的吧,打印出来的日志,你会看到是这样的:

MainThread读取的值为:true

SubThread读取的值为:null

MainThread读取的值为:true

第一条 Log 无可置疑,因为设置了值为 true,因为打印结果没什么好说的。对于第二条 Log,根据上方介绍,某线程使用 ThreadLocal 存储的数据,只能被该线程读取,因此第二条 Log 的结果是:null。紧接着在子线程中设置了 ThreadLocal 的值为 false,然后第三条 Log 将被打印,原理同上,子线程中设置了 ThreadLocal 的值并不影响主线程的数据,所以打印是 true。

实验结果证实:就算是同一个 ThreadLocal 对象,任一线程对其的 set() 和 get() 方法的操作都是相互独立互不影响的。

Looper.myLooper()


我们回到 Looper.myLooper():

static final ThreadLocal sThreadLocal = new ThreadLocal();

我们看看是在哪儿对 sThreadLocal 操作的。

public static void prepare() {

prepare(true);

}

private static void prepare(boolean quitAllowed) {

if (sThreadLocal.get() != null) {

throw new RuntimeException(“Only one Looper may be created per thread”);

}

sThreadLocal.set(new Looper(quitAllowed));

}

所以知道了吧,这就是在子线程中使用 Handler 前,必须要调用 Looper.prepare() 的原因。

可能你会疑问,我在主线程使用的时候,没有要求 Looper.prepare() 呀。

原来,我们在 ActivityThread 中,有去显示调用 Looper.prepareMainLooper():

public static void main(String[] args) {

// …

Looper.prepareMainLooper();

// …

if (sMainThreadHandler == null) {

sMainThreadHandler = thread.getHandler();

}

//…

Looper.loop();

// …

}

我们看看 Looper.prepareMainLooper():

public static void prepareMainLooper() {

prepare(false);

synchronized (Looper.class) {

if (sMainLooper != null) {

throw new IllegalStateException(“The main Looper has already been prepared.”);

学习福利

【Android 详细知识点思维脑图(技能树)】

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

详细整理在GitHub点击可见;

Android架构视频+BAT面试专题PDF+学习笔记

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

术停滞不前!**

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-iu7JIqqS-1711020035884)]
[外链图片转存中…(img-JNv7IAde-1711020035884)]
[外链图片转存中…(img-svjxrRMQ-1711020035884)]
[外链图片转存中…(img-wPKZeWVT-1711020035885)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-G2z0wgWX-1711020035885)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值