在安卓中,如果想要在多线程间进行通讯,那么最常用到的方法就是 Handler 了,用法也很简单,创建 Handler ,然后进行 Message 的发送。那么其具体是如何实现的呢?我们一点点来进行一次大揭秘
四大组件
Handler 的消息机制主要是靠4个组件来进行完成的:Handler、Message 、MessageQueue 、Looper
很早之前看过一篇文章,对于Handler的消息机制有一个特别形象的比喻,就是送信的机制(一不小心暴漏了年龄)。
| 组件 | 邮件的发送 | 作用 |
| — | — | — |
| Handler | 收件人 | 发送消息,处理消息 |
| Message | 信件 | 传递的消息 |
| MessageQueue | 信箱 | 消息队列 |
| Looper | 邮差 | 消息循环 |
感觉很恰当的一个比喻。邮差(Looper)会不断的去查看信箱(MessageQueue)是否有邮件(Message),如果有的话,邮差就会送信,交给具体的收件人(Handler)处理。
使用方式
val handler=object: Handler(){
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
}
}
//发送消息,可以在子线程
handler.sendEmptyMessage(1)
可能有人说可能会导致内存泄漏,这里暂时不考虑,小伙伴既然知道有内存泄漏,那就肯定知道标准代码应该怎么撸。
揭秘
那么是如何通过简单的方式就实现了跨线程的数据通信了呢?
Handler大揭秘
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
//获取线程对应的Looper,是线程安全的,通过ThreadLocal来实现。
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
“Can’t create handler inside thread that has not called Looper.prepare()”);
}
//设置Handler对应的消息队列
mQueue = mLooper.mQueue;
//设置回调
mCallback = callback;
mAsynchronous = async;
}
在获取 Looper 时,使用的是 myLooper() 来获取的对象,
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
所以每个线程所对应的Looper对象都是不同的,那么通过什么方式设置的呢?答案是 prepare() 方法。
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() 方法,原因就在这儿。那么很多人问了,为什么主线程创建Handler对象不用管啊?因为系统已经帮你做了啊~~~我可是有证据的
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException(“The main Looper has already been prepared.”);
}
sMainLooper = myLooper();
}
}
prepareMainLooper() 这个方法是在ActivityThread的main函数中调用的。所以在主线程中使用Handler的时候不需要我们做什么特殊的处理。
现在 Handler 对象创建完了,那么下一步就是进行消息的创建和发送了
消息的发送
进行消息的发送,有很多种方式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m465lccR-1617011293249)(https://upload-images.jianshu.io/upload_images/25094154-89be0c9f66456d97.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
我们按照我们最开始的测试代码来进行跟踪
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
//检测队列不能为空
RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
Log.w(“Looper”, e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
发现了么,其实不管调用那个方法,其实最后都会进入到 sendMessageAtTime 这个函数里面。并且通过 enqueueMessage 将消息放到对应的消息队列中。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
也是个入队的操作,只是对消息进行了一些相关的设置,设置了对应的target,和同步参数。最重要的是 enqueueMessage 操作
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {//这个target是对应的Handler,总得需要知道消息发给谁吧?
throw new IllegalArgumentException(“Message must have a target.”);
}
if (msg.isInUse()) {//如果消息已经被用过了,不能再重复消费了
throw new IllegalStateException(msg + " This message is already in use.");
}
//线程安全,同步
synchronized (this) {
if (mQuitting) {//如果队列已经关闭,则直接将消息回收处理掉
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
//标记消息已经被使用了
msg.markInUse();
msg.when = when;
//标记当前Handler要发送的消息
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//如果当前队列没有要处理的消息,或者新入队的消息需要立即处理或者如对消息的发送时间比当前要处理的小时发送时间早
//那么将消息放入到队列头,并唤醒消息
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//判断消息队列里有消息,则根据 消息(Message)创建的时间 插入到队列中
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;😉 {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
//如果需要唤醒队列对消息的处理,通过nativeWake可以唤醒 nativePollOnce (这个会在queue.next()中调用,使对于消息的处理进行休眠操作)的沉睡
nativeWake(mPtr);
}
}
最后
给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
//如果需要唤醒队列对消息的处理,通过nativeWake可以唤醒 nativePollOnce (这个会在queue.next()中调用,使对于消息的处理进行休眠操作)的沉睡
nativeWake(mPtr);
}
}
最后
给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;
[外链图片转存中…(img-pyZHTXoi-1714903428207)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!