• Android 子线程为什么不能更新UI?


    Android 应用的 UI 是在主线程上进行绘制和更新的。

    当我们在子线程中直接进行 UI 更新时,会导致以下问题:

    1. 线程安全问题:多个线程同时操作 UI,可能导致 UI 组件的状态不一致或者出现竞争条件。
    2. 卡顿和 ANR:如果在主线程中执行耗时操作,会导致主线程被阻塞,用户界面无法响应用户的输入,甚至可能发生 ANR(Application Not Responding)错误。

     移步:子线程怎么切换主线程? 

    (一)现象
    在子线程中直接更新UI就会crash,报错如下:
    android.view.ViewRootImpl$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.
    只有创建了视图层次结构的原始线程才能访问它的视图。
    也就是说,ui在哪个线程创建的,就应该在哪个线程中更新。而UI是在主线程中创建的,所以就应该在主线程中更新UI。这是crash的信息提示。

    (二)原因
    首先要明白UI更新的原理,就是通过不断的测量、布局和绘制的过程。最终都会请求view的绘制操作,即requestLayout()这个方法。

    1. View.java:
    2. public void requestLayout() {
    3. //当父视图不为空且有请求布局时,执行父视图的requestLayout()方法
    4. if (mParent!=null && !mParent.isLayoutRequested()){
    5. mParent.requestLayout();
    6. }
    7. }
    8. protected ViewParent mParent; //当前视图的父视图
    9. //父视图的赋值方法
    10. void assignParent(ViewParent parent) {
    11. if (mParentnull) {
    12. mParent = parent;
    13. } else if (parentnull) {
    14. mParent = null;
    15. } else {
    16. throw new RuntimeException(“view " + this + " being added, but it already has a parent”);
    17. }
    18. }


    从源码可以看出,当请求布局时,会调用父视图mParent的requestLayout()方法,mParent是一个接口对象,requestLayout()方法是接口方法。所以需要找到实现这个接口的类。
    可以发现,在assignParent(ViewParent parent)方法中对mParent进行了赋值,那么assignParent(ViewParent parent)在哪里被调用的呢?
    通过阅读Activity的启动过程源码,可以发现:

    (1)在Activity的onCreate()方法中调用了setContentView()方法,其实最终调用了顶层视图PhoneWindow的setContentView()方法,然后调用其内部的installDecor()初始化mDecor;
    (2)紧跟着在handleLaunchActivity()中调用了handleResumeActivity();
    (3)然后在handleResumeActivity()中可以发现调用了WindowManagerImpl的addView方法,wm.addView(decor, l)
    (4)然后又调用了WindowManagerGlobal类的addView()方法,在里面可以发现又调用了root.setView(view, wparams, panelParentView),这个root是ViewRootImpl一个实例。
    然后直接看ViewRootImpl的setView()方法:

    1. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    2. synchronized (this) {
    3. if (mView == null) {
    4. //…
    5. view.assignParent(this);
    6. //…
    7. }
    8. }
    9. }

    由此可见,最终在ViewRootImpl的setView()方法中对mParent进行了赋值。同时也指明了mParent是ViewRootImpl的一个实例,实现了ViewParent接口,所以很显然了,前面提到的调用了mParent 的requestLayout()方法,即是ViewRootImpl的requestLayout()。

    1. ViewRootImpl.java:
    2. @Override
    3. public void requestLayout() {
    4. if (!mHandlingLayoutInLayoutRequest) { // 1.请求更新布局
    5. checkThread(); // 2.首先检查当前所在线程
    6. //…
    7. }
    8. }
    9. void checkThread() {
    10. // 3.直接判断view所属的线程是否为当前线程,否则抛出异常
    11. if (mThread != Thread.currentThread()) {
    12. throw new CalledFromWrongThreadExcept ion( “Only the original thread that created a view hierarchy can touch its views.”);
    13. }
    14. }

    final Thread mThread; //存放view所属的线程
    所以,在进行UI更新时,都会进行线程的检测判断。以上就是为什么不能在子线程中直接更新UI的原因原理。
    为了避免上述问题,Android 引入了一种机制,允许我们在子线程中将任务切换到主线程执行。

    移步:子线程怎么切换主线程?
     

  • 相关阅读:
    fastjson 序列化 输出空字段
    车载毫米波雷达行业发展5——企业
    java-php-python-ssm-智慧校园学生选宿系统-计算机毕业设计
    Revisiting Large Language Models as Zero-shot Relation Extractors
    初级算法_数组 --- 旋转图像
    招投标系统简介 企业电子招投标采购系统源码之电子招投标系统 —降低企业采购成本
    23 种设计模式详解(C#案例)
    五金机电行业经销商协同管理系统解决方案
    中级C++:布隆过滤器
    Linux开发工具(4)——Makefile
  • 原文地址:https://blog.csdn.net/JiYaRuo/article/details/136673530