Android | Handler.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 进行阻塞,直到被唤醒。这里没有超时逻辑,会存在一定的问题。

存在的问题

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

  1. 没有超时取消逻辑 延时完成后,任务如果没有完成,直接回 return false,但是 Runable 依然在运行在目标线程的 MessageQueue 中,最终依然会得到执行,但是不会符合我们的预期
  2. 死锁 1,如果 Runable 在没有执行的时候被移除了,例如 Handler.removeCallBack,Looper.quit,这个任务就永远得不到执行,就会导致 wait 一直等待。 2,如果 wait 一直无法被唤醒, 并且这个时候还持有者别的锁,就会导致死锁。

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

总结

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

更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》

1.Android车载应用开发系统学习指南(附项目实战)

2.Android Framework学习指南,助力成为系统级开发高手

3.2024最新Android中高级面试题汇总+解析,告别零offer

4.企业级Android音视频开发学习路线+项目实战(附源码)

5.Android Jetpack从入门到精通,构建高质量UI界面

6.Flutter技术解析与实战,跨平台首要之选

7.Kotlin从入门到实战,全方面提升架构基础

8.高级Android插件化与组件化(含实战教程和源码)

9.Android 性能优化实战+360°全方面性能调优

10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
优化这段代码: public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); if (annotation != null) { if (this.isRepeatSubmit(request, annotation)) { AjaxResult ajaxResult = AjaxResult.error(annotation.message()); ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); return false; } } } boolean flag = false; TokenService tokenService = SpringUtils.getBean(TokenService.class); LoginAppUser loginUser = tokenService.getLoginUser(request); if (loginUser != null && loginUser.getGuest() != null && loginUser.getGuest().getGuestNum() != null) { tokenService.verifyToken(loginUser); String guestNum = loginUser.getGuest().getGuestNum(); HttpSession httpSession = request.getSession(); RedisCache redisUtil = SpringUtils.getBean(RedisCache.class); Guest guest = redisUtil.getCacheMapValue(RedisConstants.GUEST_MAP, guestNum); SysGuest sysGuest = JSON.parseObject(JSON.toJSONString(guest), SysGuest.class); if (sysGuest == null) { flag = false; httpSession.removeAttribute(Constants.SERVER_TYPE_APP); } else { if (httpSession.getAttribute(Constants.SERVER_TYPE_APP) == null) { httpSession.setAttribute(Constants.SERVER_TYPE_APP , sysGuest); } return true; } } //获取网名 也就是/login String servletPath = request.getServletPath(); if(servletPath.contains("/app/card/isCard") || servletPath.contains("/app/bank/signingBankCard") || servletPath.contains("/app/bank/sendMessage") || servletPath.contains("/app/bank/sendSysMessage") || servletPath.contains("/app/bank/login") || servletPath.contains("/app/card/guestIdAndPhone") || servletPath.contains("/app/family/isDevice") || servletPath.contains("/websocket/") || servletPath.contains("/upload/") || servletPath.contains("/app/deviceSocket/toSocket")) { flag = true; } if (!flag) { noLogin(response); } return flag; }
06-08

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值