Handler到底是一个什么东东
作为一个Android开发工程师,Handler简直是必须要了解的东西。每次面试前,Handler都会悄悄地钻到耳边对我说:“嘿,哥们,老地方见!”
果然,面试又问到了,而Handler又跑过来BB:“又被我难倒了吧!”
(内心独白,老子就不信搞不定你!)
于是便有了这篇解析。
先来几个问题
- 你了解Android的Handler机制吗?(我美不呲呵的说,那必须了解,一顿白话之后就有了下面的问题)
- 你知道Handler在安卓framework中有哪些应用吗?
- 你知道Handler中的同步屏障吗?
- 为什么在通过
view.post() 获取到的view宽高是准确的?post的消息不会出现在测量流程之后吗? - 巴拉巴拉巴拉巴拉
行吧,在听到第一个问题的时候的信心满满到最后一个问题的灰心落寞,直接被打击的透透的。
为什么我要了解那么多,会写不就行了?
确实啊,我会用handler不就行了?作为一个画UI的,我有必要了解那么多吗?
但是我的理解是这样的,对底层了解的越透彻,写出bug的可能性就越低,因为老子知道怎么回事儿了呀。还有就是能了解能力边界,知道Handler能做什么不能做什么,这为开发其他技术方案有很大帮助。
话不多说,接下来,就要把「Handler」这王八犊子好好地扒一扒。
目录
- Android中的消息处理系统
- Handler是如何转起来的
- Handler在Android系统中的身影
- Handler的能力边界(这王八犊子都能用来做什么)
Android中的消息处理系统
在很多桌面操作系统中,都有自己的 消息处理框架,比如Windows、Android。
那么最基本的,消息处理框架,至少得有消息发送方(handler)、消息接收方(handler)、消息本身(Message)。
当消息生产速度非常快时,还需要一个存储方对消息进行暂时缓存(MessageQueue)。
而消息不是直达目标本身时,需要中间的一个调度中心(Looper),分别处理消息,方便统一调度。
这里举一个现实的例子:送快递
- 发快递方把物品打包,把包裹交给快递公司。
- 快递公司将很多人要发送的包裹统一装车运送到目标城市。
- 快递到达时,再将包裹分别运送给对应的收件方
当然有人说了,我就要用闪送,一次只送我的!那我只能说,你牛笔。
上面的案例中:
- handler就相当于一个快递员。负责收、发快递。
- Message就是一个快递。当然了快递也分(专送快递、普通快递、空包)
- MessageQueue就是一个存储快递的仓库
- Looper就是用来把每个快递分发给对应的快递员的,可以当做快递公司。
- 发快递方就是线程A
- 收件方就是线程B
当然了handler不仅局限于线程间通信。
Handler是如何转起来的
tips:基于API 30
接下来上一幅简单的小图来展示Handler是如何转起来的。
虽然这个图不是非常标准,但是它确实也描述了Handler的一整个工作机制。
那么我们就知道了,实际上除了Handler之外还有其他的几个角色,这里也让他们一并亮相吧。
- Looper:用于开启循环,处理消息,相当于大总管。
- MessageQueue:存储消息(实际上主要是操作Message链表),读取消息
- Message:消息的封装,可以跨进程通信,本质是单向链表结构,并且会持有Handler(目标handler,其实就是发消息的Handler本身)。
- Handler:用于发消息和处理消息的快递员。
那么他们真正的工作流程是什么样的呢?
如何在子线程中使用Handler
thread {
Looper.prepare()
handler2 = Handler()
Looper.loop()
}.start()
总结下来的步骤就是:
- 创建Looper,Looper.prepare()。
- 创建Handler,用于发消息和接收消息。
- 进入循环,Looper.loop()。
接下来挨个步骤开始,深入源码,进行分析。
创建Looper
想要发送消息,先得有一个Looper才行。那么为什么一定要Looper.prepare() ;
那我们便深入源码看看。
发现Looper只有一个私有的构造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
奥,原来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));
}
由prepare方法发现,原来Looper提供了这个prepare方法,方便开发者创建Looper对象,那么它为什么要自己创建,而且只允许一个线程中创建一个呢?
Looper是死循环获取消息的,所以创建多个没有意义,白白浪费空间罢了。而这里我们看到了传递进来的quitAllowed 默认也都是true,而可传递参数的prepare方法又是私有的,没法直接调用到。
内部又使用了ThreadLocal对象存储了Looper对象,可以方便线程中其他方法直接通过Looper.myLooper() 获取对象。
创建Handler
Handler不像Looper,它主要通过获取当前线程中的Looper或者指定的Looper,来进行消息发送。
所以创建Handler时,可以指定Looper,也可以不指定(不指定时,当前线程汇总必须得有Looper对象),
在调用了Handler的构造方法(非主动传递Looper对象)之后,最终会调用到如下 构造方法:
public Handler(@Nullable Callback callback, boolean async) {
.......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
这里我们发现Handler中的构造方法中会存在一个async参数,当这个参数为true 时,handler会在调用发送消息的方法时,最终会将Message设置为异步消息。
if (mAsynchronous) {
msg.setAsynchronous(true);
}
然而,作为App开发者,我们是没有直接的接口可以设置这个参数的,那么我们也可以通过Message直接手动设置异步消息。
Message().apply {
isAsynchronous = true
}
进入循环状态
当调用了Looper.loop() ;整个消息处理处理算是打通了。
等等什么就打通了,你说的那个什么MessageQueue呢?那玩意不也需要创建么?
MessageQueue其实会在Looper创建时已经被创建了,作为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.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
me.mInLoop = true;
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next();
if (msg == null) {
return;
}
final Observer observer = sObserver;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
msg.recycleUnchecked();
}
}
由以上代码可知,Looper.loop() 最本质的行为,就是不断通过queue.next(); 从queue中获取Message并且处理。
那么queue.next的这个方法到底有什么特别的呢?
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
……
}
}
针对上面的代码,虽然我们都能看得懂,但是还是会有这样那样的疑问,接下来我们一起来分析一下这几个问题。
① 进入死循环,咦这里为什么是死循环?
我们知道在Looper中,已经出现一个死循环了,难道通过那个死循环还不够吗?按照我的想法,应该直接在那里直接进行死循环就行了呀,如果存在Message就执行,不存在就直接退出就行了。
首先直接退出肯定不行,万一后面还有消息要来呢,所以此处肯定不能直接退出。那么为什么再放到MessageQueue中获取呢?
MessageQueue只是获取下一个Message,并将Message返回,Looper负责处理。这样有利于责任划分,符合单一职责划分。
而当MessageQueue中取不到数据时,需要再次获取,所以要进入死循环,直到获取到那个Message。
更主要的问题是,主线程不能退出,所以通过死循环的方式可以让主线程一直处于运行状态。
② 这里就是进入等待状态,如果是进入等待状态为什么不用wait()?而用native的epoll方法?
首先wait,是监视器用来控制线程状态的,是控制多线程访问共享资源时的方法,并不能让当前线程进入等待状态。
使用native.epoll方法可以让线程进入等待状态。
③ 如果Message的target == null,则进入循环,怎么target可以为null吗?不是通过Handler发送消息的时候默认设置的吗?
首先来说,target==null 的消息有什么特别吗?当target==null 时,该消息为同步屏障消息。
在google提供的API中,我们确实不能直接发送target为空的消息。
当同步屏障消息出现时,mq会读取当前屏障消息后面的异步消息,优先处理异步消息,直到同步屏障被移除。
⑥ 对呀还要时间判断,那我其他设置了延时的消息怎么办?
为什么直接到6了?你猜。
延时的消息是在哪里设置的?如果超过了我设置的延时时间怎么办?
其实是在handler发送消息,消息进入队列时,就已经根据message的执行时间点排序好了。
还有一个问题,怎么保证消息的延迟时间是正确的?如果我修改了系统时间呢?
当Message设置when属性时,是通过SystemClock.updateMills() 来获取的,获取的是系统开启到现在的执行时间,所以不会跟随系统时间改变而改变,所以时间就是准确的了。
Handler在Android系统中的身影
如果读过framework源码的都知道,在Android中有一个非常重要的类,叫 ActivityThead ,在它里面有这样一个H类。它就是handler的子类里面记录了包含了很多系统消息的处理。
class H extends Handler {
……
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CREATE_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
("serviceCreate: " + String.valueOf(msg.obj)));
}
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case BIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
handleBindService((BindServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case UNBIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
handleUnbindService((BindServiceData)msg.obj);
schedulePurgeIdler();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
……
}
……
}
……
}
还有当我们在调用View.post方法时,最终也会调用到Handler的Post方法,那么这个Handler是哪里来的呢? 在这个Handler是ViewRootImpl中传递过来的。而在ViewRootImpl中也有一个自定义的Handler,这个Handler主要用于做什么,大家可以自行探讨。我们发现这个Handler创建的时候,没有调用Looper.prepare()这是为什么呢?因为它拿到的是主线程的Looper啦。
final class ViewRootHandler extends Handler {
……
}
handler的能力边界
首先来说它只是一个跨线程、进程通信的工具。那么它当然能用来跨线程、跨进程通信了。
那么通过它在Android中身份,和各种Handler所执行的行为,还能做些什么呢?
比如某大神通过Handler机制开发出了BlockCanary这种工具,来检测代码执行效率。
有些框架通过获取主线程Looper实现了主、子线程切换。
还有人实现了在子线程弹Toast。
那它的能力边界到底在哪呢?只能说,我也不知道~~~
其他小Tips
同步屏障
同步屏障这个问题已经被问到过很多次了。
在Handler中存在发送移除同步屏障方法。postSyncBarrier()、removeSyncBarrier()
在添加同步屏障之后,handler在碰到第一个屏障消息(target==null的那个消息)之后,会优先读取屏障消息后面的异步消息进行优先执行。
那么再Android中哪里有用到呢,毕竟这个方法我们不能直接通过api调用。
在 View 更新时,draw、requestLayout、invalidate 等很多地方都调用了ViewRootImpl#scheduleTraversals()
由于 UI 更新相关的消息是优先级最高的。
Handler怎么做到准确的计时的?
通过SystemClock来获取的时间,该时间为手机启动开始到现在的运行时间,不会随着系统时间的改变而改变。
View.post为什么能获取到准确宽高,如果它post的事件在绘制之前呢?
因为同步屏障会优先执行绘制代码。
好了可以了,今天先BB这么多,等再多看看源码,多总结总结~~
|