> 文章列表 > 你对Framework 底层中的 Handler 了解多少?

你对Framework 底层中的 Handler 了解多少?

你对Framework 底层中的 Handler 了解多少?

一个线程有几个Handler?一个线程有几个Looper?如何保证?

Handler的个数与所在线程无关,可以在线程中实例化任意多个Handler。一个线程中只有一个Looper。Looper的构造方法被声明为了private,我们无法通过new关键字来实例化Looper,唯一开放的可以实例化Looper的方法是prepare。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));}

我们知道ThreadLocal是一个线程内部的数据存储类,当某个线程调用prepare方法的时候,会首先通过ThreadLocal检查这个线程是否已经创建了Looper,如果还没创建,则实例化Looper并将实例化后的Looper保存到ThreadLocal中,而如果ThreadLocal中已经保存了Looper,则会抛出一个RuntimeException的异常。那么意味着在一个线程中最多只能调用一次prepare方法,这样就保证了Looper的唯一性。

Handler线程是如何切换的?

(1)假设现在有一个线程A,在A线程中通过Looper.prepare和Looper.loop来开启Looper,并且在A线程中实例化出来一个Handler。Looper.prepare()方法被调用时会为会初始化Looper并为ThreadLocal 设置Looper,此时ThreadLocal中就存储了A线程的Looper。另外MessageQueue也会在Looper中被初始化。

(2)接着当调用Loop.loop方法时,loop方法会通过myLooper得到A线程中的Looper,进而拿到Looper中的MessageQueue,接着开启死循环等待执行MessageQueue中的方法。 (3)此时,再开启一个线程B,并在B线程中通过Handler发送出一个Message,这个Message最终会通过sendMessageAtTime方法调用到MessageQueue的equeueMessage方法将消息插入到队列。

(4)由于Looper的loop是一个死循环,当MessageQueue中被插入消息的时候,loop方法就会取出MessageQueue中的消息,并执行callback。而此时,Looper是A线程的Looper,进而调用的Message或者Handler的Callback都是执行在A线成中的。以此达到了线程的切换。

Handler内存泄漏的原因是什么?如何解决?

通常在使用Handler的时候回通过匿名内部类的方式来实例化Handler,而非静态的匿名内部类默认持有外部类的引用,即匿名内部类Handler持有了外部类。而导致内存泄漏的根本原因是是因为Handler的生命周期与宿主的生命周期不一致。比如说在Activity中实例化了一个非静态的匿名内部类Handler,然后通过Handler发送了一个延迟消息,但是在消息还未执行时结束了Activity,此时由于Handler持有Activity,就会导致Activity无法被GC回收,也就是出现了内存泄漏的问题。解决方式是可以把Handler声明为静态的匿名内部类,但这样一来,在Handler内部就没办法调用到Activity中的非静态方法或变量。那么最终的解决方案可以使用静态内部类 + 弱引用来解决。代码如下:

public class MainActivity extends AppCompatActivity {private MyHandler mMyHandler = new MyHandler(this);@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}private void handleMessage(Message msg) {}static class MyHandler extends Handler {private WeakReference<Activity> mReference;MyHandler(Activity reference) {mReference = new WeakReference<>(reference);}@Overridepublic void handleMessage(Message msg) {MainActivity activity = (MainActivity) mReference.get();if (activity != null) {activity.handleMessage(msg);}}}@Overrideprotected void onDestroy() {mMyHandler.removeCallbacksAndMessages(null);super.onDestroy();}
}

子线程中使用Looper应该注意什么?有什么用?

子线程中开启的Looper,在没有消息时一定要调用Looper的quit或者quitSafely方法。不然子线程会一直处于阻塞状态,无法被回收,进而可能导致内存泄漏的问题。

MessageQueue是如何保证线程安全的?

enqueueMessage方法和next方法中的代码都加了synchronize关键字来保证线程安全。

我们使用Message的时候如何创建它?

这里推荐使用 Message.obtain() 方法来实例化一个 Message,好处在于它会从消息池中取,而避免了重复创建的开销。虽然直接实例化一个 Message 其实并没有多大开销,但是我们知道 Android 是消息驱动的,这也就说明 Message 的使用量是很大的,所以当基数很大时,消息池就显得非常有必要了。

