架构设计分析(二)Android消息机制篇二 为什么不能在子线程
来点前奏说明
当你打开这个文档的时候,你已经做好准备了,话不多说开搞。
本文以Android 9.0 版本进行分析,当然你也可以在线看源码
在线源码查看
Android源码下载编译
9.0源码百度网盘下载链:https://pan.baidu.com/s/1OEek7vXE9FUhzVnOfTVJzg 提取码:d0ks
在此特别说明,我这篇主要分析流程和注释。我把英语注释也粘贴了,大家自己去翻译自己消化,个人意见重点是流程+注释,流程+注释,流程+注释。
为什么有Handler:
- 主线程不能做耗时操作
- 子线程不能更新UI
那么为什么不能在子线程更新UI呢
- 如果你没看过源码或者分析过,我想你会回答谷歌这样设计的。
- 如果继续问谷歌为什么这么设计呢,我猜想你内心已经开始骂娘了,我TM哪知道他为什么这么设计。
- 这篇文章只是我自己对这个事情的看法,如果有误,欢迎各位评论指正。
子线程更新UI常见的报错信息
- android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.Java:6581)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)
堆栈代码部分
framework/base/core/java/android/view/ViewRootImpl.java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
接着往下看异常日志,发现了ViewRootImpl中调用的地方是requestLayout方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
scheduleTraversals看字面意思计划遍历
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
framework/base/core/java/android/view/Choreographer.java 调postCallback方法
刚开始对这个类注释说明了一下,我翻译了一下。协调动画、输入和绘图的计时
Coordinates the timing of animations, input and drawing
postCallback方法的的第二个参数:TraversalRunnable,意思是遍历线程,是一个后台任务
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
里面除了调用了doTraversal()方法,我们继续看doTraversal()方法
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
ViewRootImpl啥时候创建的?
可以看到里面调用了performTraversals()方法,View的绘制过程就是从performTraversals方法开始的。我们现在知道了,每一次访问UI,Android都会重新绘制View。ViewRoot会调用checkThread方法检查当前访问UI的线程是哪个,如果不是UI线程则会抛出异常。
为什么谷歌要提出:“UI更新一定要在UI线程里实现”这一规则呢?
目的在于提高移动端更新UI的效率和和安全性,以此带来流畅的体验。原因是:Android的UI访问是没有加锁的,多个线程可以同时访问更新操作同一个UI控件。也就是说访问UI的时候,android系统当中的控件都不是线程安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个UI控件时容易发生不可控的错误,这是致命的。所以Android中规定只能在UI线程中访问UI,这相当于从另一个角度给Android的UI访问加上锁
FAQ
Question1:Handler Message MessageQueue对应关系?
Answer1:一个线程只能有一个Looper和MessageQueue 但可以接收多个Handler发过来的多个消息
一个Message只能属于一个Handler
一个Handler只能处理自己发送给Looper的消息。
一个MessageQueue对应多个Message