Android Handler runWithScissors 梳理流程解析

目录
  • 前言
  • runWithScissors
    • 梳理流程
    • 存在的问题
  • 总结

前言

看 WMS 代码的时候看到了 Handler.runWithScissors 方法,所以来恶补一下

public static WindowManagerService main(final Context context, final InputManagerService im,final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,ActivityTaskManagerService atm, Supplier<SurfaceControl.Transaction> transactionFactory,Supplier<Surface> surfaceFactory,
            Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
        DisplayThread.getHandler().runWithScissors(() ->
                sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,
                        atm, transactionFactory, surfaceFactory, surfaceControlFactory), 0);
        return sInstance;
}

通过 DisplayThread.getHandler() 调用了 runWithScissors 方法。

该方法的设计初衷就是:在一个线程中通过 Handler 向另外一个线程发送消息,并等待另一个线程处理完成后再继续执行。

runWithScissors

首先来看一下官方文档的描述:

同步运行指定的任务。如何当前线程和处理线程相同,则立即执行不用排队,否则就发送到别的线程进行处理,并等待他完成后再返回。另外,这种方法很危险,使用不当可能会造成死锁,毕竟是两个线程间的通信。

还有该方法被标记为 @hide,因为有一些隐患,所以该方法不希望被开发者使用,一般都用于 Framwork 层。

下面我们来分析一下代码:

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);
}

首先获取当前线程的 looper,在拿到 Handler 所属的looper,如果是同一个,就直接执行并返回 true,否则就继续往下走。

如果所属的 looper 不相同,则使用 BlockingRunnable 进行包装,并调用 postAndWait 方法:

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 {
            mTask.run();//运行在 handler 线程
        } finally {
            synchronized (this) {
                mDone = true; //标记完成
                notifyAll(); //唤醒线程
            }
        }
    }
    public boolean postAndWait(Handler handler, long timeout) {
        //使用 post 进行发送
        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;
    }
}

在 postAndWait 方法中,首先调用 post 添加到 queue 队列中,如果成功返回 true,如果发送失败,postAndWait 方法直接退出。

发送成功后,就会添加的队里中,等到合适的时候 run 方法就会执行,然后就会执行 finally 块,将 mDone 置为 true。

post() 方法执行成功后,就会进入 synchronized 代码块,需要注意的是 run 方法中也有一个 synchronized,这两个锁对象都是 this,所以说,同一时刻只能有一个代码块被执行,另一个只能进行等待。

接着就是 timeout 大于 0 并且 mDone 标志一直处于 false,则进行 wait 等待,等待结束后如果任务还没有完成,直接 return false,表示任务失败。

如果 timeout 小于0,则不需要延时,直接进行阻塞,没有超时时间,只能等待被唤醒。

最后 return true 表示任务成功。

梳理流程

1,首先判断目标线程和当前线程是否相同,相同则立即执行任务,return true。

2,接着就使用 BlockingRunnable 进行包装,然后使用 post 发送。发送失败表示目标线程的 Looper 有问题,直接 return false, 表示任务失败。

3,发送成功以后,会有两个分支,一个是 run 方法中的 synchronized,还有一个是 postAndWait 中的synchronized 。这两个在同一时刻只能有一个执行。run 方法中执行任务,postAndWait 中进行延时或者直接等待。

4,最后就是延时等待结束后任务没完成则表示任务失败,如果没有延时就直接进行 wait 进行阻塞,直到被唤醒。这里没有超时逻辑,会存在一定的问题。

存在的问题

通过上面的分析,我们大底可以分析出问题的关键了,具体如下所示:

  • 没有超时取消逻辑
  • 延时完成后,任务如果没有完成,直接回 return false,但是 Runable 依然在运行在目标线程的 MessageQueue 中,最终依然会得到执行,但是不会符合我们的预期

死锁

1,如果 Runable 在没有执行的时候被移除了,例如 Handler.removeCallBack,Looper.quit,这个任务就永远得不到执行,就会导致 wait 一直等待。

2,如果 wait 一直无法被唤醒, 并且这个时候还持有者别的锁,就会导致死锁。

那么要如何解决呢,上面第一种也无需解决,如果它不符合你的业务,你也就不需要使用它了,第二种只需要保证当前线程没有别的锁,而且 looper 不能直接退出,需要退出的时候也需要安全退出(quitSafely方法)。

总结

