从源码与官方文档看之Handler篇(十)

简介: 每次阅读源码,我都想着许多过往云烟,可以说,一切不在一样,不过,想再多又如何,倒不如按下心思,在手中记下这一切更为妥当。还是看看这些充满智慧结晶的源码吧!

前言

感觉时间真的过的飞快,转眼我都工作大半年了,仿佛学生时代的我就在昨日,而今日却要为生计而奔波。记得小时候很爱看书,那时候最想买书店那种上面写着名著,包装又很漂亮的,而且读完书还能和小伙伴们分享自己获得的新知,而如今都化为泡影,只留下为着不知怎样的未来之景而不得不看源码的我。

每次阅读源码,我都想着许多过往云烟,可以说,一切不在一样,不过,想再多又如何,倒不如按下心思,在手中记下这一切更为妥当。还是看看这些充满智慧结晶的源码吧!

正篇

本想着先深入一下post方法的,但转念还是按部就班吧,让我们接着往下看:

/**
 * Runs the specified task synchronously.
 * <p>
 * If the current thread is the same as the handler thread, then the runnable
 * runs immediately without being enqueued.  Otherwise, posts the runnable
 * to the handler and waits for it to complete before returning.
 * </p><p>
 * This method is dangerous!  Improper use can result in deadlocks.
 * Never call this method while any locks are held or use it in a
 * possibly re-entrant manner.
 * </p><p>
 * This method is occasionally useful in situations where a background thread
 * must synchronously await completion of a task that must run on the
 * handler's thread.  However, this problem is often a symptom of bad design.
 * Consider improving the design (if possible) before resorting to this method.
 * </p><p>
 * One example of where you might want to use this method is when you just
 * set up a Handler thread and need to perform some initialization steps on
 * it before continuing execution.
 * </p><p>
 * If timeout occurs then this method returns <code>false</code> but the runnable
 * will remain posted on the handler and may already be in progress or
 * complete at a later time.
 * </p><p>
 * When using this method, be sure to use {@link Looper#quitSafely} when
 * quitting the looper.  Otherwise {@link #runWithScissors} may hang indefinitely.
 * (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
 * </p>
 *
 * @param r The Runnable that will be executed synchronously.
 * @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
 *
 * @return Returns true if the Runnable was successfully executed.
 *         Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 *
 * @hide This method is prone to abuse and should probably not be in the API.
 * If we ever do make it part of the API, we might want to rename it to something
 * less funny like runUnsafe().
 */
