子线程不可以更新UI吗?(2019-11-05)
这里文章基于Android 系统8.0及以上版本分析子线程更新UI并没有报错的原因,其它版本并没有出现这种情况可以参考这篇文章子线程能更新UI吗。经常看到文章说不要在子线程更新UI,不要在UI线程进行耗时操作。偶然间的尝试发现了一些奇怪的问题,先分析第一个问题子线程中可以更新UI吗?下面是我的代码:
1 | @Override |
点击运行之后,并没有出现我们预料中的崩溃和异常信息问题,即便我们在子线程中做了一些耗时操作。首先我们看一下我们希望预料之中出现的错误,然后根据堆栈分析错误信息为什么没有出现。
1 | android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. |
根据堆栈信息分析,可以看出错误信息是在 ViewRootImpl.checkThread() 方法中抛出,我们先来看一下 checkThread() 方法:
1 | //ViewRootImpl.java |
方法很简单判断 mThread(即 View 初始化所在线程)和当先线程比较,不是同一个线程就抛出以上异常信息。很明显我们之前的子线程和主线程的 TextView 并处在同一线程,但是为什么日志中没有看到对应的异常信息了,首先我们看一下 ViewRootImpl 中哪些方法调用了 checkThread() 方法,如下所示:
上述代码我们使用的是TextView,这里我们只分析父类View抛出异常信息的触发条件。我们挑选常见的 ViewRootImpl#requstLayout() 方法来分析具体的原因。我们来看一下 View#requestLayout() 方法:
1 | //View.java |
看一下第20行 mParent.requestLayout() 方法,其中 mParent 属于ViewParent 接口对象。从这篇文章关于View中mParent的来龙去脉中我们可以知道 mParent 为 ViewRootImpl(实现ViewParent接口)的具体实现,所以当我们调用 view#requestLayout 方法会出现预料之中的错误。其它的触发方法可以由此分析。我的总结是只有触发某些特定方法类似 View#requestLayout 等以及子类View比如TextView#setCompoundDrawables() 方法,才会出现某些异常退出,所以不推荐在子线程更新UI操作。(仅适用于Android 8以上设备。Android 7没有测试,Android 6.0 设备会出现异常信息并崩溃)。而我们上述的TextView#setText方法最终并没有调用checkThread()方法(不是某些文章提出的 ViewRootImpl 在 onResume 方法之前的原因,已验证 ),具体的原因可以对比一下Android6.0和Android8.0版本setText的源码和调试执行。待续…