我是靠谱客的博主 阳光大米,最近开发中收集的这篇文章主要介绍Android窗口机制:六、一定要在主线程才可以更新UI吗?为什么?(源码版本SDK31),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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

  • 初识
  • DecorView与SubDecor的创建加载
  • Window与Window Manager的创建加载
  • ViewRootImpl的创建以及视图真正加载
  • ViewRootImpl的事件分发
  • 一定要在主线程才可以更新UI吗?为什么?
  • Activity的Token和Dialog的关系
  • Toast机制 - 封装可以在任何线程调用的toast
  • 总结

通过前几章节的介绍,大家应该大致都了解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就可以设置,想知道怎么操作的小伙伴可以等下一章节噢????‍♀️

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

最后

以上就是阳光大米为你收集整理的Android窗口机制:六、一定要在主线程才可以更新UI吗?为什么?(源码版本SDK31)的全部内容,希望文章能够帮你解决Android窗口机制:六、一定要在主线程才可以更新UI吗?为什么?(源码版本SDK31)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(77)

评论列表共有 0 条评论

立即
投稿
返回
顶部