报错信息:

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

报错信息是在ViewRootImpl.java中出现的

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

ViewRootImpl负责DecorView的测量布局绘制的调用,调用DecorView的测量布局绘制后会遍历控件树的测量布局绘制,控件树里面的任何一个小View刷新,最终都会调用到ViewRootImpl的requestLayout方法,最终遍历整个控件树刷新

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

scheduleTraversals()函数里是对View进行绘制操作,而在绘制之前都会检查当前线程是否为主线程mThread,如果不是主线程,就抛出异常;这样做法就限制了开发者在子线程中更新UI的操作。

为什么最开始的在onCreate()里子线程对UI的操作没有报错呢,可以设想一下是因为ViewRootImp此时还没有创建,还未进行当前线程的判断。

ViewRootImp 是何时创建的?

从Activity启动过程中寻找,通过分析ActivityThread.java源码,并找到handleResumeActivity方法:

@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {

。。。
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
。。。
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}

。。。
}

可以看到是在先调用performResumeActivity之后在赋值的

public final ActivityClientRecord performResumeActivity(IBinder token, boolean clearHide) { 
if (r != null && !r.activity.mFinished) {
r.activity.performResume();
...
}
}

最后发现会回调生命周期的onResume方法。

通过分析可得知,ViewRootImpl对象是在onResume方法回调之后才创建,那么就说明了为什么在生命周期的onCreate方法里,甚至是onResume方法里都可以实现子线程更新UI,因为此时还没有创建ViewRootImpl对象,并不会进行是否为主线程的判断

总结

必须要在主线程更新UI,实际是为了提高界面的效率和安全性,带来更好的流畅性;

你反推一下,假如允许多线程更新UI,但是访问UI是没有加锁的,一旦多线程抢占了资源,那么界面将会乱套更新了,体验效果就不言而喻了;

所以在Android中规定必须在主线程更新UI

最后

有小伙伴私信问Compose的问题,好不好用啊,现在要不要学啊?

其实答案很简单,自从谷歌2019年公布了声明式UI框架Jetpack Compose后,两年多的时间,各种大力宣传,和大量资源的倾斜,API功能都趋于稳定了。

至于好不好用,各种用过的同行都是持肯定态度的。优势大概就是这四点:

强大的工具和直观的Kotlin API

简化并加速了Android上的UI开发

可以帮助开发者用更少更直观的代码创建View

有更强大的功能,以及还能提高开发速度

这么大的优势,毋庸置疑,肯定是要学的嘛,而且越快掌握越好。别等刀架到脖子上了,才去练金钟罩。

至于怎么快速上手,可以给大家免费分享一份**《Jetpack Compose 完全开发手册》**,手把手教大家从入门到精通。

第一章 初识 Jetpack Compose

  • 为什么我们需要一个新的UI 工具?
  • Jetpack Compose的着重点
  • 加速开发

    强大的UI工具

    直观的Kotlin API

Android - 子线程为什么不能更新UI_Android面试

  • API 设计

Android - 子线程为什么不能更新UI_Android面试_02

  • Compose API 的原则
    一切都是函数
    顶层函数(Top-level function)
    组合优于继承
    信任单一来源

Android - 子线程为什么不能更新UI_android_03

  • 深入了解Compose
    Core
    Foundation
    Material

Android - 子线程为什么不能更新UI_android studio_04

  • 插槽API

第二章 Jetpack Compose构建Android UI

  • Android Jetpack Compose 最全上手指南
    Jetpack Compose 环境准备和Hello World
    布局
    使用Material design 设计
    Compose 布局实时预览
    ……

Android - 子线程为什么不能更新UI_android_05

  • 深入详解 Jetpack Compose | 优化 UI 构建
    Compose 所解决的问题
    Composable 函数剖析
    声明式 UI
    组合 vs 继承
    封装
    重组
    ……

Android - 子线程为什么不能更新UI_主线程_06

  • 深入详解 Jetpack Compose | 实现原理
    @Composable 注解意味着什么?
    执行模式
    Positional Memoization (位置记忆化)
    存储参数
    重组
    ……

Android - 子线程为什么不能更新UI_主线程_07

第三章 Jetpack Compose 项目实战演练(附Demo)

  • Jetpack Compose应用1
    开始前的准备
    创建DEMO
    遇到的问题

Android - 子线程为什么不能更新UI_Android面试_08

  • Jetpack Compose应用2
  • Jetpack Compose应用做一个倒计时器
    数据结构
    倒计时功能
    状态模式
    Compose 布局
    绘制时钟

Android - 子线程为什么不能更新UI_Android面试_09

  • 用Jetpack Compose写一个玩安卓App
    准备工作
    引入依赖
    新建 Activity
    创建 Compose
    PlayTheme
    画页面
    底部导航栏
    管理状态
    添加页面

Android - 子线程为什么不能更新UI_主线程_10

  • 用Compose Android 写一个天气应用
    开篇
    画页面
    画背景
    画内容
    ……

Android - 子线程为什么不能更新UI_子线程_11

  • 用Compose快速打造一个“电影App”
    成品
    实现方案
    实战
    不足
    ……

Android - 子线程为什么不能更新UI_android studio_12

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!