Android为什么不能在子线程更新UI?
// Android中为什么子线程不能更新UI?
viewRootImpl对象是在Activity中的onResume方法执行完成之后,View变得可见时才创建的,之前的操作是没有进行线程检查的,所以没有报错。但是ViewRootImpl创建之后,由于进行了checkThread操作,所以就不能在子线程更改UI了
当访问 UI 时,ViewRootImpl 会调用 checkThread方法去检查当前访问 UI 的线程是否为创建 UI 的那个线程,如果不是。则会抛出异常
如果不做这个校验,是不是我也可以正常在子线程更新UI
// 如果不做这个校验,是不是我也可以正常在子线程更新UI?
按理来说,这样是可以的
但是google为什么要这样去设计呢
// 但是google为什么要这样去设计呢
如果在不同的线程去控制用一个控件,由于网络延时或者大量耗时操作,会使UI绘制错乱,出了问题也很难去排查到底是哪个线程更新时出了问题;
主线程负责更新,子线程负责耗时操作,能够大大提高响应效率
UI线程非安全线程,多线程进行并发访问有可能会导致内存溢出,降低硬件使用寿命;且非线程安全不能加Lock线程锁,否则会阻塞其他线程对View的访问,效率就会变得低下!
ViewRootImp是在onActivityCreated方法后面创建的吗
// ViewRootImp是在onActivityCreated方法后面创建的吗?
ViewRootImpl是在Activity的onResume()方法后面创建出来的,所以在onResume之前的UI更新可以在子线程操作而不报错,因为这个时候ViewRootImpl还没有创建,没有执行checkThread()方法。
安卓系统中,操作viwe对象没有加锁,所以如果在子线程中更新UI,会出现多线程并发的问题,导致页面展示异常。
为什么一定需要checkThread呢
// 为什么一定需要checkThread呢?
因为UI控件不是线程安全的
那为什么不加锁呢
// 那为什么不加锁呢?
- 一是加锁会让UI访问变得复杂;
- 二是加锁会降低UI访问效率,会阻塞一些线程访问UI。
- 所以干脆使用单线程模型处理UI操作,使用时用Handler切换即可
为什么一开始在Activity的onCreate方法中创建一个子线程访问UI,程序还是正常能跑起来呢
// 为什么一开始在Activity的onCreate方法中创建一个子线程访问UI,程序还是正常能跑起来呢?
因为ViewRootImpl 的创建在 onResume 方法回调之后,而我们一开篇是在 onCreate 方法中创建了子线程并访问 UI,在那个时刻,ViewRootImpl 还没有创建,我们在因此 ViewRootImpl#checkThread 没有被调用到,也就是说,检测当前线程是否是创建的 UI 那个线程 的逻辑没有执行到,所以程序没有崩溃一样能跑起来。而之后修改了程序,让线程休眠了 3000 毫秒后,程序就崩了。很明显 3000 毫秒后 ViewRootImpl 已经创建了,可以执行 checkThread 方法检查当前线程
Android中子线程真的不能更新UI吗
// Android中子线程真的不能更新UI吗?
任何线程都可以更新自己创建的UI,但是需要满足各自对应的条件
ViewRootImpl 还没创建出来之前。UI 修改的操作没有线程限制。因为 checkThread 方法不会被执行到。
在 ViewRootImpl 创建完成之后,保证「创建 ViewRootImpl 的操作」和「执行修改 UI 的操作」在同一个线程即可。也就是说,要在同一个线程调用 ViewManager的addView 和 ViewManager的updateViewLayout 方法。
// 注:ViewManager 是一个接口,WindowManger 接口继承了这个接口,我们通常都是通过 WindowManger(具体实现为 WindowMangerImpl) 进行 view 的 add remove update 操作的。
对应的线程需要创建 Looper 并且调用 Looper的loop 方法,开启消息循环。
保证上述条件1成立不就可以避免checkThread时候抛出异常了吗?为什么还需要开启消息循坏
// 保证上述条件1成立,不就可以避免checkThread时候抛出异常了吗?为什么还需要开启消息循坏?
条件 1 可以避免检查异常,但是无法保证 UI 可以被绘制出来。
条件 2 可以让更新的 UI 效果呈现出来。
注:WindowManger的addView 最终会调用WindowManageGlobal的addView方法,进而触发ViewRootImpl的setView 方法,该方法内部会调用ViewRootImpl的requestLayout 方法。根据 UI 绘制原理,下一步就是 scheduleTraversals了,该方法会往消息队列中插入一条消息屏障,然后调用 Choreographer的postCallback 方法,往 looper 中插入一条异步的 MSG_DO_SCHEDULE_CALLBACK 消息。等待垂直同步信号回来之后执行
使用子线程更新UI有实际应用场景吗
// 使用子线程更新 UI 有实际应用场景吗?
Android 中的 SurfaceView 通常会通过一个子线程来进行页面的刷新。如果我们的自定义 View 需要频繁刷新,或者刷新时数据处理量比较大,那么可以考虑使用 SurfaceView 来取代 View