在Android开发中,无论是刚入门的新人还是工作多年的老司机,都经常用到Handler,用的最多的场景就是用子线程来做耗时请求(如:网络、数据库等),然后切换到主线程来处理请求结果。没错,Handler的主要作用就是实现线程间通信的。
Handler的一些基础用法这里就不赘述了。接下来我们一起来探究下Handler是如何实现线程间通信的。
1.Handler中几个重要的类。
首先我们回顾下项目中我们是如何使用Handler的:
Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); } }; Message msg = handler.obtainMessage(); Message.obtain(handler, new Runnable() { @Override public void run() { } }); msg.what = 1; msg.obj = "xxx"; handler.sendMessage(handler.obtainMessage());
以上代码中我们得出Handler消息机制中几个重要的类:
(1)Message:需要传递的消息,看源码我们会发现Message中可以携带参数int what
、Object obj
、int arg1
、int arg2
、Bundle data
、Runnable callback
。
(2)Looper:Looper 是一个消息循环器,它可以帮助 Handler 在一个线程中处理消息。每个线程只能有一个 Looper,Looper 负责创建该线程的消息队列并开启消息循环。当 Looper 的 loop() 方法被调用时,它会一直循环检查消息队列中是否有需要处理的消息,如果有,就将消息发送给对应的 Handler 进行处理。当消息队列中没有消息时,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."); } ...... for (;;) { if (!loopOnce(me, ident, thresholdOverride)) { return; } } } /** * Poll and deliver single message, return true if the outer loop should continue. */ private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) { Message msg = me.mQueue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return false; } ...... try { msg.target.dispatchMessage(msg); ...... } ...... return true; }
(3)Handler:可以创建消息并将其投递到消息队列中。每个Handler都关联着一个消息队列,当有消息投递到消息队列时,Looper.loop就会取出消息并交给Handler来处理。同时每个Handler也会关联着一个线程,这个线程通常是Handler创建时所在的线程。
看下Handler中的几个方法的调用关系:
public final boolean sendMessage(@NonNull Message msg) { return sendMessageDelayed(msg, 0); } 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); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
当我们调用handler.sendMessage最终会调用MessageQueue的enqueueMessage方法。那接下来我们再看另外一个重要的类MessageQueue。
(4)MessageQueue:消息队列是一个先进先出的队列,用来存储需要处理的消息。Handler 的主要作用就是将消息投递到消息队列中,等待被处理。通过消息队列,我们可以在多个线程之间传递消息,从而实现线程间通信的功能。
看下MessageQueue中的enqueueMessage方法:
public final class MessageQueue { @UnsupportedAppUsage Message mMessages; ...... boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } synchronized (this) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } 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; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. 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; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; } ...... }
先看msg.next = p
,Message有个next属性,它指向的也是Message对象,由此我们可以得出这里队列并不是使用List来记录的,而是采用的单链表的方式来记录的。
我们知道java中存储数据是放在堆中的,如果用List来记录数据需要在堆中开辟连续的空间,而内存空间是有限的,如果List越来越大,会导致内存中很难开辟那么多连续的空间从而会导致OOM,而采用单链表的好处就是可以利用内存中不连续的空间。
接下来我就详细分析下enqueueMessage方法的实现。
假设每次enqueue都进入29行的if里面。
那么第一次enqueue后链表结构如下:

假设第二次enqueue后进入的是34行的else里面,默认情况下下一个Message的when肯定比上一个的要大,因为看上面Handler中的源码,when参数的值是SystemClock.uptimeMillis() + delayMillis
。所以我们暂时不考虑when < p.when
的情况。那么执行完后的链表结构如下:

第三次enqueueMessage后的链表结构如下:

