Handler的使用
public Handler(Looper looper, Callback callback, boolean async)
val handler = object : Handler(Looper.getMainLooper(), object : Callback {
override fun handleMessage(msg: Message): Boolean {
return true // 优先于 handleMessage,返回true的话 handleMessage 就不再执行
}
}) {
override fun handleMessage(msg: Message) {
}
}
handler.sendMessage()
handler.sendMessageAtTime()
handler.sendEmptyMessageDelayed()
handler.sendMessageAtFrontOfQueue()
handler.post()
- boolean async: Handler发送的消息是否要设置成异步消息,异步消息用于同步屏障。
- Callback: 如果设置了该参数,Handler在dispatchMessage时会优先调用callback;返回为true,则不再执行handleMessage
- Looper: 每个Handler都有一个对应的Looper,如果没有显式设置Looper,那么Handler会取该线程对应的Looper赋给该Handler。子线程中必须手动创建Looper(prepare)。
消息机制工作流程
- Handler:通过send message或post runnable到Looper处理,post最终也是调用send方法调用。
- MessageQueue: 它内部使用一个 Message 链表实现消息的存和取。 链表的排列依据是 Message.when,表示 Message 期望被分发的时间,该值是 SystemClock. uptimeMillis() 与 delayMillis 之和。主要是两个操作,插入和读取,读取伴随着删除。通过enqueueMessage完成入队的操作,即链表的插入,而next里面是一个死循环,当有消息时会通过调用next方法从单链表里移出,没有时则阻塞
- Looper:Looper.prepare方法会为当前线程创建一个Looper,一个线程只能对应一个 Looper,再创建时会报错;而Looper.loop这是开启消息循环;Looper.myLooper() 获取当前线程的Looper,其实调用sThreadLocal.get()。
- loop是一个死循环,必须执行loop才能开始处理msg. 当MessageQueue的next返回null时则退出循环,否则就与next一起阻塞,否则就通过target调用handler的dispatchMessage处理消息。
发送 message 时,通过 enqueueMessage 加入消息队列队列 MessageQueue。而Loop也是一个循环,不断调用 MessageQueue 的 next 方法来读取消息,next阻塞Loop也阻塞。
//handler与 MessageQueue、Looper的关联
public Handler(@Nullable Callback callback, boolean async) {
mLooper = Looper.myLooper(); //sThreadLocal.get(),这里是直接取。所以子线程一定要先创建好
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//Looper的创建
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));
}
//MQ的创建
private Looper(boolean quitAllowed) {//quitAllowed 是否允许退出Looper
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
message
- message 支持复用,未使用的 message 以单链表形式维持关系,sPool指向的对象永远是单链表的第一个元素。
- MessageQueue 中 message 排列依据是 Message.when,表示 Message 期望被分发的时间,该值是 SystemClock.uptimeMillis() 与 delayMillis 之和。uptimeMillis: Returns milliseconds since boot, not counting time spent in deep sleep。
- target 指向发送此消息的 handler,后续 Looper 派发消息时要知道是哪个 handler。
public final class Message implements Parcelable {
public int what;
public int arg1;
public int arg2;
public Object obj;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public long when;
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
/*package*/ Message next;
//Message 以单链表形式维持关系,sPool指向的对象永远是单链表的第一个元素
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
// 消息回收
void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
when = 0;
target = null; //...
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
}
MessageQueue
- enqueueMessage、next 取消息时需要加锁保证多线程下链接操作的安全,因为发送消息时可能会从多个线程上。
- 当 无消息或者消息的when不到时间时, 会调用 nativePollOnce 使线程阻塞,让出cpu调度;通过 nativeWake 唤醒线程
- 如果使用wait和notify来阻塞或唤醒线程,只能处理java层的消息,对于系统的消息不能处理。
public final class MessageQueue {
Message mMessages; // 指向队首
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); // IdleHandler
private IdleHandler[] mPendingIdleHandlers;
private native void nativePollOnce(long ptr, int timeoutMillis); // 队列休眠,looper 也会阻塞
private native static void nativeWake(long ptr); // 唤醒队列
boolean enqueueMessage(Message msg, long when) {
// 加锁,保证线程安全
synchronized (this) {
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 {
// 按 when 顺序插入链表中
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;
}
//是否需要尝试唤醒队列
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
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) {
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;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}
}
}
// 同步屏障相关
public int postSyncBarrier() { }
private int postSyncBarrier(long when) { }
public void removeSyncBarrier(int token) { }
// IdleHandler 相关
public void addIdleHandler(IdleHandler handler) { }
public void removeIdleHandler(@NonNull android.os.MessageQueue.IdleHandler handler) {}
// 其它 message 操作相关
boolean hasMessages(Handler h, Runnable r, Object object) { }
boolean hasMessages(Handler h) { }
void removeMessages(Handler h, int what, Object object) { }
void removeEqualMessages(Handler h, int what, Object object) { }
......
}
Looper
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final MessageQueue mQueue;
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));
}
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;
}
}
}
private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
return true;
}
public static Looper myLooper() {
return sThreadLocal.get();
}
public static MessageQueue myQueue() {
return myLooper().mQueue;
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
}
- 如果是想创建子线程的Handler,子线程默认没有Looper的,需要如下:
Looper.prepare();// 创建Looper
Handler handler = new Handler();
Looper.loop();
- 如果是创建主线程的Handler,则需要传入MainLooper: Handler mainHandler = new Handler(Looper.getMainLooper());
异步消息与同步屏障
- 屏障消息和普通消息的区别在于屏障没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
- 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
- postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
- postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。
- 插入普通消息会唤醒消息队列,但是插入屏障不会。
- 移除同步屏障的方法是MessageQuene.removeSyncBarrier(int token),token就是添加同步屏障时候返回的唯一标示.
通过构造参async为true的Handler发送的Message都为异步消息,或者 调用msg.setAsynchronous(true); 异步消息一般与同步屏障配合使用。
//构造一个发送处理异步消息的Handler
Handler mHandler = new Handle(true)
//通过这个mHandler发送的Message,在queueMessage时候,都会把Message的asynchronous设置为true,即异步消息
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//将Handler赋值给Message的target变量
msg.target = this;
//mAsynchronous为true,为异步消息
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
同步屏障是通过MessageQueue的postSyncBarrier方法插入到消息队列的。
//mHandler.getLooper().getQueue().postSyncBarrier();
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
//1、屏障消息和普通消息的区别是屏障消息没有tartget。
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
//2、根据时间顺序将屏障插入到消息链表中适当的位置
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
//3、返回一个序号,通过这个序号可以撤销屏障
return token;
}
}
那么屏障是如何挡住普通消息只允许异步消息通过的呢?我们知道MessageQueue是通过next方法来获取消息的。
Message next() {
//1、如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {//2、遇到屏障 msg.target == null
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());//3、遍历消息链表找到最近的一条异步消息
}
if (msg != null) {
//4、如果找到异步消息
if (now < msg.when) {//异步消息还没到处理时间,就在等会(超时时间)
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//异步消息到了处理时间,就从链表移除,返回它。
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 如果没有异步消息就一直休眠,等待被唤醒。
nextPollTimeoutMillis = -1;//nativePollOnce 传入-1即可
}
}
}
IdleHandler
- IdleHandler 就是 MessageQueue为空或者目前没有需要执行的Message时会回调的接口对象
- 当前队列是否空闲:mMessages == null || now < mMessages.when
- IdleHandler queueIdle() 返回true就是单次回调后不删除,下次进入空闲时继续回调该方法,false只回调单次。
//当前队列将进入阻塞等待消息时调用该接口回调,即队列空闲
Looper.myQueue().addIdleHandler(object : MessageQueue.IdleHandler {
override fun queueIdle(): Boolean {
//返回true就是单次回调后不删除,下次进入空闲时继续回调该方法,false只回调单次。
return false
}
})
public final class MessageQueue {
//判断当前队列是不是空闲的,辅助方法
public boolean isIdle() {
synchronized (this) {
final long now = SystemClock.uptimeMillis();
return mMessages == null || now < mMessages.when;
}
}
......
//Looper的prepare()方法会通过ThreadLocal准备当前线程的MessageQueue实例,
//然后在loop()方法中死循环调用当前队列的next()方法获取Message。
Message next() {
......
for (;;) {
......
nativePollOnce(ptr, nextPollTimeoutMillis);
...
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
//循环遍历所有IdleHandler
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 {
//调用IdleHandler接口的queueIdle方法并获取返回值。
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
//如果IdleHandler接口的queueIdle方法返回false说明只执行一次需要删除。
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
......
}
}
}
线程切换是如何做到的
在子线程中sendMessage(Message)发送消息,然后在Handler的handleMessage(Message)接收消息,执行更新UI操作。那么Handler是如何把消息从MyThread传递到MainThread中来呢?
没有什么所谓的线程的切换。消息的回调线程由Looper所在线程决定。Looper 所在线程内部有一个 while(true),有消息就调用回掉函数,没有就 wait。Handler 只是一个用于收发消息的作用。因为 Handler 的 dispatchMessage 方法是在创建 Handler 的线程中的 Looper 内部调用的,因此 Looper 所处的线程也就决定了你 Handler 提交任务执行所在的线程。handler 所属线程的 Looper 会将 msg 从 msgqueue 中取出然后执行相关逻辑。handler的执行跟创建handler的线程无关,跟创建looper的线程相关。
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler handler=new Handler(getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,"hello,world",Toast.LENGTH_LONG).show();
}
});
Looper.loop();
}
});
thread.start();
Looper 死循环与卡顿
对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder 线程也是采用死循环的方法,通过循环方式不同与 Binder 驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法 onCreate/onStart/onResume 等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。
主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到 Linux pipe/epoll 机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 Loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步 I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量 CPU 资源。
HandlerThread
HandlerThread 继承 Thread,内部创建了Looper并循环。作用就是不断的从消息队列中取出Message,然后在当前线程执行。
在什么时候用HandlerThread? 可以使用HandlerThread来模拟『单线程+队列』结构,HandlerThread就相当于是一个SingleThreadExecutor结构,即单线程池,在App启动的时候,如果有很多初始化工作不需要在UI线程完成,那么可以将这些任务发送至HandlerThread,然后顺序完成,加快应用的启动速度
HandlerThread extends Thread {
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
}
// 使用步骤如下
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
// 步骤2:启动线程
mHandlerThread.start();
// 步骤3:创建工作线程Handler & 复写handleMessage()
Handler workHandler = new Handler(handlerThread.getLooper() ) {
@Override
public boolean handleMessage(Message msg) {
...//消息处理
return true;
}
});
// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
Message msg = Message.obtain();
workHandler.sendMessage(msg);
// 步骤5:结束线程,即停止线程的消息循环
mHandlerThread.quit();
如下使用,作用类似于 Executors.newSingleThreadExecutor():
private val lightWorkThread = HandlerThread("LightThread", THREAD_PRIORITY_BACKGROUND).apply {
start()
}
private val lightWorkHandler: Handler = Handler(lightWorkThread.looper)
private val lightWorkExecutor: Executor = HandlerExecutor(lightWorkHandler)
private val lightWorkDispatcher: CoroutineDispatcher = lightWorkExecutor.asCoroutineDispatcher()
private var lightWorkScope: CoroutineScope = CoroutineScope(lightWorkDispatcher)
class HandlerExecutor(private val handler: Handler) : Executor {
override fun execute(command: Runnable?) {
command?.let { handler.post(it) }
}
}