public final boolean runWithScissors(@NonNull 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(),老规矩,先解释一下注释,其实注释也挺有趣的!

首先方法名头上顶了一个@hide的注解,表明这个方法不希望普通开发者使用,然后还打趣道:"此方法容易被滥用,可能不应该出现在 API 中。如果我们真的让它成为 API 的一部分,我们可能得把它重命名为像 runUnsafe() 这样不那么有趣的东西比较好。"这段话形象的告诉了我们这个方法应该成为一个冷门方法,但经过我查询资料和源码发现,这个方法用到的地方其实不少,在 Framework 中,更是很多场景都用到了,一些博客也举例了,例如比较熟悉的 WMS 启动流程中,分别在 main()initPolicy() 中,通过 runWithScissors() 切换到 "android.display" 和 "android.ui" 线程去做一些初始工作:

private void initPolicy() {
  UiThread.getHandler().runWithScissors(new Runnable() {
    public void run() {
      // 运行在"android.ui"线程
      WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
      mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
    }
  }, 0);
}

全局搜索也能看到众多应用场景:

网络异常,图片无法展示
|

之所以在Framework中有这么多应用场景而不建议我们用是因为该方法存在隐患,这点在注释第二段重点说明:这种方法很危险!使用不当会导致死锁。切勿在持有任何锁时调用此方法,或以可能可重入的方式使用它。这段话表明该方法有死锁风险以及超时后,没有取消的逻辑(即通过 runWithScissors() 发送 Runnable 时可设置超时时间,但当超时唤醒时,会直接 false 退出。所以当超时退出时,这个 Runnable 依然还会在目标线程的 MessageQueue 中,并没有被移除掉,这也就导致它最终还是会被 Handler 线程调度并执行。)

接着我们再去看看它的作用:runWithScissors() 接受一个 Runnable,并且可以设置超时时间,先简单的对入参进行校验;如果当前线程和 Handler 的处理线程一致,则直接运行 run() 方法;线程不一致,则通过 BlockingRunnable 包装一下,并执行其 postAndWait() 方法,也就是说我们可以通过阻塞的方式,向目标线程发送任务,并等待任务执行结束。

而注释又说明:在后台线程必须同步等待必须在处理程序线程上运行的任务完成的情况下,此方法有时很有用。然而,这个问题通常是糟糕设计的征兆。在采用这种方法之前,请考虑改进设计(如果可能),哈哈哈,结果仍是不希望我们直接使用该方法,如果想使用,可以考虑我们自己重新实现一个 BlockingRunnable 去使用。而且得注意安全使用 runWithScissors() 还需要满足 2 个条件:

  1. Handler 的 Looper 不允许退出,比如说, Android 主线程 Looper 就不允许退出;
  2. Looper 退出时,要使用安全退出 即quitSafely() 方式退出;

总结

总之,这个注释特长的方法不推荐使用,仅为我们阅读源码所需要,毕竟方法存在缺陷,在阅读源码又可能会遇到,所以还是得看看的,而且这个方法还涉及Synchronized 锁和它的等待/通知机制,可以复习或学习一些临界问题,线程的同步、死锁,以及 Handler 的退出方式对消息的影响这方面的知识集。

所以,后续我也要复习一下有关这些锁啊,同步啊之类的知识了。

相关文章
|
消息中间件 Android开发
Handler源码解读——handler使用时的注意事项
工作中经常会遇到从子线程发送消息给主线程,让主线程更新UI的操作,常见的有handler.sendMessage(Message),和handler.post(runnable)和handler.postDelayed(runnable, milliseconds);一直在使用这些方法,却不知道他们的原理,今天就来解释一下他们的原理。
从源码与官方文档看之Handler篇(七)
接下来就是我们可以正常调用的Handler类方法了,希望早日完成Handler类的源码阅读。
|
监控
从源码与官方文档看之Handle篇(一)
之前的文章实在太肤浅,写出来其实很多都没有太大帮助,所以我们另起一个系列“从源码与官方文档看”。
110 0
|
JSON Java 数据格式
Retrofit笔记 | 基本使用步骤
Retrofit笔记 | 基本使用步骤
|
Java Android开发
从源码与官方文档看之Handler篇(二)
今天又发现自己原来是Handler这个类名打错了导致前面几篇一直再说Java的Handle,而安卓的是Handler,前一篇文章我们主要翻译了安卓官方对Handler的注释,这一篇我们来看看Handler类的一些属性,成员变量。
|
消息中间件 Java 调度
从源码与官方文档看之Handler篇(一)
好家伙,写了四篇文章才发现自己看源码的Handle所属的包不一样
107 0
从源码与官方文档看之Handler篇(五)
尽管前路坎坷,可我们还是继续走了下去,一转眼已经又到了第五篇了,每篇内容较少,所以后面整理完时,会汇总成一篇文章,相信这样做会更具有可读性。
|
安全 API
从源码与官方文档看之Handler篇(六)
今天继续看Handler类,构造方法已经基本全部看完,后面应该是一些类的一般方法之类的内容了,继续加油。
121 0
从源码与官方文档看之Handler篇(三)
前面我们说到dispatchMessage方法,今天我们继续往下看源码。
128 0
|
消息中间件 Android开发
从源码与官方文档看之Handler篇(九)
今天我们来详细看看Handler的post()方法吧
112 0