來點(diǎn)前奏說明
當(dāng)你打開這個文檔的時(shí)候鼎文,你已經(jīng)做好準(zhǔn)備了凡简,話不多說開搞纱兑。
本文以Android 9.0 版本進(jìn)行分析呀闻,當(dāng)然你也可以在線看源碼
在線源碼查看
Android源碼下載編譯
9.0源碼百度網(wǎng)盤下載鏈:https://pan.baidu.com/s/1OEek7vXE9FUhzVnOfTVJzg 提取碼:d0ks
在此特別說明,我這篇主要分析流程和注釋潜慎。我把英語注釋也粘貼了捡多,大家自己去翻譯自己消化,個人意見重點(diǎn)是流程+注釋铐炫,流程+注釋垒手,流程+注釋。
為什么有Handler:
- 主線程不能做耗時(shí)操作
- 子線程不能更新UI
那么為什么不能在子線程更新UI呢
- 如果你沒看過源碼或者分析過倒信,我想你會回答谷歌這樣設(shè)計(jì)的科贬。
- 如果繼續(xù)問谷歌為什么這么設(shè)計(jì)呢,我猜想你內(nèi)心已經(jīng)開始罵娘了鳖悠,我TM哪知道他為什么這么設(shè)計(jì)榜掌。
- 這篇文章只是我自己對這個事情的看法,如果有誤乘综,歡迎各位評論指正憎账。
子線程更新UI常見的報(bào)錯信息
- 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看字面意思計(jì)劃遍歷
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)動畫、輸入和繪圖的計(jì)時(shí)
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啥時(shí)候創(chuàng)建的允蚣?
可以看到里面調(diào)用了performTraversals()方法于颖,View的繪制過程就是從performTraversals方法開始的。我們現(xiàn)在知道了嚷兔,每一次訪問UI森渐,Android都會重新繪制View做入。ViewRoot會調(diào)用checkThread方法檢查當(dāng)前訪問UI的線程是哪個,如果不是UI線程則會拋出異常同衣。
為什么谷歌要提出:“UI更新一定要在UI線程里實(shí)現(xiàn)”這一規(guī)則呢竟块?
目的在于提高移動端更新UI的效率和和安全性,以此帶來流暢的體驗(yàn)耐齐。原因是:Android的UI訪問是沒有加鎖的浪秘,多個線程可以同時(shí)訪問更新操作同一個UI控件。也就是說訪問UI的時(shí)候埠况,android系統(tǒng)當(dāng)中的控件都不是線程安全的耸携,這將導(dǎo)致在多線程模式下,當(dāng)多個線程共同訪問更新操作同一個UI控件時(shí)容易發(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