本文重点:
HandlerThread
、ActivityThread
为例,讲解Handler
的用法;Handler
中的重要角色以及运行过程;Handler
使用不当的坑。代码以 Android API 28 (Android 9)为准,如有不当,希望指正😀
Handler
的作用就是跨线程执行任务,可以在A线程中通知B线程去执行相应的任务。我们从Android
的自带组件来分析Handler
的使用方式,以线程种类作为区分——工作线程与主线程
这里说的工作线程就是指所有非主线程的线程。工作线程的典型用法就是HandlerThread
,先简单介绍下HandlerThread
是什么:
Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.
它是一个装备了Handler
的线程类(继承Thread
,自动创建Looper
用于创建Handler
),我们可以使用它的Handler
发送消息,HandlerThread
会为我们执行这个消息所代表的任务,当然,使用之前需要用start
方法来启动这个线程。
当我们调用start
方法后会触发run
方法,在HandlerThread
的run
方法中,它启动了Looper
,而在另一个方法——getThreadHandler
中创建了Handler
:
@Override public void run() { mTid = Process.myTid(); // 创建Looper Looper.prepare(); synchronized (this) { // 保存Looper引用,用于创建Handler mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); // 启动Looper Looper.loop(); mTid = -1; } public Handler getThreadHandler() { if (mHandler == null) { // 获取mLooper,创建Handler(注意要先启动线程,不然会报错的喔) mHandler = new Handler(getLooper()); } return mHandler; } 复制代码
先看run
方法是如何启动Looper
的:
首先我们来看Looper.prepare()
方法的相关代码:
// 一定要先调用prepare()方法,sThreadLocal.get()才会返回不为空的值喔 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); // 消息队列 mThread = Thread.currentThread(); // 当前线程 } 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)); } 复制代码
Looper.prepare()
方法中系统又调用了它的重载方法,其中创建了Looper
对象;在Looper
的构造方法中,消息队列MessageQueue
被创建,并且记录了当前线程mThread
。创建的Looper
对象使用一个ThreadLocal
静态变量“保存”(本质上不是ThreadLocal
保存了Looper
,而是Thread
,继续往下看~)。
这里细心的人会发现问题,最后一个prepare
方法是一个静态方法,调用时,如果sThreadLocal
中已有对应的Looper
对象就会抛出异常,没有则会创建一个新的Looper
,但是set
方法只传入了Looper
对象,get
方法没有传入参数,这个存取的依据到底是什么呢?我在任何地方调用这个静态方法,什么时候算是重复创建了Looper
呢?
我们可以先看抛出的这个异常:"Only one Looper may be created per thread" ,意思是一个Thread
中只能创建一个Looper
对象(要注意线程是没有自带Looper
的,需要主动调用Looper.prepare()
创建),那我们就有很大把握说这个get
&set
与Thread
有关系。
这里先记住一个结论:set
是将Looper
对象存入当前线程中,get
是获取当前线程中的Looper
对象,而ThreadLocal
对象是作为查找索引存在的,Looper
对象就是对应的这个索引对应的值。ThreadLocal
的实现很特别,可以看下:
源码阅读#ThreadLocal完全分析
总结一下,Looper.prepare()
方法给当前线程创建了Looper
对象,并保存在当前线程中,可以使用ThreadLocal
对象获取当前线程对应的Looper
。
调用Looper.myLooper()
并不是必须的,HandlerThread
里会获取Looper
引用,在我们调用getThreadHandler
方法时为我们生成Handler
:
@NonNull public Handler getThreadHandler() { if (mHandler == null) { mHandler = new Handler(getLooper()); } return mHandler; } public Looper getLooper() { if (!isAlive()) { return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; } 复制代码
isAlive()
是Thread
中的方法,代表线程是否在运行。getLooper()
方法的逻辑是:线程不在运行时就返回null
,在运行时会等待mLooper
被赋值后返回mLooper
对象,这里的synchronized
是配合上面run
方法中的这段代码的:
synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } 复制代码
这里是为了保证mLooper
获取过程的原子性(顺便简单复习下Java并发的三大特性),如果线程已经开始运行而Looper
未创建完成,此时getLooper
会等待Looper
完成创建。
根据以上的代码我们知道,只有当我们将HandlerThread
这个线程运行起来了,我们才能通过getHandler()
获取对应的Handler
对象。
我们来看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; // 无限循环 for (;;) { Message msg = queue.next(); // might block(获取消息,可能会阻塞) if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.target.dispatchMessage(msg); // 分发消息 msg.recycleUnchecked(); // 回收消息对象 } } 复制代码
首先,myLooper()
获取当前线程中的Looper
对象,如果为空就会抛出异常(提示必须先调用Looper.prepare()
方法);然后获取Looper
对象中的消息队列对象;最后在无限循环中不断地获取消息、分发消息、回收消息对象。当获取的消息为空的时候,循环就退出了。
这一步完成后,一个线程中的Looper
就成功启动了
以上就是HandlerThread
的启动过程,示范了如何在工作线程中启动Looper
并创建Handler
,分为以下四步:
个人觉得HandlerThread
其实挺鸡肋的,我们平时调用Handler
有三种方式:
sendMessage
发送消息,需要重写Handler#handleMessage
方法处理对应的消息sendMessage
发送消息,需要在构造方法中传入Handler#Callback
post
一个Runnable
第1、2种在HandlerThread
中无法使用,因为HandlerThread
已经为我们创建了Handler
,我们无法传入Callback
也无法重写handleMessage
方法,只能用第三种方法,但是第三种方法又与创建一个Thread
并实现他的run
方法没有太多差别,所以其实HandlerThread
挺鸡肋的......并不常用😂 感觉只是Android
出给大家看的一个示范。
(还有一点,我们使用Handler
其实更多的是异步线程回调主线程,更多的是在主线程中创建Handler
,然后在异步线程中调用sendMessage
)
我们知道Java
进程被创建后都会有自己的主线程,Android
系统fork
出进程后会调用ActivityThread
的main
方法创建ActivityThread
对象,并关联主线程,用它来管理主线程中的事物,而应用进程会与系统进程进行通讯,使用的是Binder
,当系统进程调用应用进程的Binder
对象时,最终会在应用进程中的Binder
线程池中执行的,为了在主线程执行相应方法,此时系统会使用一个Handler
发送消息到主线程执行相应的任务,比如启动Activity
、绑定Application
等等,这些方法暴露出可重写的方法,比如Activity
的onCreate
、onDestory
。大致过程如下:
举个例子:Binder
远程代理是IApplicationThread
,调用到ApplicationThread
(继承于IApplicationThread.Stub
)对象中的对应方法,然后使用H
(继承于Handler
,绑定主线程Looper
)发送消息,在H#handleMessage
中执行相应方法。
接下来我们看这个H
的创建过程。
我们先来看ActivityThread
是怎么初始化Handler
的:
final Looper mLooper = Looper.myLooper(); // 这里保留了主线程Looper的引用 final H mH = new H(); // 系统主要使用这个Handler从binder线程回调主线程 static volatile Handler sMainThreadHandler; // set once in main() public static void main(String[] args) { ...... Looper.prepareMainLooper(); // 1 ActivityThread thread = new ActivityThread(); // 2 if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } Looper.loop(); // 3 ...... } final Handler getHandler() { return mH; } 复制代码
这里要注意,ActivityThread
的main
方法就是在主线程上调用的,所以此时线程是已启动的
在main
方法中首先调用了Looper.prepareMainLooper()
,这是专门提供给主线程用的方法(在这之后我们可以调用Looper.getMainLooper()
在任何地方获取主线程的Looper
):
public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } } 复制代码
这里的synchronized
不同于getLooper
中的,因为getMainLooper
是个静态方法,这里锁的是Looper.class
(锁类对象)。
我们可以看到prepareMainLooper
本质上和Looper.prepare()
方法调用的方法是一致的,只不过保存了主线程的Looper
引用——sMainLooper
,并保证sMainLooper
只初始化一次,并且这个Looper
是不可退出的(只能等到进程退出了才行)。
创建ActivityThread
的时候首先将主线程Looper
对象赋值给了mLooper
,然后创建了H
,它是继承于Handler
,主线程的各种重要事件都要经过它的手,比如Activity
的生命周期、Service
的生命周期等。
我们看到,这里其实还有一个静态的Handler
——sMainThreadHandler
它其实就是mH
,但是是静态的,在使用静态Handler
的时候我踩了一个坑,后面我会讲到。
启动Looper
只需调用这个方法,前面说过,这里就不多说了。
其实主线程本质上与工作线程创建Handler
的流程一致,只是主线程多了两个限制:
sMainLooper
只能初始化一次)quitAllowed
为false
)前面说到Looper
启动了一个无限循环,要想了解Handler
的运行原理,就要搞懂这个循环里都做了什么。在这之前,我们要了解下Handler
发送的“消息”是什么,消息队列又是怎么组成的。
我们所说的消息就是Message
的对象,我们在用Handler
发送消息的时候可以设置消息类型what
(需要自定义),消息传参arg1
、arg2
、obj
,同时我们可以通过setData
设置Bundle
进行数据传送:
public int what; public int arg1; public int arg2; public Object obj; /*package*/ Bundle data; 复制代码
Message
中会使用对象池来重复利用Message
对象,这里的对象池其实是一个单向链表,Message
中会持有这个链表的首个消息,即下面的sPool
变量,而每个消息都会持有它的下一个消息next
:
// sometimes we store linked lists of these things /*package*/ Message next; public static final Object sPoolSync = new Object(); // 锁对象 private static Message sPool; // 链表头(Message对象链表的头部) private static int sPoolSize = 0; // 链表长度 private static final int MAX_POOL_SIZE = 50; // 最大长度 复制代码
这样就可以形成一个Message
链(MessageQueue
中的消息也是以此数据结构存储的,它会持有队列头的Message
对象,变量名为mMessages
),在对Message
对象调用recycleUnchecked
后,该对象就会被回收,我们可以通过一系列的静态方法obtain
来获取这些回收的对象。
void recycleUnchecked() { ...... synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; // 插入链表头部 step1 sPool = this; // 插入链表头部 step2 sPoolSize++; // 长度+1 } } } public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; // 取出消息 step1 sPool = m.next; // 取出消息 step2 m.next = null; // 取出消息 setp3 m.flags = 0; // clear in-use flag sPoolSize--; // 长度-1 return m; } } return new Message(); } 复制代码
这里还要提一点,就是Message
其实会持有发送它的Handler
对象的引用:
/*package*/ Handler target; 复制代码
我们常说内存泄漏,这里的target
就是元凶,Message
如果被发送出去,会持有Handler
的引用,如果此时Handler
持有Activity
的引用,就有可能出现内存泄漏了。
好了,了解了什么是“消息”,什么是“消息队列”,接下来我们就来看看Looper
的无限循环中都做了什么。我们先来复习下代码:
for (;;) { Message msg = queue.next(); // might block(1、获取消息,可能会阻塞) if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.target.dispatchMessage(msg); // 2、分发消息 msg.recycleUnchecked(); // 3、回收消息对象 } 复制代码
首先是queue.next()
方法,它是会阻塞的,而且在获取到的Message
对象为空时会退出循环。具体如何阻塞?这里我们直接看MessageQueue#next
的源码:
Message next() { // MessageQueue中有quit方法,调用后会清除所有Message并使用native方法将mPtr置为0 // 这里 return null 后 Looper中的循环也就停止了 final long ptr = mPtr; if (ptr == 0) { return null; } // 等待执行的IdleHandler个数 int pendingIdleHandlerCount = -1; // -1 only during first iteration // 这个是一个等待时间,下面会用到 int nextPollTimeoutMillis = 0; // 无限循环取消息 for (;;) { if (nextPollTimeoutMillis != 0) { // 将进程中未执行的命令一并送往CPU处理 Binder.flushPendingCommands(); } // 这里调用了native方法,在底层进行阻塞 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // 如果Message的target是空的,就会去寻找消息队列中的“异步消息”,这个机制叫同步屏障。 if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; // isAsynchronous 其实只是一个标志位,可以用set方法设置,代表这个消息为异步消息(有时候并不是真的异步)。 } while (msg != null && !msg.isAsynchronous()); } // 如果上面找到了异步消息就执行异步消息,否则执行消息队列的头部消息(消息插入时是插入到队列尾部的) if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { // 这里去除的是异步消息 prevMsg.next = msg.next; } else { // 除去头部消息 mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg; // 返回Message } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); // 退出 return null; } // 当找不到Message或者“Message is not ready”的时候就会进入下面的步骤 // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } // 有IdleHandler,初始化mPendingIdleHandlers if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } } 复制代码
这段代码有几个重要的步骤:
关于第一点,可以参考这篇文章:Android 中 MessageQueue 的 nativePollOnce;关于第二点和第四点,我的下篇文章会分析: 源码阅读#Handler(下)同步屏障与IdleHandler
这里调用的是Handler
的dispatchMessage
方法:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); // Handler的post方法发送的带Runnable对象的Message } else { if (mCallback != null) {// 构造方法中传入的Callback if (mCallback.handleMessage(msg)) {// 这里返回true就不会继续执行下面的handlerMessage了 return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); } public void handleMessage(Message msg) {} public interface Callback { public boolean handleMessage(Message msg); } 复制代码
这里有三种处理方式:
Message
的callback
,这个callback
是个Runnable
对象,当我们调用Handler
的post
方法的时候会传入一个Runnable
,然后发送一个带上这个Runnable
的Message
对象,最终会在dispatchMessage
里执行。Callback
对象,它也有一个handleMessage
方法,当这个方法返回true
的时候,就不会调用Handler
自身的handleMessage
方法了,代表被消费掉了(这里和点击事件的分发逻辑有点像)。Handler
自身的handleMessage
方法处理消息。回收消息对象的过程上面第1小点分析过,这里就不再赘述啦
整理了一下方法调用的顺序:
我们以创建线程A为开始,先在线程A中创建Looper
(Looper.prepare()
);然后启动Looper
(Looper.loop()
)并创建Handler
(可以传入Callback
);创建Handler
后我们就可以调用sendMessage
方法或者post
方法发送消息,最后调用到handleCallback
或者handlerMessage
方法处理消息。
图中区分了线程A和线程B,所有方法都被放在了执行该方法的线程中,记得之前被问过“MessageQueue在哪个线程执行?”的问题(是不是有点奇葩......),回答的是“放入队列的方法是在发送消息的线程中执行的,取消息的方法是在回调线程中执行的”,一放一取,就跨了线程。
到这里,Handler
的基本用法与原理就讲了一部分了,知识点可能有点穿插,下一篇我会接着讲两种高级使用方法:源码阅读#Handler(下)同步屏障与IdleHandler
先给大家看一个类:
public class BugClass{ public static boolean b; public static Handler handler = new Handler(); } 复制代码
这个handler
的作用是回调主线程,写这个类的人希望这个handler
能在主线程被创建,但是程序在运行的时候会报这个错:Can't create handler inside thread XXX that has not called Looper.prepare()。为什么呢?
答案是在App启动的时候创建了一个异步线程,在其中调用了BugClass.b
,此时BugClass
就会被类加载器加载,同时,静态的handler
会被创建,而此时,这个异步线程是没有创建Looper
的,所以就报了这个错。(当时见到这个BUG的时候也很新奇......)
最后这样解决了:
public static Handler handler = new Handler(Looper.getMainLooper()); 复制代码
最后,如果觉得这篇文章对你有帮助,也帮忙点个赞吧~