public final class Message implements Parcelable {//消息的标示public int what;//系统自带的两个参数public int arg1;public int arg2;//处理消息的相对时间long when;Bundle data;Handler target;Runnable callback;Message next;	//消息池是以链表结构存储 Messageprivate static Message sPool;	//消息池中的头节点//公有的构造方法,所以我们可以通过 new Message() 实例化一个消息了public Message() {}//推荐以这种方式实例化一个 Message,public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {//取出头节点返回Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}//回收消息public void recycle() {if (isInUse()) {if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}return;}recycleUnchecked();}void recycleUnchecked() {//清空消息的所有标志信息flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = -1;when = 0;target = null;callback = null;data = null;synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {//链表头插法next = sPool;sPool = this;sPoolSize++;}}}
}

Message中维护了一个sPools的消息池,obtain方法会从消息池中取出一个消息,避免了实例化。而在Message使用结束后会通过recycle或者recycleUnchedked来回收Message。

Looper死循环为什么不会导致应用卡死?

如果按照 Message.next 方法的注释来解释的话,如果返回的 Message 为空,就说明消息队列已经退出了,这种情况下只能说明应用已经退出了。这也正符合我们开头所说的,Android 本身是消息驱动,所以没有消息几乎是不可能的事;如果按照源码分析,Message.next() 方法可能会阻塞是因为如果消息需要延迟处理(sendMessageDelayed等),那就需要阻塞等待时间到了才会把消息取出然后分发出去。然后这个 ANR 完全是两个概念,ANR 本质上是因为消息未得到及时处理而导致的。同时,从另外一方面来说,对于 CPU 来说,线程无非就是一段可执行的代码,执行完之后就结束了。而对于 Android 主线程来说,不可能运行一段时间之后就自己退出了,那就需要使用死循环,保证应用不会退出。这样一想,其实这样的安排还是很有道理的。

能不能让一个Message被加急处理?

系统内部可以通过开启同步屏障,并发送异步消息来优先处理异步消息。但是由于开启同步屏障的方法postSyncBarrier被隐藏,外部无法调用,并且发送异步消息的方法也是被hide的,所以在APP开发中实际是没有办法去发送一个加急消息的。

Handler的同步屏障是什么?

在 Handler 中还存在了一种特殊的消息,它的 target 为 null,并不会被消费,仅仅是作为一个标识处于 MessageQueue 中。它就是 SyncBarrier (同步屏障)这种特殊的消息。当读取到这个同步屏障后就会开启一个循环取查找msg.isAsynchronous()为true的消息。如果找到则终止循环,优先执行msg.isAsynchronous()为true的这个Message。这个逻辑是在MessageQueue的next方法中实现的,代码如下

