上一篇文章《Anroid异步消息机制(Handler、Looper、Message、MessageQueue)以及ThreadLocal运用》提到,在Android中,非主线程不能更新UI(ViewRootImpl在主线程中创建,所以我们要在主线程中更新UI。同理,如果ViewRootImpl在子线程中创建的话,那么也可以在子线程中更新UI,也就是说在哪里更新UI和ViewRootImpl在哪里创建是关联的。默认ViewRootImpl在主线程中创建),这时候我们可以借助Handler来实现(Activiy.runOnUiThread()也可以实现,但原理也是Handler,调用的post(Runnable))“。
一、我们做个测试
1、activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <TextView android:id="@+id/main_thread" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="UI主线程" /> <TextView android:id="@+id/sub_thread" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="UI主线程中直接建立子线程" /> <TextView android:id="@+id/sub_thread_thread" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="UI主线程中直接建立子线程的子线程" /> <TextView android:id="@+id/click_window" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="直接更新UI线程" /> <TextView android:id="@+id/click_subclass_window" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="直接建立子类更新UI线程并且跳出子窗口" /> <TextView android:id="@+id/click_thread" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击,直接建立子线程" /> <TextView android:id="@+id/click_subclass_thread" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击,建立子类,子类中建立子线程" /> <Button android:id="@+id/click_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击,直接更新UI线程" /> <Button android:id="@+id/click_subclass_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击,直接建立子类更新UI线程并且跳出子窗口" /> <Button android:id="@+id/click_thread_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击,直接建立子线程" /> <Button android:id="@+id/click_subclass_thread_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击,建立子类,子类中建立子线程" /> <Button android:id="@+id/click_windowManager_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击,建立子窗口" /></LinearLayout>2、MainActivity.java
3、结果
点击“点击,直接更新UI进程”,结果图如下:
点击“点击,直接建立子类更新UI线程并且跳出子窗口”,如下图所示:
当点击“点击,直接建立子线程”和“点击,建立子类,子类中建立子线程”按钮,应用崩溃报错,如下:
从结果中可以看出,sub_thread、sub_thread_thread、click_window、click_subclass_window对应的操作可以正常更新UI;但点击按钮“点击,直接建立子线程”和“点击,建立子类,子类中建立子线程”按钮的时候,应用崩溃报错(内部类中调用)。从错误日志,可以看出调用顺序
TextView.setText()->TextView.checkForRelayout()->View.requestLayout()->ViewRootImpl.requestLayout(()->ViewRootImpl.checkThread()。
1、TextView (继承View)
2、View3、ViewRootImpl (实现接口ViewParent)从checkThread(),当当前线程不是创建ViewRootImpl的原始线程的时候,报出“Only the original thread that created a view hierarchy can touch its views.”错误,意思很明了,即只有创建视图的源线程才能更改它的视图。那如何在非UI线程中更新UI线程?二、非UI线程中更新UI线程
1、Handler异步消息模式
2、创建新的ViewRootImpl
比如WindowManager
在我们的测试中,点击“点击,建立子窗口”就是实现子线程中利用WindowManager建立新的ViewRootImpl,点击按钮,出现应用崩溃,报错如下图:
根据报错跟踪具体代码,分析如下:
1、WindowManager接口(实现接口ViewManager)
2、WindowManagerImpl(实现接口WindowManager)
3、WindowManagerGlobal从上面内容与ViewRootImpl源代码分析,我们知道ViewRootImpl会创建变量ViewRootHandler mHandler,这是Handler对象;从文章《Anroid异步消息机制(Handler、Looper、Message、MessageQueue)以及ThreadLocal运用》,我们知道新建Handler,需要调用Looper.myLooper(),它会检查当前线程是否有Looper存在,如果没有,就报错,提示我们需要通过走新建Looper的流程(Looper.PRepare()->Looper.loop(),具体可以看文章)。故需要在WindowManager建立前加上Looper.prepare(),建立后加上Looper.loop(),具体看代码中注释部分。
三、Android视图
从ViewRootImpl到WindowManger源码分析,可以猜测每个Activity可以有多个ViewRootImpl,通过WindowManager.addView(View view, ViewGroup.LayoutParams params)将View mView添加到新建的ViewRootImpl中;view的逻辑与事件都会一层层上到ViewRootImpl来处理;各个ViewRootImpl是相互独立的。我们可以推导出WindowManger、ViewRootImpl、View、Activity等之间的关系,如下图所示:
四、遗留问题
1、requestLayout()、invalidate()有何区别?
requestLayout分为三步:测量(测宽、高),布局(坐标),绘制
invalidate:UI线程,进行绘制
postInvalidate:非UI线程通过Handler更新UI
2、为什么sub_thread、sub_thread_thread对应的操作可以正常更新UI,他们跳过了checkThread()?
新闻热点
疑难解答