在Android中,非主线程不能更新UI

来源:互联网 发布:mac怎么输入罗马数字 编辑:程序博客网 时间:2024/05/11 15:15

上一篇文章《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、View

3、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()?

0 0
原创粉丝点击