温故知新:探究Android UI 绘制刷新流程

一、说明:

  1. 基于之前的了解知道ui的绘制最终会走到Android的ViewRootImplscheduleTraversals进行发送接收vsync信号绘制,在ViewRootImpl中还会进行主线程检测,也就是我们所谓子线程更新ui会抛出异常。

  2. 像我们常用的刷新ui,invalidate,requestLayout方法,(按我之前的理解在ViewRootImpl初始化添加后,在子线程中刷新ui一定会崩溃:如下图)

二、问题:invalidate一定会导致异常崩溃?

2.1、例子:子线程更新TextView文本(注意这里是TextView,为什么是它而不是ImageView,因为我的背景就是使用的TextView,使用它的时候发现了invalidate,requestLayout方法的区别 )

某天我在onResume中利用子线程更新了TextView的一段代码,发现并没有抛出异常崩溃,代码如下:

 override fun onResume() {
        super.onResume()
        mBind.btTest.setOnClickListener{
            lifecycleScope.launch(Dispatchers.IO) {
                mBind.btTest.text = "子线程点击改变:${Thread.currentThread().name}"
            }
        }
    }

我在想为什么呢?,看代码: 一步步debug:TextView控件中:

1.、
public final void setText(CharSequence text) {
    setText(text, mBufferType);
}
2.、
public void setText(CharSequence text, BufferType type) {
    setText(text, type, true, 0);

    if (mCharWrapper != null) {
        mCharWrapper.mChars = null;
    }
}
3.、
private void setText(CharSequence text, BufferType type,
                     boolean notifyBefore, int oldlen) {
     ...省略
    if (mLayout != null) {
        checkForRelayout();
    }
    ...省略
    
}

以上主要看第三步中的 checkForRelayout()检测是否需要重绘,方法如下

@UnsupportedAppUsage
private void checkForRelayout() {
    // If we have a fixed width, we can just swap in a new text layout
    // if the text height stays the same or if the view height is fixed.

    if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
            || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
            && (mHint == null || mHintLayout != null)
            && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
        // Static width, so try making a new text layout.

        int oldht = mLayout.getHeight();
        int want = mLayout.getWidth();
        int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();

        /*
         * No need to bring the text into view, since the size is not
         * changing (unless we do the requestLayout(), in which case it
         * will happen at measure).
         */
        makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
                      mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
                      false);
        
        //1.检测文本的显示类型,就是我们的过长省略号这种
        if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
            // In a fixed-height view, so use our new text layout.
            if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
                    && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
                autoSizeText();
                invalidate();
                return;
            }

            // Dynamic height, but height has stayed the same,
            // so use our new text layout.
            if (mLayout.getHeight() == oldht
                    && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
                autoSizeText();
                invalidate();
                return;
            }
        }

        // We lose: the height has changed and we have a dynamic height.
        // Request a new view layout using our new text layout.
        requestLayout();
        invalidate();
    } else {
        // Dynamic width, so we have no choice but to request a new
        // view layout with a new text layout.
        nullLayouts();
        requestLayout();
        invalidate();
    }
}

从上面的checkForRelayout()方法中的if (mEllipsize != TextUtils.TruncateAt.MARQUEE)条件知道成立,因为我们没有设置过mEllipsize = 跑马灯效果,所以走了invalidate()方法然后直接return截断,不会走后面的requestLayout()方法,至于requestLayout() 与 invalidate()的区别我就不讲了

2.2、分析requestLayout方法

基于之前的知识我知道调用requestLayout()方法会崩溃,至于为什么调用requestLayout()方法会崩溃?

我们先看requestLayout()方法,暂停一会invalidate()的跟进

requestLayout()方法代码如下:

public void requestLayout() {
    if (mMeasureCache != null) mMeasureCache.clear();

    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        // Only trigger request-during-layout logic if this is the view requesting it,
        // not the views in its parent hierarchy
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null && viewRoot.isInLayout()) {
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }

    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

requestLayout()方法中会循环递归调用 mParent.requestLayout()方法,直到找到ViewRootImpl中的requestLayout()方法,而它的方法做了线程检测如下图:这就是requestLayout()方法会崩溃的原因。

