Android基础 -- 子线程可以修改UI吗?

2023-05-16

子线程可以修改UI吗?为什么会产生这样的问题,可能是因为在开发过程中遇到了

"Only the original thread that created a view hierarchy can touch its views."

这个异常信息,又或者是常用Handler将子线程中的数据更新到UI上,又或者是其他的一些原因,如果你思考到了为什么要在主线程中更新UI或者子线程中可以更新UI吗这样的问题,说明你有一颗探寻问题本质的心,对于技术从业者来讲,是极为有益的,知其然更要知其所以然,接下来,就一起看看其中的逻辑。

先说结论,子线程是可以修改的UI的,例如下面这行代码

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.yourlayoutid);
        new Thread(new Runnable() {
            @Override
            public void run() {
                mContentTv.setText("我是来自子线程的更新信息");
            }
        }).start();
    }

但是如果给这个线程做个延迟,或者使用按钮点击来执行,就会看到上面熟悉的异常信息了,那么我们从这个异常信息入手,这个异常信息是在ViewRootImpl中抛出的

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

如果你不熟悉ViewRootImpl,先不要跳出去看ViewRootImpl,对于刚开始看源码的同学而言,切记不要一看到陌生的类,就跳出去看,在跳出去看的过程中,可能又会遇到不熟悉的类,所以,相关的类的知识点,只需要知道它的大致功能就好,等理解了当前的知识点后,在去看这些陌生的类的细节,刚开始可能会比较痛苦,但是一段时间之后,看源码的效率会大大提高。

ViewRootImpl可以先暂时理解为一个View的管理类,它在执行一些View相关操作的时候,会去检查线程是否为主线程,如果不是,就抛出这个异常。

那么接下来的关注点就是ViewRootImpl何时被创建?

既然是创建,我们就从构造方法入手

public ViewRootImpl(Context context, Display display) {
        ...        
}

可以看到在WindowManagerGlobal的addView方法里,有着ViewRootImpl的初始化操作

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        
        ViewRootImpl root;
        
        synchronized (mLock) {

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

        }
    }

而WindowManagerGlobal的addView方法会在WindowMangerImpl被调用

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

WindowManagerImpl是WindowManager的实现类,而WindowsManager继承自ViewManager,在Activity里的makeVisible方法里有着WindowMangerImpl的addView方法的调用

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

而makeVisible方法在ActivityThread的handleResumeActivity里进行了调用

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
       
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }
        }

    }

在handleResumeActivity里,调用了perfromResumeActivity方法

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
      
        }

在perfromResumeActivity方法里,调用了activity的performResume方法

    @VisibleForTesting
    public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
            String reason) {
            r.activity.performResume(r.startsNotResumed, reason);
        } 

在performResume方法里,调用了Instrument的callActivityOnResume方法

final void performResume(boolean followedByPause, String reason) {
        mInstrumentation.callActivityOnResume(this);
    }

callActivityOnResume方法里,调用了onResume

public void callActivityOnResume(Activity activity) {
        activity.mResumed = true;
        activity.onResume();
    }

至此,我们就可以明白,ViewRootImpl是在onResume的时候创建的,而上面的示例代码,是在onCreate里修改UI的,这个时候还没有ViewRootImpl,所以不会检查线程。

上面的源码分析是从入口一步一步找上来了,现在顺着逻辑在来做一个总结。

在ActivityThread里的handleResumeActivity方法里,调用了onResume,并且创建了ViewRootImpl,有了ViewRootImpl后,就无法在子线程中直接修改UI了。

这两条线,小伙伴可以自己顺着源码找一找具体的调用关系,调用到的函数在文章里都提到了,希望会对小伙伴有帮助~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android基础 -- 子线程可以修改UI吗? 的相关文章

