Google为何不让用Handler的runWithScissors()

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 个场景:

  1. Handler post() 失败,表示 Looper 出问题了;

  2. 等待超时,任务还没有执行结束;

除了超时唤醒外,我们还需要在任务执行完后,唤醒当前线程。

回看 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 个条件:

  1. Handler 的 Looper 不允许退出,例如 Android 主线程 Looper 就不允许退出;

  2. Looper 退出时,使用安全退出 quitSafely() 方式退出;

 

转载:https://mp.weixin.qq.com/s/Qs9C6m21e3OfS0uo94ohGA

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值