验证猜想:TextView设置跑马灯属性,使上面的if (mEllipsize != TextUtils.TruncateAt.MARQUEE)不成立,走下面的requestLayout()方法,代码如下:

 override fun onResume() {
        super.onResume()
        mBind.btTest.ellipsize = TextUtils.TruncateAt.valueOf("MARQUEE")
        mBind.btTest.setOnClickListener{
            lifecycleScope.launch(Dispatchers.IO) {
                mBind.btTest.text = "子线程点击改变:${Thread.currentThread().name}"
            }
        }
    }

果然点击后崩溃:

2.3、继续分析invalidate()方法,为什么不会导致textview的更新崩溃

看代码在View.java文件中

public void invalidate() {
    invalidate(true);
}

public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
    if (mGhostView != null) {
        mGhostView.invalidate(true);
        return;
    }

    if (skipInvalidate()) {
        return;
    }

    // Reset content capture caches
    mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
    mContentCaptureSessionCached = false;

    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
            || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
            || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
            || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
        if (fullInvalidate) {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~PFLAG_DRAWN;
        }

        mPrivateFlags |= PFLAG_DIRTY;

        if (invalidateCache) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        // Propagate the damage rectangle to the parent view.
        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            p.invalidateChild(this, damage);
        }

        // Damage the entire projection receiver, if necessary.
        if (mBackground != null && mBackground.isProjected()) {
            final View receiver = getProjectionReceiver();
            if (receiver != null) {
                receiver.damageInParent();
            }
        }
    }
}

核心代码是上面第三段invalidateInternal方法中的invalidateChild方法

它回调到ViewGroup中的invalidateChild方法

看:invalidateChild如下图:我们知道

if (attachInfo != null && attachInfo.mHardwareAccelerated) 条件成立attachInfo不为空 ,且硬件加速被开启(从API 14 (3.0)起。硬件加速默认开启)。 attachInfo 是一个view在attach至其父window被赋值的一系列信息。

所以条件成立后走的onDescendantInvalidated方法 如下:

@CallSuper
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
    /*
     * HW-only, Rect-ignoring damage codepath
     *
     * We don't deal with rectangles here, since RenderThread native code computes damage for
     * everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
     */

    // if set, combine the animation flag into the parent
    mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);

    if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
        // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
        // optimization in provides in a DisplayList world.
        mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;

        // simplified invalidateChildInParent behavior: clear cache validity to be safe...
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
    }

    // ... and mark inval if in software layer that needs to repaint (hw handled in native)
    if (mLayerType == LAYER_TYPE_SOFTWARE) {
        // Layered parents should be invalidated. Escalate to a full invalidate (and note that
        // we do this after consuming any relevant flags from the originating descendant)
        mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
        target = this;
    }

    if (mParent != null) {
        mParent.onDescendantInvalidated(this, target);
    }
}

上面一段代码核心是 mParent.onDescendantInvalidated(this, target); 类似于requestLayout()方法 onDescendantInvalidated中会循环递归调用 mParent.onDescendantInvalidated(this, target);方法,直到找到ViewRootImpl中的onDescendantInvalidated(this, target)方法,而它的方法没做线程检测如下图:这就是开了硬件加速后invalidate方法不会崩溃的原因。如下图:直接走scheduleTraversals绘制刷新有兴趣可看:

而关闭硬件加速后会怎样呢? 继续看invalidateChild方法

@Deprecated
@Override
public final void invalidateChild(View child, final Rect dirty) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null && attachInfo.mHardwareAccelerated) {
        // HW accelerated fast path
        onDescendantInvalidated(child, child);
        return;
    }

    ViewParent parent = this;
    if (attachInfo != null) {
     
       ...

        do {
             ....
            parent = parent.invalidateChildInParent(location, dirty);
               
            ....
        } while (parent != null);
    }
}

上面一段核心是 parent = parent.invalidateChildInParent(location, dirty);方法 同理while循环不停调用 invalidateChildInParent方法直到找到ViewRootImpl中的invalidateChildInParent(int[] location, Rect dirty)方法,如下图内部进行了线程检测

**验证猜想关闭硬件加速:android:hardwareAccelerated="false"**果然崩溃了。

三、总结

这就是我遇到的问题:单纯的根据TextView在子线程可以更新得出的结论,总的来说要想不崩溃还得绕过ViewRootImpl中的checkThread的检测。至于研究它有什么用,只有知道理解源码的流程,才能写出更好的东西。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/126929.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

华为ipsec vpn双链路主备备份配置案例

配置就是这配置&#xff0c;意外是完成后不通&#xff0c;待以后处理&#xff01; FW_A配置&#xff1a; dhcp enable ip-link check enable ip-link name check_b destination 2.2.2.2 interface GigabitEthernet1/0/0 mode icmp next-hop 202.38.163.2 acl number 3000 rul…

新方向!文心一言X具身智能,用LLM大模型驱动智能小车

具身智能已成为近年来研究的热点领域之一。具身智能强调将智能体与实体环境相结合&#xff0c;通过智能体与环境的交互&#xff0c;来感知和理解世界&#xff0c;最终实现在真实环境中的自主决策和运动控制。 如何基于文心大模型&#xff0c;低成本入门“具身智能”&#xff0…

振南技术干货集:C语言的一些“骚操作”及其深层理解(2)

注解目录 第二章《c语言的一些“操作”及其深层理解》 一、字符串的实质就是指针 &#xff08;如何将 35 转为对应的十六进制字符串”0X23”&#xff1f;&#xff09; 二 、转义符\ &#xff08;打入字符串内部的“奸细”。&#xff09; 三、字符串常量的连接 &#xff…

JAVA基础1:Java概述

1.JAVA语言 语言&#xff1a;人与人交流沟通的表达方式 计算机语言&#xff1a;人与计算机之间进行信息交流沟通的一种特殊语言 JAVA语言是美国Sun公司在1995年推出的计算机语言 JAVA之父&#xff1a;詹姆斯高斯林 2.JAVA语言跨平台原理 跨平台&#xff1a;JAVA程序可以在…

Yolov5 + 界面PyQt5 +.exe文件部署运行

介绍 Yolov5是一种基于深度学习的目标检测算法&#xff0c;PyQt5是一个Python编写的GUI框架&#xff0c;用于创建交互式界面。在部署和运行Yolov5模型时&#xff0c;结合PyQt5可以方便地创建一个用户友好的界面&#xff0c;并将代码打包为.exe文件以供其他人使用。 下面是一个…

学者观察 | 联邦学习与区块链、大模型等新技术的融合与挑战-北京航空航天大学童咏昕

导语 当下&#xff0c;数据已成为经济社会发展中不可或缺的生产要素&#xff0c;正在发挥越来越大的价值。但是在数据使用过程中&#xff0c;由于隐私、合规或者无法完全信任合作方等原因&#xff0c;数据的拥有者并不希望彻底和他方共享数据。为解决原始数据自主可控与数据跨…

新登录接口独立版变现宝升级版知识付费小程序-多领域素材资源知识变现营销系统

源码简介&#xff1a; 资源入口 点击进入 源码亲测无bug&#xff0c;含前后端源码&#xff0c;非线传&#xff0c;修复最新登录接口 梦想贩卖机升级版&#xff0c;变现宝吸取了资源变现类产品的很多优点&#xff0c;摒弃了那些无关紧要的东西&#xff0c;使本产品在运营和变现…

线上SQL超时场景分析-MySQL超时之间隙锁 | 京东物流技术团队

前言 之前遇到过一个由MySQL间隙锁引发线上sql执行超时的场景&#xff0c;记录一下。 背景说明 分布式事务消息表&#xff1a;业务上使用消息表的方式&#xff0c;依赖本地事务&#xff0c;实现了一套分布式事务方案 消息表名&#xff1a;mq_messages 数据量&#xff1a;3…

【LeetCode:2300. 咒语和药水的成功对数 | 二分】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

“三大阶段稳定性测试”筑牢长安链信任基石

前言 随着长安链应用生态的不断丰富、面对的应用场景更加多元&#xff0c;稳定性测试在长安链测试流程中占到越来越重要的位置。本文将介绍长安链稳定性测试的发展历程及如何通过三大阶段稳定性测试应对不断出现的复杂的商业需求&#xff0c;筑牢长安链信任基石。 功能测试和…

IDEA 设置 Git 在左侧展示

File->settings->Version Control->commit 勾选 Use non-model commit interface

AI:77-基于深度学习的工业缺陷检测

🚀 本文选自专栏:人工智能领域200例教程专栏 《人工智能领域200例教程专栏》从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,通过本专栏案例和项目实践,都有参考学习意义。每篇案例都包含代码实例,详细讲解供大家学习。 ✨✨✨ 每一个案例都附带有代码,在本…

MySQL -- 视图

MySQL – 视图 文章目录 MySQL -- 视图一、基本使用二、视图规则和限制 视图是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一样&#xff0c;视图包含一系列带有名称的列和行数据。视图的数据变化会影响到基表&#xff0c;基表的数据变化也会影响到视图。 一、基本使用…

更新 | Apinto 网关 V0.15 版本发布!

该插件支持对后端服务返回的响应信息进行过滤&#xff0c;包括响应头部、响应体。过滤的响应体字段或响应头将会被移除&#xff0c;不会返回给客户端&#xff0c;从而避免敏感信息的泄漏。 若此时上游服务返回的响应体为&#xff1a; {"code":0,"data":{…

Redis-命令操作Redis

目录 一.Redis简介 二.Redis安装 2.1.Linux安装 2.2.Windows安装 三.Redis常用命令 3.1 Redis字符串 3.2 Redis哈希(Hash) 3.3 Redis列表&#xff08;List&#xff09; 3.4 Redis集合&#xff08;Set&#xff09; 好啦今天就到这里了&#xff01;&#xff01;希望能帮…

如何将系统盘MBR转GPT?无损教程分享!

什么是MBR和GPT&#xff1f; MBR和GPT是磁盘的两种分区形式&#xff1a;MBR&#xff08;主引导记录&#xff09;和GPT&#xff08;GUID分区表&#xff09;。 新硬盘不能直接用来保存数据。使用前应将其初始化为MBR或GPT分区形式。但是&#xff0c;如果您在MBR时需…

软件工程一些图的画法

软件工程一些图的画法 【一】数据库设计&#xff1a;ER图【1】ER图简介【2】实体之间的关系【3】ER图绘制常见问题【4】ER图转关系模式 【二】流程图【1】流程图的作用【2】流程图中使用的符号【3】三种循环的流程图画法【4】流程图的基本结构【5】流程图常用的形式 【一】数据…

CCF ChinaSoft 2023 论坛巡礼 | 云计算标准化论坛

2023年CCF中国软件大会&#xff08;CCF ChinaSoft 2023&#xff09;由CCF主办&#xff0c;CCF系统软件专委会、形式化方法专委会、软件工程专委会以及复旦大学联合承办&#xff0c;将于2023年12月1-3日在上海国际会议中心举行。 本次大会主题是“智能化软件创新推动数字经济与社…

“位不配财”?程序员兼职,稳妥挣钱才是王道!

一、配不上 戏称程序员为“码农”&#xff0c;一年到头&#xff0c;像那地里的老黄牛和勤勤恳恳的老农民。 又像极了那工地上的农民工&#xff0c;天天搬砖&#xff0c;苦得嘞。 作为推动时代进步的得力干将&#xff0c;工作量自然是不容小觑。说程序员不加班都没人信&#x…

什么样的CRM系统更适合外贸企业?

外贸CRM系统作为外贸客户关系管理的工具&#xff0c;已经成为了当下外贸企业对外贸易过程中不可或缺的一环。那什么样的CRM系统更适合外贸企业&#xff1f;小Z向您推荐Zoho CRM。下面说说它到底有什么好处和作用。 一、搭建更高效的客户关系管理系统 外贸企业从前期推广、开发…
最新文章