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...

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

相关推荐
teacher伟大光荣且正确8 小时前
Qt Creator 配置 Android 编译环境
android·开发语言·qt
飞猿_SIR10 小时前
Android Exoplayer 实现多个音视频文件混合播放以及音轨切换
android·音视频
HumoChen9911 小时前
GZip+Base64压缩字符串在ios上解压报错问题解决(安卓、PC模拟器正常)
android·小程序·uniapp·base64·gzip
沙振宇15 小时前
【HarmonyOS】ArkTS开发应用的横竖屏切换
android·华为·harmonyos
橙子1991101617 小时前
Kotlin 中的作用域函数
android·开发语言·kotlin
zimoyin17 小时前
Kotlin 懒初始化值
android·开发语言·kotlin
枣伊吕波17 小时前
第六节第二部分:抽象类的应用-模板方法设计模式
android·java·设计模式
萧然CS18 小时前
使用ADB命令操作Android的apk/aab包
android·adb
_extraordinary_1 天前
MySQL 事务(二)
android·数据库·mysql
鸿蒙布道师1 天前
鸿蒙NEXT开发动画案例5
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei