智能座舱进阶-应用框架层-Handler分析
首先明确, handler是为了解决单进程内的线程之间的通信问题的。我也需要理解Android系统中进程和线程的概念, APP启动后,会有三四个线程启动起来,其中,有一条mainUITread的线程,专门用来处理UI事件,以及显示组件的生命周期,多个Activity的线程都是同一个。此外还要知晓,单进程内的多线程是共享内存、信号量、运行指令。如下图:
除了私有栈区,不能相互共享, 代码的方法指令、数据都可以在进程内任意线程执行。这里大家先记住这点, 在后面的问题3的解释中会用到。
enqueueMessage中的消息的存入,是可以设置时间先后的;
我们本篇幅文章只讨论三个问题:
1. Handler 的消息发送过程;
2. Message 被消耗的过程;
3. 为何需要Handler这个类的原因;
基础知识
跟handler相关的类:
Handler:发送消息的快递员(属于某个快递公司的职员)
Message:承担消息的包裹(可以放置很多东西的箱子)
MessageQueue:消息中心(这里会按照消息带的时间长短,一个排列好消息的单链表)
Looper: 真正在消费消息的永动机(这个是在主线程的,上面三个都在另一个线程里)
问题1#:Handler 的消息产生和发送过程
我们在Activity中的应用场景:
创建Handler的对象,里面也重写了真正去使用msg的回调方法:
//创建 Handler对象,并关联主线程消息队列
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
···略···
}
}
};
发送消息:
Message msg = new Message();
msg.setData(bundle);
msg.what = 2;
mHandler.sendMessage(msg)
消息的产生, 可以在任何地方通过Message 去创建,它是一个比较简单的实体类, 包含1、what,when,target等。 这里面说一下:
2、what:就是携带的数据,Int类型;
3、when:这个一般不怎么用, 但是是可以使用,具体是输入时间长短后,当在msg发送到MessageQueue的队列里, 会根据when和当前时间来推送,需要去消费它的具体时间, 跟队列里的消息进行对比,然后排列插进去;
4、target: 这个一般我们没有赋值,是因为他在Messag的实例化对象的时候【obtain()方法】或者是在message的后续传递中底层会帮我们赋值, 这个在最后looper里消费的时候, 会使用,用它找到这个消息是用哪个handler来消费,调用他的handleMessage()方法。 这里也就说明了, 一个looper是支持绑定多个hander对象的,同时也不会混乱。
我们现在看一下handler的实例化过程:
public Handler(Callback callback, boolean async) {
//......
mLooper = Looper.myLooper(); //返回与当前线程关联的Looper对象,在后面Looper会讲到
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //返回Looper对象的消息队列,在后面MessageQueue会讲到
mCallback = callback; //接口回调
mAsynchronous = async; //是否异步
}
public interface Callback {
public boolean handleMessage(Message msg); //这个函数大家都很熟悉了,暂不细说,总之都知道是用来回调消息的
}
整个构造方法的过程中会确立以下几件事:
• 获取当前Handler实例所在线程的Looper对象:mLooper = Looper.myLooper(),这个looper获取的就是主线程那个loop
• 如果Looper不为空,则获取Looper的消息队列,赋值给Handler的成员变量mQueue:mQueue = mLooper.mQueue
• 可以设置Callback 来处理消息回调:mCallback = callback。
looper这里面为什么没有创建直接获取呢, 因为我们的Activity在每次创建的时候,
ActivityThread的1个静态的main();
而main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象,同时也会生成其对应的MessageQueue对象。为进程中的管理进行初始化。
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “ActivityThreadMain”);
// Install selective syscall interception
AndroidOs.install();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
// Call per-process mainline module initialization.
initializeMainlineModules();
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper(); // 在这里创建新的looper
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();// 获取Handler
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();// 开始调用loop()方法
好了,言归正传, handler创建完成调用
mHandler.sendMessage(msg)方法进行发送:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue; //获得当前的消息队列
if (queue == null) { //若是在创建Handler时没有指定Looper,就不会有对应的消息队列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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //这个target就是前面我们说到过的
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
消息通过这个方法 queue.enqueueMessage(msg, uptimeMillis)进入到了queue队列里了。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) { //判断msg的所属Handler
throw new IllegalArgumentException("Message must have a target.");
}
//......
synchronized (this) { //因为是队列,有先后之分,所以用了同步机制
//......
msg.when = when;
Message p = mMessages; //对列中排在最后的那个Message
//......
if (p == null || when == 0 || when < p.when) {
//若队列为空,或者等待时间为0,或者比前面那位的等待时间要短,就插队
msg.next = p; //此处的next就是前面我们在Message提到的,指向队列的下一个结点
mMessages = msg;
//......
} else {
//......
Message prev;
for (;;) {
//此处for循环是为了取出一个空的或者when比当前Message长的一个消息,然后进行插入
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
//......
}
msg.next = p; // 置换插入
prev.next = msg; // 置换插入
}
//......
}
return true;
}
这个就消息新建产生,然后发送到了queue的消息队列里,对待looper去取然后分发消费了。
问题2#:Message的消费过程
这个我们要看一下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.");
}
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {// 无限循环
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {// 处理消息
msg.target.dispatchMessage(msg);
......
}
前面有代码片里说到 ActivityThread.mian-> Looper.prepareMainLooper()->mLooper.loop():
我们只要看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.");
}
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {// 无限循环
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {// 处理消息
msg.target.dispatchMessage(msg);
......
这里面就看到,两件事情,根据target来判断是哪个handler,同时调用它的dispatchMessage来分发消息:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
最终调用了 我们附带了的callback的handleMessage()方法。
这里面还有一个疑惑, looper是怎么能够一直处理消息的。 大家看loop()方法中, 有一个for(;;)死循环,这个其实就是永远在轮询队列中的消息,for循环是可以被阻塞的, 在这里就是这个函数Message msg = queue.next(); 就是如果队列没有消息了,它就会阻塞, 类似thread.sleep(1000)的方法。 当有新消息, queue.next()就会正常,继续执行消息。
问题3#: 为什么需要handler
需要handler,是因为在Android的设计里,不允许除了UI主线程外的其他线程对UI进行操作,最终都会走到View.requestLayout()里面判断当前线程是否是主线。
所以我们就需要想办法,需要子线程执行的某个阶段,有需求更新UI,那我们就必须通知在主线程里面去执行刷新UI的代码,这个在很多地方都有这种设计, 设计一个轮转片的循环,线程内的通信,通过消息来通知。 比如JS这种单线程设计语言, 就有Emit的设计。
1.创建了一个Looper对象保存在ThreadLocal中。这个Looper同时持有一个MessageQueue对象。
2.创建Handler获取到Looper对象和MessageQueue对象。在调用sendMessage方法的时候在不同的线程(子线程)中把消息插入MessageQueue队列。
3.在主线程中(UI线程),调用Looper的loop()方法无限循环查询MessageQueue队列是否有消息保存了。有消息就取出来调用dispatchMessage()方法处理。这个方法最终调用了我们自己重写了消息处理方法handleMessage(msg);这样就完成消息从子线程到主线程的无声切换。
最后补充:我们经常使用Handler没有创建Looper调用Looper.pepare()和Looper.loop()是因为在ActivityThread中已经创建了主线程的Looper对象,保存在了ThreadLocal中,我们在创建Hander的时候就会从ThreadLocal中取出来这个Looper。