Android窗口机制:六、一定要在主线程才可以更新UI吗?为什么?(源码版本SDK31)

Android 窗口机制 SDK31源码分析 总目录

通过前几章节的介绍,大家应该大致都了解Android整体的窗口机制了吧。

那么问一个问题,UI一定要在主线程才可以更新吗?回答这个问题之前,先看一个简单的例子

示例演示

代码如下所示:代码很简单,在onCreate中启动子线程(子线程的名字被设置为Myself Thread)更新TextViewtext属性,在onResume函数中将当前的text值打印在控制台。

	lateinit var binding: ActivityThreadTestBinding

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityThreadTestBinding.inflate(layoutInflater)
        setContentView(binding.root)

        //子线程更新text
        Thread {
            binding.tv1.text = "The name of the current thread is ${Thread.currentThread().name}."
        }.apply {
            name = "Myself Thread"
        }.start()
    }

    override fun onResume() {
        super.onResume()
        //打印出当前控件所设置的text
        binding.tv1.text.toString().toLogI()
    }

大家可以猜测一下,上述代码可以正常运行吗❓

答案是:可以正常运行,并且控制台打印出来了 The name of the current thread is Myself Thread.

啊😮?这是为什么呢?子线程竟然更新UI竟然没有抛异常而且成功了🤦‍♀️?不着急,我们慢慢分析。

为什么子线程可以更新UI呢?下面我们分析一下。

我们知道视图更新会调用View的requestLayout或者invalidate方法,而这两个方法均会调用ViewRootImpl的requestLayout方法,进而调用到ViewRootImpl的scheduleTraversals,好的我们回顾一下ViewRootImpl的requestLayout方法。

ViewRootImpl
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        //判断当前是否是主线程即mThread,如果不是,则抛异常。就是在子线程更新UI没使用handler的话就会抛出的异常
        checkThread();
        //设置mLayoutRequested为true。
        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.");
    }
}

看得到,在ViewRootImpl的checkThread的方法里面,会进行判断,如果不是主线程的话,就抛出异常(就是我们常见的不能在子线程更新UI的异常)。

那么聪明的同学们可能就要发问了,那为什么上面的代码没有抛异常还运行成功了?

我们再回忆一下前面的章节内容,ViewRootImpl是在什么时候被实例化的呢

流程大致如下:ActivityThread.handleResumeActivity -> Activity.onResume -> WindowManagerGlobal.addView -> new ViewRootImpl -> ViewRootImpl.setView -> View.assignParent(this)

就是在系统准备显示界面的时候,直观一点的话,就是在Activity的onResume方法被调用之后,会新创建一个ViewRootImpl,之后会调用其setView方法,里面调用到DecorView的assignParent方法,将其分配给View的mParent变量。

所以综上所述:onCreate方法里面调用设置UI的时候,并没有进行实际的绘制流程,因为ViewRootImpl还没有被设置,那么猜测我们设置的值,应该是被View存在内存了,等到进行真正执行绘制流程的时候,才被渲染出来

下面进行上述猜测验证,简单分析一下TextView.setText的流程。

TextView 
private void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) {

	...
    //将value设置到内存
    setTextInternal(text)
    ...
    //具体绘制调用
    if (mLayout != null) {
        checkForRelayout();
    }
    ...
}

private void setTextInternal(@Nullable CharSequence text) {
    //内存中储存text值
    mText = text;
    mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
    mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
}

private void checkForRelayout() {
    ...
    //则机会调用view的这两个方法
    requestLayout();
    invalidate();
}
//View的requestLayout
public void requestLayout() {
    ...
    //mParent指的是ViewRootImpl,如果是在onCreate的时候异步线程更新UI的话,此时mParent为null,所以不会具体的进行绘制,所以就不会抛异常
    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    ...
}

显而易见,在onCreate中更新UI时,只是把值保存到了内存中,当视图真正渲染时,才进行正常的绘制流程。

比如如果我们把异步线程setText的操作,放到一个按钮里面,通过点击实现,那么一定会抛出异常(因为此时界面已经显示出来了)。如下所示:

binding.bt1.setOnClickListener {
	Thread {
		binding.tv1.text = "The name of the current thread is ${Thread.currentThread().name}."
	}.apply {
		name = "Myself Thread2"
	}.start()
}

点击,控制台打印的异常信息如下:
    	android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:9328)
        ...
        at com.pumpkin.automatic_execution.View.ThreadTestActivity.onCreate$lambda-2$lambda-0(ThreadTestActivity.kt:24)

看看抛出的异常堆栈信息了吗?在ViewRootImpl的checkThread方法。

好了 , 以上的分析就到这里。

那么ViewRootImpl判断的线程,能不能自己设置呢?Dialog就可以设置,想知道怎么操作的小伙伴可以等下一章节噢🙆‍♀️

创作不易,如有帮助一键三连咯🙆‍♀️。欢迎技术探讨噢!

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pumpkin的玄学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值