Handler机制中同步屏障原理及结合实际问题分析

前言

       本人是一个毕业两年的智能座舱车载应用工作者,在这里我将分享一个关于Handler同步屏障导致的bug,我和我的同事遇到了一个应用不响应Handler消息的bug,bug的现象为有Touch事件,但没有界面的view却没有任何响应。当时项目组拉了fwk和驱动的同事,从屏幕驱动到fwk的事件分发,甚至卡顿及内存泄漏都做了分析,唯独大家没有考虑到从Handler的消息机制切入。后面排除到是和Handler的同步屏障有关,最终才解决此bug,此篇文章将解释Handler的同步屏障机制及此bug的原因。

Handler同步屏障机制

Handler消息分为以下三种:

1.同步消息

2.异步消息

3.同步屏障(其实更像一个机制的开关)

       其实在没有开启同步屏障的情况下,Handler对同步消息和异步消息的响应是没有太大区别的,都是通过Looper轮询MessageQueue中的消息然后传递给对应的Handler去处理,其中会按照Message的需要响应时间去决定其插入到链表中的位置,如果时间较早就会插在前面。(在此笔者不赘述过多关于Handler消息机制的内容,网上文章很多)但如果开启了同步屏障,Handler会优先处理异步消息,不响应同步消息,直到同步屏障关闭。

Handler同步屏障开启后的队列消息运作机制

       我们知道在MessageQueue队列中,Message是按照延时时间的长短决定其在链表中的位置的。但是当我们打开了同步屏障之后,MessageQueue在消息出队的时候会优先出异步消息,绕开同步消息。具体如源码所示。

synchronized (this) {
    // Try to retrieve the next message.  Return if found.
    final long now = SystemClock.uptimeMillis();
    Message prevMsg = null;
    Message msg = mMessages;
    if (msg != null && msg.target == null) {
        // Stalled by a barrier.  Find the next asynchronous message in the queue.
        //可以看到当队列中有消息屏障的时候,会优先处理异步消息,绕开同步消息
        do {
            prevMsg = msg;
            msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());
    }

       如下是同步屏障开启以及开启后消息出队的一个流程图(其中两个异步消息是绘图表达有误,并非代表一起出列时候的状态)

遭遇bug原因

       回到前言中提及的bug,其实由于在app在非主线程中去做了更新UI的操作,而这个操作没有做主线程校验,所以也没有抛出Only the original thread that created a view hierarchy can touch its views.在app中具体是调用了ViewRootImpl的如下方法

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
//此方法没有加锁,是个线程不安全的方法
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

       如以上代码所示,这里是没有加同步锁的方法,app又是通过子线程去调用了此线程不安全的方法,导致插入了多个同步屏障,在移除的时候有没有将所有同步屏障消息移除,导致后来的同步消息全部不会出队,Handler也不会去处理这些消息,app的界面更新以及很多组件之间的通讯都是依赖Handler来处理,就导致整个app的现象是不论怎么触摸,都不会有界面更新,但通过系统日志又能看到触摸事件的日志。

void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        //移除消息屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        //移除消息屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

抽象出来的流程图如下图所示:

       其实这里又回到了一个线程安全的问题,这个问题也是Andorid设计的时候要在UI线程(主线程)中更新UI的原因,保证线程的同步更新UI。最后通过排除app中的子线程更新UI代码段将此bug解决。

总结

1.Handler的同步屏障消息会让队列中的异步消息优先处理,同步消息被屏蔽。

2.结合笔者遇到的bug,大家其实要注意平时编写app代码时对UI的更新一定要放到主线程,保证线程的同步。

3.这段分析和经历不仅仅是博客中记录的原理,更多是拓宽了笔者解决问题的思维,我们总是说要去读源码,其实读懂只是帮助我们理解和避开写出bug,更多的我们应该学习里面的设计思维运用到实际开发中去。

本博客是笔者人生第一篇博客若有不对,请大家指出,本人虚心接受,有指导才有进步!

参考博客:https://blog.csdn.net/leihu007/article/details/126190777

感谢以上链接博客的作者,本博客有参考上面链接博客,同时也感谢公司同事的分享和分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值