Android Handler runWithScissors 梳理流程解析

作者:345丶 时间:2023-01-29 11:51:27 

前言

看 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方法)。

来源:https://juejin.cn/post/7156821621557166087

标签:Android,Handler,runWithScissors
0
投稿

猜你喜欢

  • C# 设计模式系列教程-状态模式

    2022-11-07 13:31:55
  • SpringBoot用配置影响Bean加载@ConditionalOnProperty

    2022-11-28 22:16:07
  • C# 根据字符串生成二维码的实例代码

    2023-09-16 09:06:50
  • 详解如何在Java中实现堆排序算法

    2023-11-11 11:34:46
  • java之使用多线程代替for循环(解决主线程提前结束问题)

    2021-11-21 01:23:55
  • SpringBoot 配置文件加密的步骤

    2023-10-23 02:55:55
  • 解决Jenkins集成SonarQube遇到的报错问题

    2023-11-24 08:54:10
  • spring boot中多线程开发的注意事项总结

    2022-03-14 19:20:07
  • Java中包装类介绍与其注意事项

    2023-03-20 18:26:36
  • c#读写注册表示例分享

    2022-06-26 13:08:45
  • 详谈jvm--Java中init和clinit的区别

    2022-01-10 10:35:22
  • Spring MVC注解式开发使用详解

    2021-07-08 23:27:50
  • Android自定义横向滑动菜单的实现

    2022-08-26 03:57:14
  • SpringMVC中@ModelAttribute与@RequestBody的区别及说明

    2023-11-24 12:09:51
  • Android P实现静默安装的方法示例(官方Demo)

    2022-04-05 20:06:13
  • SpringBoot整合screw实现数据库文档自动生成的示例代码

    2023-11-29 05:30:15
  • Android编程使用LinearLayout和PullRefreshView实现上下翻页功能的方法

    2023-06-30 02:16:15
  • Android条目拖拽删除功能实例代码

    2022-06-19 22:47:37
  • springboot创建线程池的两种方式小结

    2022-07-12 09:27:19
  • C# WPF实现的语音播放自定义控件

    2022-11-23 19:45:49
  • asp之家 软件编程 m.aspxhome.com