runWithScissors() 是 Handler 的一个方法,被标记为 @hide,不允许普通开发者调用。这个方法算是比较冷门,如果面试中被问及,面试者不知道时,通常面试官会换个问法:"如何在子线程通过 Handler 向主线程发送一个任务,并等主线程处理此任务后,再继续执行?"。这个场景,就可以借助 runWithScissors()
来实现。虽然该方法被标记为 @hide,但是在 Framework 中,也有不少场景使用到它。
先撇开 runWithScissors()
方法,既然这里存在 2 个线程的通信,那肯定需要考虑多线程同步。首先想到的就是 Synchronized 锁和它的等待/通知机制,而通过 Handler 跨线程通信时,想要发送一个「任务」,Runnable 肯定比 Message 更适合。接下来,我们看看 runWithScissors()
的实现是不是如我们预想一样:
public final boolean runWithScissors(final Runnable r, long timeout) {
if (r == null) {
throw new IllegalArgumentException("runnable must not be null");
}
if (timeout < 0) {
throw new IllegalArgumentException("timeout must be non-negative");
}
if (Looper.myLooper() == mLooper) {
r.run();
return true;
}
BlockingRunnable br = new BlockingRunnable(r);
return br.postAndWait(this, timeout);
}
可以看到,runWithScissors()
接受一个 Runnable,并且可以设置超时时间。流程也非常简单:
1.先简单的对入参进行校验;
2.如果当前线程和 Handler 的处理线程一致,则直接运行 run()
方法;
3.线程不一致,则通过 BlockingRunnable 包装一下,并执行其 postAndWait()
方法。
那再继续看看 BlockingRunnable 的源码:
private static final class BlockingRunnable implements Runnable {
private final Runnable mTask;
private boolean mDone;
public BlockingRunnable(Runnable task) {
mTask = task;
}
@Override
public void run() {
try {
// 运行在 Handler 线程
mTask.run();
} finally {
synchronized (this) {
mDone = true;
notifyAll();
}
}
}
public boolean postAndWait(Handler handler, long timeout) {
if (!handler.post(this)) {
return false;
}
synchronized (this) {
if (timeout > 0) {
final long expirationTime = SystemClock.uptimeMillis() + timeout;
while (!mDone) {
long delay = expirationTime - SystemClock.uptimeMillis();
if (delay <= 0) {
return false; // timeout
}
try {
wait(delay);
} catch (InterruptedException ex) {
}
}
} else {
while (!mDone) {
try {
wait();
} catch (InterruptedException ex) {
}
}
}
}
return true;
}
}
待执行的任务,会记入 BlockingRunnable 的 mTask,等待后续被调用执行。
postAndWait()
的逻辑也很简单,先通过 handler 尝试将 BlockingRunnable 发出去,之后进入 Synchronized 临界区,尝试 wait()
阻塞。
如果设置了 timeout,则使用 wait(timeout)
进入阻塞,若被超时唤醒,则直接返回 false,表示任务执行失败。
那么现在可以看到 postAndWait()
返回 false 有 2 个场景:
-
Handler
post()
失败,表示 Looper 出问题了; -
等待超时,任务还没有执行结束;
除了超时唤醒外,我们还需要在任务执行完后,唤醒当前线程。
回看 BlockingRunnable 的 run()
方法,run()
被 Handler 调度并在其线程执行。在其中调用 mTask.run()
,mTask 即我们需要执行的 Runnable 任务。执行结束后,标记 mDone 并通过 notifyAll()
唤醒等待。
任务发起线程,被唤醒后,会判断 mDone,若为 true 则任务执行完成,直接返回 true 退出。
runWithScissors() 的问题:
1 如果超时了,没有取消的逻辑
通过 runWithScissors()
发送 Runnable 时,可以指定超时时间。当超时唤醒时,是直接 false 退出。当超时退出时,这个 Runnable 依然还在目标线程的 MessageQueue 中,没有被移除掉,它最终还是会被 Handler 线程调度并执行。此时的执行,显然并不符合我们的业务预期。
2 可能造成死锁
而更严重的是,使用 runWithScissors()
可能造成调用线程进入阻塞,而得不到唤醒,如果当前持有别的锁,还会造成死锁。
我们通过 Handler 发送的 MessageQueue 的消息,一般都会得到执行,而当线程 Looper 通过 quit()
退出时,会清理掉还未执行的任务,此时发送线程,则永远得不到唤醒。
那么在使用 runWithScissors()
时,就要求 Handler 所在的线程 Looper,不允许退出,或者使用 quitSafely()
方式退出。
quit()
和 quitSafely()
都表示退出,会去清理对应的 MessageQueue,区别在于,qiut()
会清理 MessageQueue 中所有的消息,而 quitSafely()
只会清理掉当前时间点之后(when > now)的消息,当前时间之前的消息,依然会得到执行。
那么只要使用 quitSafely()
退出,通过 runWithScissors()
发送的任务,依然会被执行。
也就是说,安全使用 runWithScissors()
要满足 2 个条件:
-
Handler 的 Looper 不允许退出,例如 Android 主线程 Looper 就不允许退出;
-
Looper 退出时,使用安全退出
quitSafely()
方式退出;