// MessageQueue
Message next() {// ...省略无关代码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 && msg.target == null) { // 读取到msg.target为null,即这里被添加了一个同步屏障// Stalled by a barrier.  Find the next asynchronous message in the queue.do { // 开启循环查找MessageprevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous()); // 找到isAsynchronous为true的消息,则终止循环,执行后边的代码来处理这个Message}// ... 省略无关代码}}

同步屏障的原理其实就这么简单,那么这个同步屏障有什么用呢?其实同步屏障仅仅是在系统内部使用,我们无法通过公开的API来开启一个同步屏障。具体的使用案例来看View的绘制流程,在ViewRootImp类中有如下代码:

    void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;// 调用MessageQueue的postSyncBarrier开启同步屏障mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();// 此处会发送一个synchronus为true的MessagemChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}

接着来看MessageQueue中的postSyncBarrier方法:

	/ @hide*/public int postSyncBarrier() {return postSyncBarrier(SystemClock.uptimeMillis());}private int postSyncBarrier(long when) {// Enqueue a new sync barrier token.// We don't need to wake the queue because the purpose of a barrier is to stall it.synchronized (this) {final int token = mNextBarrierToken++;final Message msg = Message.obtain();msg.markInUse();msg.when = when;msg.arg1 = token;Message prev = null;Message p = mMessages;if (when != 0) {while (p != null && p.when <= when) {prev = p;p = p.next;}}if (prev != null) { // invariant: p == prev.nextmsg.next = p;prev.next = msg;} else {msg.next = p;mMessages = msg;}return token;}}

可以看到postSyncBarrier使用了@hide注解隐藏了API,意味着这个方法外部是无法使用的。而在postSyncBarrier(long when)方法中为MessageQueue插入了一个Message,而这个Message并没有给target赋值。至此可以联系到next方法中对target为null情况的处理。

接下来看下mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)这个方法的源码

    public void postCallbackDelayed(int callbackType,Runnable action, Object token, long delayMillis) {if (action == null) {throw new IllegalArgumentException("action must not be null");}if (callbackType < 0 || callbackType > CALLBACK_LAST) {throw new IllegalArgumentException("callbackType is invalid");}postCallbackDelayedInternal(callbackType, action, token, delayMillis);}private void postCallbackDelayedInternal(int callbackType,Object action, Object token, long delayMillis) {if (DEBUG_FRAMES) {Log.d(TAG, "PostCallback: type=" + callbackType+ ", action=" + action + ", token=" + token+ ", delayMillis=" + delayMillis);}synchronized (mLock) {final long now = SystemClock.uptimeMillis();final long dueTime = now + delayMillis;mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);if (dueTime <= now) {scheduleFrameLocked(now);} else {Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);msg.arg1 = callbackType;// 设置异步消息msg.setAsynchronous(true);// 发送异步消息到MessageQueuemHandler.sendMessageAtTime(msg, dueTime);}}}

可以看到在postCallbackDelayed方法中最终发送了一个异步消息,因为有了前边的同步屏障,那么这个异步消息就会优先执行。可见这里通过同步屏障和异步消息来保证了View的绘制会优先执行,避免了消息过多的情况下出现掉帧的情况。

Handler的阻塞唤醒机制是什么?

Handler 中其实还存在着一种阻塞唤醒机制,我们都知道不断地进行循环是非常消耗资源的,有时我们 MessageQueue 中的消息都不是当下就需要执行的,而是要过一段时间,此时如果 Looper 仍然不断进行循环肯定是一种对于资源的浪费。

因此 Handler 设计了这样一种阻塞唤醒机制使得在当下没有需要执行的消息时,就将 Looper 的 loop 过程阻塞,直到下一个任务的执行时间到达或者一些特殊情况下再将其唤醒,从而避免了上述的资源浪费。

这个阻塞唤醒机制是基于 Linux 的 I/O 多路复用机制 epoll 实现的,它可以同时监控多个文件描述符,当某个文件描述符就绪时,会通知对应程序进行读/写操作。

Handler的阻塞机制的实现是在MessageQueue的next方法中,通过调用nativePollOnce的一个native方法实现的。

遇到以下情况,Java 层会调用 natvieWake 方法进行唤醒。

MessageQueue 类中调用 nativeWake 方法主要有下列几个时机:

调用 MessageQueue 的 quit 方法进行退出时,会进行唤醒

消息入队时,若插入的消息在链表最前端(最早将执行)或者有同步屏障时插入的是最前端的异步消息(最早被执行的异步消息)

移除同步屏障时,若消息列表为空或者同步屏障后面不是异步消息时

可以发现,主要是在可能不再需要阻塞的情况下进行唤醒。(比如加入了一个更早的任务,那继续阻塞显然会影响这个任务的执行)


相信很多小伙伴在面试的过程中或工作中,经常会被问到Framework的问题,这类问题通常会经过面试官的层层深挖,直到挑战到你的技术边界。而大多数开发者更多的还是在做业务开发,对于Framework基本停留在“不够了解”的阶段,其中不乏一些工作多年的 Android 工程师。

如果想要精进,不仅要对底层原理充分了解,还要知道如何利用Framework知识指导我们代码实践开发,像Android App 的启动机制、AMS、PMS、WMS、Handler、Binder等…

所以为了帮助能够更深入层次的了解 Framework 底层原理,这准备了《Android Framework核心笔记》及对应的一些面试题,供大家参考学习!!!

《Android Framework学习手册》:https://qr18.cn/AQpN4J

1.Handler 机制实现原理部分:
2.Binder 原理
3.Zygote
4.AMS源码分析
6.深入PMS源码
7.WMS
8.……

你对Framework 底层中的 Handler 了解多少?