通过分析我们也可以看出来 runWithScissors 方法基本上不是偏向于业务的,而是偏向于 framwork 层的,因此该方法被标注为了 hide 方法。如果我们业务真的需要使用这个方法,我们也完全可以仿照源码自己写一个出来,并且还可以随意修改,岂不美滋滋。

以上就是Android Handler runWithScissors 梳理流程解析的详细内容,更多关于Android Handler runWithScissors 的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android Handler的使用详解

    在Android开发中,我们经常会遇到这样一种情况:在UI界面上进行某项操作后要执行一段很耗时的代码,比如我们在界面上点击了一个"下载"按钮,那么我们需要执行网络请求,这是一个耗时操作,因为不知道什么时候才能完成.为了保证不影响UI线程,所以我们会创建一个新的线程去执行我们的耗时的代码.当我们的耗时操作完成时,我们需要更新UI界面以告知用户操作完成了.所以我们可能会写出如下的代码: package ispring.com.testhandler; import android.app.

  • Android Handler使用案例详解

    什么是Handler? Handler可以发送和处理消息对象或Runnable对象,这些消息对象和Runnable对象与一个线程相关联.每个Handler的实例都关联了一个线程和线程的消息队列.当创建了一个Handler对象时,一个线程或消息队列同时也被创建,该Handler对象将发送和处理这些消息或Runnable对象. handler类有两种主要用途: 执行Runnable对象,还可以设置延迟. 两个线程之间发送消息,主要用来给主线程发送消息更新UI. 为什么要用Handler 解决多线程并

  • Android线程间通信Handler源码详解

    目录 前言 01. 用法 02.源码 03.结语 前言 在[Android]线程间通信 - Handler之使用篇主要讲了 Handler 的创建,发送消息,处理消息 三个步骤.那么接下来,我们也按照这三个步骤,从源码中去探析一下它们具体是如何实现的.本篇是关于创建源码的分析. 01. 用法 先回顾一下,在主线程和非主线程是如何创建 Handler 的. //主线程 private val mHandler: Handler = object : Handler(Looper.getMainLo

  • Android线程间通信 Handler使用详解

    目录 前言 01.定义 02.使用 第一步.创建 第二步.发送消息 第一种是 post(Runnable) 第二种是 sendMessage(Message) 第三步.处理消息 03.结语 前言 Handler,可谓是面试题中的一个霸主了.在我<面试回忆录>中,几乎没有哪家公司,在面试的时候是不问这个问题的.简单一点,问问使用流程,内存泄漏等问题.复杂一点,纠其源码细节和底层 epoll 机制来盘你.所以其重要性,不言而喻了吧. 那么今天让我们来揭开 Handler 的神秘面纱.为了读者轻松易

  • android studio的Handler简单实例代码

    实现:EditText输入消息,通过按钮选择发送给主线程或者子线程: 以下有效果图.MainActivity.java代码和activity_main.xml代码 效果图: MainActivity.java代码 package huan.san.handleroneapp; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle;

  • Android开发中Google为什么不让用Handler的runWithScissors()

    目录 一.序 二.Handler.runWithScissors() 2.1 runWithScissors() 2.2 Framework 中的使用 三.runWithScissors() 的问题 3.1 如果超时了,没有取消的逻辑 3.2 可能造成死锁 四.总结时刻 一.序 大家好,这里是承香墨影! runWithScissors() 是 Handler 的一个方法,被标记为 @hide,不允许普通开发者调用. 这个方法算是比较冷门,如果面试中被问及,面试者不知道时,通常面试官会换个问法:"

  • Android Handler runWithScissors 梳理流程解析

    目录 前言 runWithScissors 梳理流程 存在的问题 总结 前言 看 WMS 代码的时候看到了 Handler.runWithScissors 方法,所以来恶补一下 public static WindowManagerService main(final Context context, final InputManagerService im,final boolean showBootMsgs, final boolean onlyCore, WindowManagerPoli

  • Android okhttp的启动流程及源码解析

    前言 这篇文章主要讲解了okhttp的主要工作流程以及源码的解析. 什么是OKhttp 简单来说 OkHttp 就是一个客户端用来发送 HTTP 消息并对服务器的响应做出处理的应用层框架. 那么它有什么优点呢? 易使用.易扩展. 支持 HTTP/2 协议,允许对同一主机的所有请求共用同一个 socket 连接. 如果 HTTP/2 不可用, 使用连接池复用减少请求延迟. 支持 GZIP,减小了下载大小. 支持缓存处理,可以避免重复请求. 如果你的服务有多个 IP 地址,当第一次连接失败,OkHt

  • 全面解析Android系统指纹启动流程

    本章主要整理Android 指纹启动流程,侧重于hal和framework部分. 一.从Android系统启动流程看指纹启动流程 下图图片出处  → 第一阶段 Boot ROM,Android设备上电后,首先会从处理器片上ROM的启动引导代码开始执行,片上ROM会寻找Bootloader代码,并加载到内存.主要就是上电让系统启动. 第二阶段 Bootloader开始执行,首先负责完成硬件的初始化,然后找到Linux内核代码,并加载到内存. 启动过程中,bootloader(默认是bootable

  • Android Handler,Message,MessageQueue,Loper源码解析详解

    本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文< Android中Handler的使用>,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解. 概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制.我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功能.但

  • 详解Android Activity的启动流程

    前言 activity启动的流程分为两部分:一是在activity中通过startActivity(Intent intent)方法启动一个Activity:二是我们在桌面通过点击应用图标启动一个App然后显示Activity:第二种方式相较于第一种方式更加全面,所以本文会以第二种流程来分析. 简要 我们手机的桌面是一个叫做Launcher的Activity,它罗列了手机中的应用图标,图标中包含安装apk时解析的应用默认启动页等信息.在点击应用图标时,即将要启动的App和Launcher.AMS

  • Android中View绘制流程详细介绍

    创建Window Window即窗口,这个概念在AndroidFramework中的实现为android.view.Window这个抽象类,这个抽象类是对Android系统中的窗口的抽象.在介绍这个类之前,我们先来看看究竟什么是窗口呢? 实际上,窗口是一个宏观的思想,它是屏幕上用于绘制各种UI元素及响应用户输入事件的一个矩形区域.通常具备以下两个特点: 独立绘制,不与其它界面相互影响: 不会触发其它界面的输入事件: 在Android系统中,窗口是独占一个Surface实例的显示区域,每个窗口的S

  • 详解Android Handler机制和Looper Handler Message关系

    概述 我们就从以下六个问题来探讨Handler 机制和Looper.Handler.Message之前的关系? 1.一个线程有几个Handler? 2.一个线程有几个Looper?如何保证? 3.Handler内存泄漏原因?为什么其他的内部类没有说过这个问题? 4.为何主线程可以new Handler?如果在想要在子线程中new Handler 要做些什么准备? 5.子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用? 6.Looper死循环为什么不会导致应用卡死? 一.

  • 一款Android APK的结构构成解析

    目录 一. APK 组成解析 1.1 Apk 分析工具 1.2 Dex 知识点拓展 二. 构建源码导读 2.1 源码引入 2.2 BuildConfig Task 详解 2.3 获取所有 task 对应的类名 三.构建流程梳理 四.手动构建 APK 作者:hockeyli,腾讯 PCG 客户端开发工程师 一. APK 组成解析 在开始解析 Android 构建流程之前,我们先来看下构建的最终产物 APK 的整体组成: APK 主要由五个部分组成,分别是: Dex:.class 文件处理后的产物,

  • Android开发App启动流程与消息机制详解

    目录 引言 1.第一步了解 ThreadLocal 2.App的启动流程 3.Activity中创建Handler 引言 相信很多人对这个问题不陌生,但是大家回答的都比较简单,如谈到app启动流程有人就会是app的生命周期去了,谈到消息机制有人就会说looper循环消息进行分发,如果是面试可能面试官不会满意,今天我们搞一篇完善的源码解析来进行阐述上面的问题 1.第一步了解 ThreadLocal 什么是ThreadLocal呢,专业的来讲,ThreadLocal 是一个线程内部的数据存储类,通过

  • Android 10 启动Init进程解析

    目录 按下电源键时,android做了啥? init进程解析 FirstStageMain SetupSelinux SecondStageMain init.rc 解析 按下电源键时,android做了啥? 当我们按下电源键时,手机开始上电,并从地址0x00000000处开始执行,而这个地址通常是Bootloader程序的首地址. bootloader是一段裸机程序,是直接与硬件打交道的,其最终目的是“初始化并检测硬件设备,准备好软件环境,最后调用操作系统内核”.除此之外,bootloader

随机推荐