本文重点:
- 以
HandlerThread、ActivityThread为例,讲解Handler的用法; - 分析
Handler中的重要角色以及运行过程; - 最后讲一个自己遇到的
Handler使用不当的坑。
代码以 Android API 28 (Android 9)为准,如有不当,希望指正😀
一、Handler 基本用法
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的:
1、创建Looper:Looper.prepare()(必须步骤)
首先我们来看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,但是sThreadLocal.set只传入了Looper对象,sThreadLocal.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的实现很特别,可以看下:
总结一下,Looper.prepare()方法给当前线程创建了Looper对象,并保存在当前线程中,可以使用ThreadLocal对象获取当前线程对应的Looper。
2、获取创建的Looper对象:Looper.myLooper()
调用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并发的三大特性),整个getLooper的逻辑是:如果线程已经开始运行而Looper未创建完成,此时getLooper会等待Looper完成创建。
根据以上的代码我们知道,只有当我们将HandlerThread这个线程运行起来了,我们才能通过getHandler()获取对应的Handler对象。
3、启动Looper开始无限循环:Looper.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;
// 无限循环
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。分为以下四步:
- 启动线程:thread.start()
- 在线程中调用Looper.prepare()创建Looper
- 在线程中调用Looper.loop()启动Looper
- 创建Handler
个人觉得HandlerThread其实挺鸡肋的,因为你想想,我们平时调用Handler有三种方式:
sendMessage发送消息,需要重写Handler#handleMessage方法处理对应的消息sendMessage发送消息,需要在构造方法中传入Handler#Callbackpost一个Runnable
第1、2种在HandlerThread中无法使用,因为HandlerThread已经为我们创建了Handler,我们无法传入Callback也无法重写handleMessage方法,只能用第三种方法,但是第三种方法又与创建一个Thread并实现他的run方法没有太多差别,所以其实HandlerThread挺鸡肋的......并不常用😂 感觉只是Android出给大家看的一个示范。
(还有一点,我们使用Handler其实更多的是异步线程回调主线程,更多的是在主线程中创建Handler重写handleMessage方法,然后在异步线程中调用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方法就是在主线程上调用的,所以此时线程是已启动的
1、创建主线程Looper:Looper.prepareMainLooper()
在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中的,这里锁的是Looper.class,锁了这个类对象,因为getMainLooper是个静态方法。
我们可以看到prepareMainLooper本质上和Looper.prepare()方法调用的方法是一致的,只不过保存了主线程的Looper引用——sMainLooper,并保证sMainLooper只初始化一次,并且这个Looper是不可退出的(只能等到进程退出了才行)。
2、创建Handler:H
创建ActivityThread的时候首先将主线程Looper对象赋值给了mLooper,然后创建了H,它是继承于Handler,主线程的各种重要事件都要经过它的手,比如Activity的生命周期、Service的生命周期等。
我们看到,这里其实还有一个静态的Handler——sMainThreadHandler它其实就是mH,但是是静态的,在使用静态Handler的时候我踩了一个坑,后面我会讲到。
3、启动Looper:Looper.loop()
启动Looper只需调用这个方法,前面说过,这里就不多说了。
其实主线程本质上与工作线程创建Handler的流程一致,只是主线程多了两个限制:
- 主线程只有一个(
sMainLooper只能初始化一次) - 不能终止loop过程(
quitAllowed为false)
二、Handler运行原理
前面说到Looper启动了一个无限循环,要想了解Handler的运行原理,就要搞懂这个循环里都做了什么。在这之前,我们要了解下Handler发送的“消息”是什么,消息队列又是怎么组成的。
1、什么是“消息”,消息队列如何组成
我们所说的消息就是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中会持有一个链表头——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来获取Message对象。
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的引用,就有可能出现内存泄漏了。
2、Loop无限循环都做了什么
好了,了解了什么是“消息”,什么是“消息队列”,接下来我们就来看看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、回收消息对象
}
2.1、取消息的过程:queue.next()
首先是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;
}
}
这段代码有几个重要的步骤:
- nativePollOnce阻塞。
- 判断是否有同步屏障消息,有的话去寻找异步消息。
- 如果获取到消息,不论是普通消息还是异步消息,直接返回
- 如果没有取到消息或者消息还没准备好,执行IdleHandler
关于第一点,可以参考这篇文章:Android 中 MessageQueue 的 nativePollOnce;关于第二点和第四点,我的下篇文章会分析: 源码阅读#Handler(下)同步屏障与IdleHandler
2.2、分发消息的过程:msg.target.dispatchMessage(msg)
这里调用的是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方法处理消息。
2.3、回收消息对象:msg.recycleUnchecked()
回收消息对象的过程上面第1小点分析过,这里就不再赘述啦
3、Handler运行原理总结
整理了一下方法调用的顺序:
我们以创建线程A为开始,先在线程A中创建Looper(Looper.prepare());然后启动Looper(Looper.loop())或者创建Handler(可以传入Callback),这两步可以不分先后;创建Handler后我们就可以调用sendMessage方法或者post方法发送消息,最后调用到handleback或者handlerMessage方法处理消息。
图中区分了线程A和线程B,所有方法都被放在了执行该方法的线程中,记得之前被问过“MessageQueue在哪个线程执行?”的问题(是不是有点奇葩......),回答的是“放入队列的方法是在发送消息的线程中执行的,取消息的方法是在回调线程中执行的”。
到这里,Handler的基本用法与原理就讲了一部分了,知识点可能有点穿插,下一篇我会接着讲两种高级使用方法:源码阅读#Handler(下)同步屏障与IdleHandler
三、静态Handler的一个坑
先给大家看一个类:
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());
最后,如果觉得这篇文章对你有帮助,也帮忙点个赞吧~