从源码与官方文档看之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 的退出方式对消息的影响这方面的知识集。

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

相关文章
|
4天前
|
人工智能 API 内存技术
刚刚 DeepSeek V4.1 Flash 开启内测,1 分钟教你用上!
刚刚 DeepSeek 内测群发布了 DeepSeek V4.1 Flash 中间版本内测的消息,这次的模型采用了新的结构,原生支持多模态、能力更强、速度更快、且成本更低。
1670 5
|
8天前
|
人工智能 运维 BI
阿里云千问办公QwenWork深度解析:基于Qwen3.8,六大核心能力重构企业全自动化工作流与计费选型指南
传统AI办公工具大多停留在对话问答、文档摘要、简单文案生成层面,只能完成单点碎片化任务,无法自主拆解复杂业务流程,很难串联多工具、多文档、外部业务系统完成端到端完整工作交付。很多企业在落地AI办公的时候,需要组合多款不同工具,来回切换界面,手动复制粘贴中间结果,智能化改造落地门槛居高不下。千问办公QwenWork是整合多款智能体产品能力打造的一体化企业办公智能体平台,底层基座依托Qwen3.8大模型,打通桌面端Agent、云端Agent、企业协同Agent三种运行形态,不再局限简单问答,接收业务目标之后自主拆解任务步骤,调用各类工具,处理文档、表格、浏览器自动化、数据查询,直接输出可交付的办公
1613 1
|
5天前
|
SQL 人工智能 前端开发
QoderWake 1.0 正式发布:从桌面里的 Agent,到工作现场的数字员工
QoderWake v1.0正式发布:企业级数字员工团队平台。支持“一句话建岗”,预置10类特训岗位;Waker常驻钉钉/飞书群,@即响应、自动协作、跨任务记忆;具备定时/事件/API多触发方式与统一任务看板;已沉淀27.6万条记忆、12.3万项技能,助力组织实现人机协同增效。
715 1
|
17天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
3876 5
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
|
8天前
|
人工智能 自然语言处理 安全
阿里云AI数智鉴密:AI 生成内容如何拿到一张"防篡改的身份证"
隐形水印 + C2PA签名:让AI生成内容“持证上岗”。
1142 0
|
9天前
|
网络协议 Linux iOS开发
【2026实测】Wireshark下载+安装+汉化+使用教程(图文版,巨详细)
Wireshark 是一款免费开源的网络协议分析工具,可实时捕获、解析并可视化数据包,助你诊断网络故障、分析通信协议(如HTTP、DNS、TCP等)。支持Windows/macOS/Linux,含中文界面,新手入门便捷。(239字)
|
3天前
|
缓存 测试技术 API
DeepSeek V4.1 Flash 内测接入:改个模型名即可调用(附代码)
DeepSeek V4.1 Flash 内测不用申请,base_url 不变、改个模型名就能调,9/10 到期。本文讲清接入、计费限流与多模态注意点。
694 0
DeepSeek V4.1 Flash 内测接入:改个模型名即可调用(附代码)
|
10天前
|
缓存 数据可视化 开发工具
DeepSeek Harness 怎么更新?dsh 更新完整指南:更新本体(npx、npm、源码)与更新插件两种方式
DeepSeek Harness 的更新分两层:本体更新(npx 自动最新、npm update -g、源码 git pull)与插件更新(插件市场点更新、命令行覆盖安装)。本文按「准备 → 更新本体 → 更新插件 → 更新后检查」四步走,覆盖新手常见疑问。
1266 1
DeepSeek Harness 怎么更新?dsh 更新完整指南:更新本体(npx、npm、源码)与更新插件两种方式