Android 窗口机制 SDK31源码分析 总目录
- 初识
- DecorView与SubDecor的创建加载
- Window与Window Manager的创建加载
- ViewRootImpl的创建以及视图真正加载
- ViewRootImpl的事件分发
- 一定要在主线程才可以更新UI吗?为什么?
- Activity的Token和Dialog的关系
- Toast机制 - 封装可以在任何线程调用的toast
- 总结
通过前几章节的介绍,大家应该大致都了解Android整体的窗口机制了吧。
那么问一个问题,UI一定要在主线程才可以更新吗?回答这个问题之前,先看一个简单的例子
示例演示
代码如下所示:代码很简单,在onCreate
中启动子线程(子线程的名字被设置为Myself Thread
)更新TextView
的text
属性,在onResume
函数中将当前的text
值打印在控制台。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19lateinit 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
方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19ViewRootImpl 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
的流程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34TextView 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
的操作,放到一个按钮里面,通过点击实现,那么一定会抛出异常(因为此时界面已经显示出来了)。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13binding.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吗内容请搜索靠谱客的其他文章。
发表评论 取消回复