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在消息出队的时候会优先出异步消息,绕开同步消息。具体如源码所示。

java 复制代码
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的如下方法。

java 复制代码
@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的现象是不论怎么触摸,都不会有界面更新,但通过系统日志又能看到触摸事件的日志。

java 复制代码
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,更多的我们应该学习里面的设计思维运用到实际开发中去。

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

参考博客:blog.csdn.net/leihu007/ar...

感谢以上链接博客的作者,本博客有参考上面链接博客

相关推荐
CYRUS_STUDIO1 小时前
利用 Linux 信号机制(SIGTRAP)实现 Android 下的反调试
android·安全·逆向
CYRUS_STUDIO2 小时前
Android 反调试攻防实战:多重检测手段解析与内核级绕过方案
android·操作系统·逆向
黄林晴5 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我6 小时前
flutter 之真手势冲突处理
android·flutter
法的空间6 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止6 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭6 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
jctech6 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831676 小时前
为何Handler的postDelayed不适合精准定时任务?
android
叽哥7 小时前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin