【Android】为什么在子线程中更新UI不会抛出异常

news2024/12/28 5:34:22

转载请注明来源:https://blog.csdn.net/devnn/article/details/135638486

前言

众所周知,Android App在子线程中是不允许更新UI的,否则会抛出异常:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

详细异常信息见下图
在这里插入图片描述
View的绘制是在ViewRootImpl中(关于view的绘制流程不是本文重点):

//ViewRootImpl.java
 @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        //省略无关代码
     }

  @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

问题

笔者偶然发现在协程中是可以更新UI的,比如在Activity的onCreate有以下一段代码:

  lifecycleScope.launchWhenResumed {
        withContext(Dispatchers.IO) {
        Log.i("MainActivity", "launchWhenResumed,threadId:${Thread.currentThread().id}")//threadId打印233
        binding.demo1.text="NEW"
 	}
}

这其实已经是在子线程中更新UI,为什么不会抛出异常呢?难道是协程检测到是UI操作自动帮我们切换到了主线程?经过笔者上一篇文章对协程的字节码分析,排除了这种可能。

【Kotlin】协程的字节码原理

难道是页面还没有开始绘制,还没有调用ViewRootImpl.checkThread()代码吗?那让子线程更新UI操作之前先休眠等待一段时间呢?

  lifecycleScope.launchWhenResumed {
        withContext(Dispatchers.IO) {
        Log.i("MainActivity", "launchWhenResumed,threadId:${Thread.currentThread().id}")//threadId打印233
        Thread.sleep(10000)
        binding.demo1.text="NEW"
 	}
}

经验证也是没有抛出异常。这就有点匪夷所思了!

另外,改用直接使用Thread创建子线程也是同样不会抛异常:

 Thread {
     Thread.sleep(10000)
     val button1 = binding.demo1.text="NEW"
 }.start()

这也验证了跟协程是没有关系的。

那只有从源码中寻找答案。

setText流程

看看TextViewsetText方法的源码。
public void setText(CharSequence text)方法内部会调到以下4个参数的重载方法。

//TextView.java
  private void setText(CharSequence text, BufferType type,
                         boolean notifyBefore, int oldlen) {
       //省略无关代码
      if (mLayout != null) {
            checkForRelayout();
       }    
      //省略无关代码           
   }

checkForRelayout方法用来判断是调用invalidate还是requestLayout来更新UI。

//TextView.java
 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);

            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方法会发现,当TextView的宽高是写死的,或者宽高跟之前没有变化,那么就调invalidate(),否则调用requestLayout。

笔者经过断点验证,发现在协程中调用的setText方法内部走到以下if语句内部然后return了,这说明宽高没有变化,调用了invalidate()方法来更新UI。

//TextView.java
    if (mLayout.getHeight() == oldht
                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
                    autoSizeText();
                    invalidate();
                    return;
                }

invalildate方法会调用到parent的invalidateChild方法:

//ViewGroup.java
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;
        }
        //省略无关代码
    }

可以发现,当attachInfo非空并且开启了硬件加速,那么就走onDescendantInvalidated流程。View的onDescendantInvalidated方法最终会递归到ViewRootImplonDescendantInvalidated方法:

//ViewRootImpl.java
   @Override
    public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
        // TODO: Re-enable after camera is fixed or consider targetSdk checking this
        // checkThread();
        if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
            mIsAnimating = true;
        }
        invalidate();
    }

    @UnsupportedAppUsage
    void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            scheduleTraversals();
        }
    }

ViewRootImpl的onDescendantInvalidated方法直接调用了invalidate并没有调用checkThread方法。

硬件加速默认是开启了,可以使用view的isHardwareAccelerated方法判断是否开启:

 lifecycleScope.launchWhenResumed {
 		withContext(Dispatchers.IO) {
 		Thread.sleep(10000)
		binding.demo1.text="NEW"
		Log.i("MainActivity", "demo1.isHardwareAccelerated:${binding.demo1.isHardwareAccelerated}")
	}
}

当给Application配置关闭硬件加速后: android:hardwareAccelerated="false"
以上代码正如所料抛出了异常。

结论

经过以上分析,当使用invalidate更新UI并且开启了硬件加速,那么是可以在子线程中更新UI的。

还有一种情况就是DecorView还没有添加到Window中(相当于ViewTree还没有渲染)的情况下,在子线程中也是可以更新UI的,但是更新不会立即生效,因为这个时候ViewRootImpl还没有创建,比如在onCreate中开启子线程立即更新UI。

转载请注明来源:https://blog.csdn.net/devnn/article/details/135638486

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智慧灌区解决方案:针对典型灌区水利管理需求

​随着国家对农业水利的重视,各地积极推进智慧灌区建设,以实现对水资源的精准调度和科学化管理。下面我们针对典型灌区水利管理需求,推荐智慧灌区解决方案。 一、方案构成智慧水利解决方案- 智慧水利信息化系统-智慧水利平台-智慧水利公司 - 星创智慧水利 一、方案构成 (一)水…

安全加速SCDN是什么

安全加速SCDN(Secure Content Delivery Network,SCDN) 是集分布式DDoS防护、CC防护、WAF防护、BOT行为分析为一体的安全加速解决方案。已使用内容分发网络(CDN)或全站加速网络(ECDN)的用户&…

Java CAS原子操作过程及ABA问题

目录 一.什么是CAS 二.流程 三.缺点 四.ABA 问题 五.解决ABA问题 一.什么是CAS CAS(Compare And Swap,比较并交换),通常指的是这样一种原子操作:针对一个变量,首先比较它的内存值与某个期望值是否相同…

边缘计算AI智能分析网关V4客流统计算法的概述

客流量统计AI算法是一种基于人工智能技术的数据分析方法,通过机器学习、深度学习等算法,实现对客流量的实时监测和统计。该算法主要基于机器学习和计算机视觉技术,其基本流程包括图像采集、图像预处理、目标检测、目标跟踪和客流量统计等步骤…

EasyDarwin计划新增将各种流协议(RTSP、RTMP、HTTP、TCP、UDP)、文件转推RTMP到其他视频直播平台,支持转码H.264、文件直播推送

之前我们尝试做过EasyRTSPLive(将RTSP流转推RTMP)和EasyRTMPLive(将各种RTSP/RTMP/HTTP/UDP流转推RTMP,这两个服务在市场上都得到了比较多的好评,其中: 1、EasyRTSPLive用的是EasyRTSPClient取流&#xff…

Presents-codeforces

题目链接:Problem - 136A - Codeforces 解题思路: 这题挺有意思,大致意思是,每个人都会互相送礼物,可能送给自己,可能送给别人,第i个数表示第i个人要把礼物送给第i个数的人比如1 3 2&#xff0…

C++系列-第1章顺序结构-9-字符类型char

在线练习: http://noi.openjudge.cn/ https://www.luogu.com.cn/ 总结 本文是C系列博客,主要讲述字符类型char 字符类型char 在C编程语言中,char是一种基本的数据类型,它用于存储单个字符。字符可以是字母、数字、标点符号或者…

智慧门店:如何利用AI视频智能监管与存储技术让门店降本增效?

一、行业背景 TSINGSEE青犀视频智慧门店解决方案是一种集成了人工智能、大数据、物联网等技术的零售解决方案,目的是提高门店的运营效率、用户体验和业绩。随着数字化转型的加速,连锁门店需要跟上时代的步伐,需要利用数字化手段提高运营效率…

Android14之DefaultKeyedVector实现(一百八十二)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…

打造更智能的应用 - 机器学习和Andorid

打造更智能的应用 - 机器学习和Andorid 一、关于机器学习和Andorid二、使用 Gemini 让您的 Android 应用如虎添翼2.1 Gemini API2.2 Android AICore 三、现成可用的还是自定义的机器学习3.1 机器学习套件 SDK 的常见用户流3.2 高性能自定义机器学习 四、机器学习套件 SDK&#…

GIt与IDEA结合,多人操作gitee仓库

提交到本地 push到gitee远程仓库 在做这些之前注意配置git要忽略上传的文件,一般上传代码只上传src和pom.xml即可 在c盘用户里放置 git.ignore # Compiled class file *.class# Log file *.log# BlueJ files *.ctxt# Mobile Tools for Java (J2ME) .mtj.tmp/# Package Files …

python实现截图识别文字v2.0[脱离开发环境]

目录 1、简介 2、如何使用 3、完整代码 4、免费下载 5、说明文档 🍃作者介绍:双非本科大三网络工程专业在读,阿里云专家博主,专注于Java领域学习,擅长web应用开发、数据结构和算法,初步涉猎Python人工…

element-ui 打包流程源码解析(下)

目录 目录结构和使用1,npm 安装1.1,完整引入1.2,按需引入 2,CDN3,国际化 接上文:element-ui 打包流程源码解析(上) 文章中提到的【上文】都指它 ↑ 目录结构和使用 我们从使用方式来…

简单高效学习 LaTeX 007 - LaTex Format Control 科学排版之格式控制

这一集的视频演示了如何在LaTeX中进行排版的格式控制: https://www.douyin.com/user/self?modal_id7303925716830211379&showTabpost

ubuntu系统 vscode 配置c/c++调试环境

文章目录 1.安装插件2.目录结构3.cmake tools配置 1.安装插件 c/c插件 cmake cmake tools插件 2.目录结构 . ├── build ├── CMakeLists.txt ├── demo │ └── main.cpp ├── image.png ├── src │ ├── add.cpp │ └── add.hpp └── vsdebug.…

Debian 11.8.0 安装图解

引导和开始安装 这里直接回车确认即可,选择图形化安装方式。 选择语言 这里要区分一下,当前选中的语言作为安装过程中安装器所使用的语言,这里我们选择中文简体。不过细心的同学可能发现,当你选择安装器语言之后,后续安…

汽车用螺纹紧固件的拧紧力矩规范主要考虑哪些方面——SunTorque智能扭矩系统

在汽车制造过程中,螺纹紧固件是连接和固定各个零部件的重要元件。为了保证汽车的可靠性和安全性,对于螺纹紧固件的拧紧力矩有着严格的规定和规范。SunTorque智能扭矩系统和大家一起掌握这一重要知识点。 拧紧力矩是指将螺纹紧固件拧紧到预定位置所需的力…

最小公倍数之和(莫比乌斯反演P3911)

路径&#xff1a; P3911 最小公倍数之和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路&#xff1a; 代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include<cstring> #include<cmath> #include<…

新上线一个IT公司微信小程序

项目介绍 项目背景: 一家IT公司,业务包含以下六大块: 1、IT设备回收 2、IT设备租赁 3、IT设备销售 4、IT设备维修 5、IT外包 6、IT软件开发 通过小程序,提供在线下单,在线制单,在线销售,业务介绍,推广,会员 项目目的: 业务介绍: 包含企业业务介绍 客户需…

蓝桥杯(C++ 整数删除 优先队列 )

优先队列&#xff1a; 优先队列具有队列的所有特性&#xff0c;包括队列的基本操作&#xff0c;只是在这基础上添加了内部的一个排序&#xff0c;它本质是一个堆实现的。 1.头文件&定义 #include <queue> #include <functional> //greater<>// 定义 p…