随机推荐

  • idea中Gradle项目控制台中文乱码

    我使用的是IEDA2021 xff0c 之前跑maven项目一切正常 今天导入了一个Gradle项目 xff0c debug的时候控制台中文乱码了 之前直接用idea控制台中文乱码做关键词搜索 xff0c 改了file settings e
  • @RequestMapping value值置为““

    我们通常用 64 RequestMapping来映射请求 xff0c 比如 xff0c 写一个方法 xff1a span class token annotation punctuation 64 RequestMapping span s
  • 三十分钟做一个网页游戏

    本文目的是短时间之内 xff0c 通过做出一个简单的缘分对对碰游戏 xff0c 了解网页的基本要素 之前没有接触过网页开发 xff0c 这次算是个入门了 对于大部分网页 xff0c 都要包括HTML CSS JavaScript三种技术 而
  • 软件安装时窗口出现在屏幕左上角而且拖不出来

    今天在安装MYSQL是出现如下问题 xff1a 安装助手出现在屏幕左上角而且拖不出来 xff0c 导致安装没办法完成 用一个很简单的方法解决了问题 xff1a 桌面空白处右键 xff0c 点屏幕分辨率 把方向改成纵向 xff0c 左上角的窗
  • DELL笔记本插入耳机没反应

    新入的戴尔燃7000 xff0c 上午因为CPU占用飙升 xff0c 关掉了笔记本上的几个自启动项 xff0c 下午插入耳机就无响应了 xff0c 耳机插进去 xff0c 还是外放 百度原因 xff0c 很多都提及了Realtek这一声卡驱
  • the server responded with a status of 404 (Not Found)

    使用ajax跳转方法时 xff0c 页面ctrl 43 shift 43 i调试报告了一个404错误 xff0c 说找不到方法 页面地址栏直接指向方法的地址跳转也是404 目标方法是新增的 xff0c 于是使用复制黏贴 xff0c 确定各处
  • select设置只读

    根据需求 xff0c 需要根据后台传来的参数 xff0c 动态设置select标签是否可以选择 xff0c 因此 xff0c 当判断某个select应当设为只读时 xff0c 使用 span class hljs variable span
  • java:程序包XXXX不存在

    使用idea导入maven项目 xff0c 编译时报错 xff1a java 程序包XXXX不存在 如图 xff1a 百度到的诸如右键libraries所在文件夹 xff0c 选择add to libraries 等方法没有作用 后来去查看
  • tomcat启动报错:java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.Lifec

    tomcat启动报错 xff1a java lang IllegalStateException ContainerBase addChild start org apache catalina Lifec 百度的结果一般都是让修改web
  • UE4 音乐的播放与停止--基于蓝图

    要实现的功能非常简单 xff1a 点击按钮 xff0c 播放音乐 这个功能非常基础 xff0c 就两步 xff1a 1 将音乐源文件拖到context文件夹中 注意 xff0c 这里的音乐文件必须是 wav格式的 2 在按钮的onclick
  • UnityEditor.BuildPlayerWindow+BuildMethodException

    unity3D安卓打包报错 xff1a UnityEditor BuildPlayerWindow 43 BuildMethodException 61 errors at UnityEditor BuildPlayerWindow 43
  • Spark常用API<Scala>

    概览 1 转换 2 动作1 Transformation 1 1一个RDD进行转换操作1 2 两个RDD的转换操作1 3对一个Pair RDD进行转化操作1 4对两个PairRDD进行转换操作2 Action 2 1对一个RDD进行行动操作
  • 关于特定网页打不开问题的解决

    如果有一些特定的网站打不开 排除被屏蔽的可能性的话 xff0c 试着把DNS设置成了自动获取ip试试看 我就这样子解决了打不开学校官网的问题
  • 渲染业务领域全景图

    最近图形学应用领域愈发广泛 xff0c 根据我的理解 xff0c 制作了一张渲染相关业务全景图 xff0c 希望对大家的职业规划有一定帮助
  • AI 入门怎么学?这份学习指南请收好!

    万事开头难 xff01 AI 入门对很多初学 AI 的同学来说是一大难题 搜集了一大堆入门资料 xff0c Python 数学 深度学习应有尽有 xff0c 但就是无从下手 xff0c 总是在第一章与放弃之间徘徊 那么 xff0c AI 应
  • FTP如何设置用户名密码

    1 新建FTP站点 xff0c 指定名称和物理路径 2 身份验证 选择 基本 xff0c 允许访问 选择 指定用户 xff0c 下面文本框中输入 本地用户和组 中现有的一个用户名即可 注意 xff1a 只能是 本地用户和组 中的用户 xff
  • Android布局 -- Navigation实现底部导航栏

    底部导航栏加页卡的切换 xff0c 很多App采用这种布局设计 xff0c 在以前的开发中 xff0c 需要自定义底部导航栏以及使用FragmentTransaction来管理Fragment的切换 xff0c 代码量较大 xff0c 而使
  • ViewModelProviders is deprecated

    原有的创建ViewModel的方法 xff1a viewModel 61 ViewModelProviders of this get ViewModel class 提示ViewModelProviders过时 改为 xff1a view
  • Android Fragment退出 返回上一个Fragment与直接退出

    例如应用底部有两个导航按钮A与B xff0c 刚进入的时候显示为第一个AFragment xff0c 点击B切换到BFragment 如果需求是在BFragment点击返回键回到AFragment xff0c 需要配置 app defaul
  • Android基础 -- 子线程可以修改UI吗?

    子线程可以修改UI吗 xff1f 为什么会产生这样的问题 xff0c 可能是因为在开发过程中遇到了 34 Only the original thread that created a view hierarchy can touch it