來點前奏說明
當(dāng)你打開這個文檔的時候,你已經(jīng)做好準(zhǔn)備了,話不多說開搞。
本文以Android 9.0 版本進(jìn)行分析,當(dāng)然你也可以在線看源碼
在線源碼查看
Android源碼下載編譯
9.0源碼百度網(wǎng)盤下載鏈:https://pan.baidu.com/s/1OEek7vXE9FUhzVnOfTVJzg 提取碼:d0ks
在此特別說明,我這篇主要分析流程和注釋。我把英語注釋也粘貼了,大家自己去翻譯自己消化,個人意見重點是流程+注釋,流程+注釋,流程+注釋。
為什么有Handler:
- 主線程不能做耗時操作
- 子線程不能更新UI
那么為什么不能在子線程更新UI呢
- 如果你沒看過源碼或者分析過,我想你會回答谷歌這樣設(shè)計的。
- 如果繼續(xù)問谷歌為什么這么設(shè)計呢,我猜想你內(nèi)心已經(jīng)開始罵娘了,我TM哪知道他為什么這么設(shè)計。
- 這篇文章只是我自己對這個事情的看法,如果有誤,歡迎各位評論指正。
子線程更新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.");
}
}
接著往下看異常日志,發(fā)現(xiàn)了ViewRootImpl中調(diào)用的地方是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 調(diào)postCallback方法
剛開始對這個類注釋說明了一下,我翻譯了一下。協(xié)調(diào)動畫、輸入和繪圖的計時
Coordinates the timing of animations, input and drawing
postCallback方法的的第二個參數(shù):TraversalRunnable,意思是遍歷線程,是一個后臺任務(wù)
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
里面除了調(diào)用了doTraversal()方法,我們繼續(xù)看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啥時候創(chuàng)建的?
可以看到里面調(diào)用了performTraversals()方法,View的繪制過程就是從performTraversals方法開始的。我們現(xiàn)在知道了,每一次訪問UI,Android都會重新繪制View。ViewRoot會調(diào)用checkThread方法檢查當(dāng)前訪問UI的線程是哪個,如果不是UI線程則會拋出異常。
為什么谷歌要提出:“UI更新一定要在UI線程里實現(xiàn)”這一規(guī)則呢?
目的在于提高移動端更新UI的效率和和安全性,以此帶來流暢的體驗。原因是:Android的UI訪問是沒有加鎖的,多個線程可以同時訪問更新操作同一個UI控件。也就是說訪問UI的時候,android系統(tǒng)當(dāng)中的控件都不是線程安全的,這將導(dǎo)致在多線程模式下,當(dāng)多個線程共同訪問更新操作同一個UI控件時容易發(fā)生不可控的錯誤,這是致命的。所以Android中規(guī)定只能在UI線程中訪問UI,這相當(dāng)于從另一個角度給Android的UI訪問加上鎖
FAQ
Question1:Handler Message MessageQueue對應(yīng)關(guān)系?
Answer1:一個線程只能有一個Looper和MessageQueue 但可以接收多個Handler發(fā)過來的多個消息
一個Message只能屬于一個Handler
一個Handler只能處理自己發(fā)送給Looper的消息。
一個MessageQueue對應(yīng)多個Message