我们会发现每次发送消息,都是将新的消息放到链表的尾部,然后全局的mMessage对象指向链表的头部,这样取消息的时候就可以从头部开始一步步的取下去,关于怎么取消息,具体可以看下MessageQueue中的next方法,比较简单,这里就不赘述了。
上面流程有不懂的地方其实可以在纸上画画就明白了,刚开始prev指向链表头,p指向表头的下一个位置,然后比较when,因为我们刚开始就假设了一个前提,新的Message的when只会比表尾的Message的when大,所以每次都会插入到表尾的位置。
理解了上面的流程,我们再回过头来看看when,接着上面的图来看,假设有个Message4要插入进来,而它的when比Message2大,比Message3要小,那该如何执行呢?
首先会进入else中的for循环,然后当prev指向Message2,p指向Message3的时候,判断这句话:
if (p == null || when < p.when) {
break;
}
会发现满足when<p.when,退出循环,然后再执行这句话:
msg.next = p; prev.next = msg;
这样Message4就插入到了Message2和Message3之间了,如下图:

最后我们再整体回顾下Handler、Looper、Message、MessageQueue是如何协调工作的,下面是我画的流程图,为了加深记忆,我举了个不是特别恰当的例子,大家不要对号入座,仅供学习娱乐:

Handler就是辛勤工作赚钱养家的老王,它有一个吃饭的本领sendMessage,挣到钱(Message)后自己也舍不得花,就往家里的保险柜MessageQueue里面存,保险柜还比较高级,只有老王会操作,需要使用专有的指纹enqueueMessage才能打开往里面存,而老王有个败家娘们Looper它有个技能loop,就是花钱,但是保险柜没指纹打不开,它就让老王给它也录个专有指纹next,这样它就可以愉快的花钱(Message)了。
2.Looper死循环为什么不会导致应用卡死?
我们看ActivityThread源码会发现,main方法有下面两段话:
public static void main(String[] args) { ...... Looper.prepareMainLooper(); ...... Looper.loop(); }
我们在子线程经常这样做:
new Thread(new Runnable() { @Override public void run() { while (!quit) { } Log.d("TAG", "如果quit为false这里执行不到"); } }).start();
只要quit不为true,while循环会一直执行下去,那while循环体外面的语句都执行不到。其实我们每个应用程序的主线程就是一个死循环,那为什么在主线程开启一个死循环程序不会卡死呢?
要回答这个问题,我们得先了解在 Android 应用程序中,主线程通过回调机制来接收各种事件,这些事件包括用户的输入事件(比如点击、触摸、滑动等)、系统事件(比如屏幕旋转、电量低等)、应用程序事件(比如 Activity 生命周期事件、广播接收器事件等)等。当发生某个事件时,系统会回调主线程中对应的方法,比如 onClick()、onTouchEvent()、onCreate() 等,主线程就会根据事件类型进行相应的操作。
与消息循环不同的是,主线程的事件回调是由系统控制的,它不会一直占用线程,而是会根据事件的发生情况进行回调。例如,在应用程序启动时,系统会回调主线程中的 onCreate() 方法来进行初始化操作;当用户点击某个按钮时,系统会回调主线程中的 onClick() 方法来响应用户的操作;当系统检测到电量低时,系统会回调主线程中的 onLowBattery() 方法来通知应用程序等等。
那有人会问,既然Android中的事件是由回调的方式来处理的,那要Looper.loop是干什么呢?答案是:为了处理UI绘制。我们看看下面这篇文章,了解下UI绘制的整个流程,讲的有点复杂,我们重点关注下面一句话:
https://www.jianshu.com/p/5bd5f6d77add
onVsync()
是底层会回调的,可以理解成每隔 16.6ms 一个帧信号来的时候,底层就会回调这个方法,当然前提是我们得先注册,这样底层才能找到我们 app 并回调。当这个方法被回调时,内部发起了一个 Message,注意看代码对这个 Message 设置了 callback 为 this,Handler 在处理消息时会先查看 Message 是否有 callback,有则优先交由 Message 的 callback 处理消息,没有的话再去看看Handler 有没有 callback,如果也没有才会交由 handleMessage() 这个方法执行。
也就是GPU绘制的时候通过vsync信号告诉上层应用,回调到onVsync()方法,然后通过Handler发送消息最终通过doFrame方法来绘制UI。
就分析到这里吧,其实日常开发中我们会用就行,源码分析是让我们知其然知其所以然,让我们看透本质罢了,至于作用吧,嗯。。。,我感觉就是看透本质,我只能(会)这么解释,哈哈哈。。。。。。
原创不易,转载请注明出处。