Android View 的繪制流程 - 開篇 MeasureSpec
Android View 的繪制流程 01 - 前置流程
Android View 的繪制流程 02 - performMeasure
Android View 的繪制流程 03 - performLayout
Android View 的繪制流程 04 - performDraw
Android View 的繪制流程總結(jié)
之前文集中學(xué)習(xí)了幾個自定義的View, 那么一定還記得三個自定義View的重要流程.
- measure, (測量, 測量每一個 View 及 ViewGroup 的尺寸 )
- layout,???? (擺放, 根據(jù)測量的結(jié)果及參數(shù), 在布局上擺放每一個控件)
- draw, ??????(繪制, 擺放好位置后, 就開始繪制, 然后顯示在屏幕上)
這個文集也主要是圍繞著三個流程來學(xué)習(xí). 正式開始.
?
1. 前置流程
View 的繪制流程最初就是在 ActivityThread.handleLaunchActivity() 中開始.
?
?
2.1 ActivityThread.handleLaunchActivity()
在 Activity 啟動的過程中, 會調(diào)用 ActivityThread.handleLaunchActivity() 方法.
handleLaunchActivity( ) 內(nèi)部調(diào)用以下方法.
- ActivityThread.performLaunchActivity() - 這個方法會調(diào)用 Activity 的 onCreate 方法
- ActivityThread.handleResumeActivity() - 這個方法會調(diào)用 Activity 的 onResume 方法.(起始)
這個方法雖是入口, 但是我們重點學(xué)習(xí)不在這里, 所以就不再貼代碼上來, 只是簡單說一下流程.
?
?
1.2 ActivityThread.handleResumeActivity()
ActivityThread 3599 行
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
r = performResumeActivity(token, clearHide, reason);
...
if (...) {
//-------------1----------------
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
...
//------------2----------------
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
...
...
if (...) {
if (...) {
...
//-------------3--------------
wm.addView(decor, l);
}
}
...
}
handleResumeActivity 方法中調(diào)用了 performResumeActivity, 我猜測里面可能會調(diào)用了 Activity 的 onResume 方法, 在這里不是重點, 不再表述.
重點是下面幾行.
第一部分
看到 View decor = r.window.getDecorView();
, 又見到了熟悉的 DecorView, 結(jié)合 Android 之 setContentView 流程 這個文集, 立刻能聯(lián)想到 DecorView 的一系列關(guān)系.
- DecorView 在 PhoneWindow 中
- PhoneWindow 是 Window 的唯一派生類
- DecorView 是一個 FrameLayout.
- DecorView 內(nèi)部有一個布局, 布局內(nèi)部有一個ID 為 android.id.content 的 ViewGroup.(mContentParent)
- 執(zhí)行完 setContentView 后, DecorView 中 mContentParent 被改名為 NO_ID,
- mContentParent 中包含了一個 SubDecorView ,
- SubDecorView 中 有一個 ContentFrameLayout, 改名為 android.id.content.
- 我們調(diào)用 setContentView ,傳入的資源文件, 就在 SubDecorView 的 ContentFrameLayout 控件中.
- ...
怎么樣, 有沒有想起上面的那些?
所以說在 View 的 繪制流程中, 和這個 DecorView 有著非常密切的關(guān)系.
那么現(xiàn)在我們可以知道, PhoneWindow 賦值給了 r.window 屬性, DecorView 賦值給了 decor 變量.
?
?
第二部分
接著看 ViewManager wm = a.getWindowManager();
a 就是 Activity , Activity 中 getWindowManager() 返回的是 WindowManager 對象 mWindowManager,
public WindowManager getWindowManager() {
return mWindowManager;
}
public interface WindowManager extends ViewManager {
}
可是 WindowManager 只是一個接口, ViewManager 也是一個接口, 那么肯定有實現(xiàn)類, 那么繼續(xù)在 Activity.java 中搜索, 看是實例化的哪個類.
在 Activity.java 6955 行, 看到 mWindowManager 是在這里被賦值的.
mWindowManager = mWindow.getWindowManager();
.
接著跟進去, 看 mWindowManager 在 Window.java 769 行中被賦值.
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
那么現(xiàn)在就知道了, WindowManagerImpl 就是 ViewManager 子類的子類 . (WindowManagerImpl 繼承自 WindowManager, WindowManager 是一個接口, 又繼承自 ViewManager),
至此, 我們得知, vm 其實可以看做是 WindowManagerImpl 對象
接著看 WindowManager.LayoutParams l = r.window.getAttributes();
這個比較簡單, 代碼跟進去發(fā)現(xiàn)就是 獲取 PhoneWindow 的窗口屬性
?
?
第三部分
wm.addView(decor, l);
調(diào)用 WindowManagerImpl.addView 方法, 傳入 decorView 和 PhoneWindow 的窗口屬性.
?
?
1.3 WindowManagerImpl.addView(View, ViewGroup.LayoutParams)
WindowManagerImpl.java 90 行
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
又調(diào)用了 WindowManagerGlobal 的addView 方法, 并且傳入DecorView 與 params
?
?
1.4 WindowManagerGlobal.addView(View, ViewGroup.LayoutParams, Display, Window )
WindowManagerGlobal.java 278行
//管理所有Activity 的 ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//管理所有 Activity 的 DecorView
private final ArrayList<View> mViews = new ArrayList<View>();
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
...
synchronized (mLock) {
...
ViewRootImpl root;
...
//初始化 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
//添加 DecorView 到 DecorView 集合
mViews.add(view);
//添加 ViewRootImpl 到集合
mRoots.add(root);
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
throw e;
}
}
}
WindowManager 維護著所有 Activity 的 DecorView 和 ViewRootImpl .這里初始化了一個 ViewRootImpl 又使用 ViewRootImpl 調(diào)用了 setView, 也傳入了 DecorView 和經(jīng)過轉(zhuǎn)換的 WindowManager.LayoutParams.
?
?
1.5 ViewRootImpl.setView(View , WindowManager.LayoutParams , View)
ViewRootImpl.java 632 行
View mView;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
//異步刷新View
synchronized (this) {
if (mView == null) {
mView = view;
....
requestLayout();
...
view.assignParent(this);
...
}
}
}
首先把 DecorView 賦值給 ViewRootImpl 類成員變量 mView. 這里需要記住 mView 就是 DecorView.
然后調(diào)用了 ViewRootImpl 類 方法 requestLayout()
, 請求對頁面進行布局, 對View 完成異步刷新, 在其內(nèi)部執(zhí)行 View 的繪制方法., 再去看 requestLayout()
方法之前, 先看一下 view.assignParent(this);
這個方法將 ViewRootImpl 對象 this 作為參數(shù)調(diào)用了 View的 assignParent()
.
View.java 16834 行
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"+ " it already has a parent");
}
}
- 參數(shù)是 ViewParent, 而 ViewRootImpl 實現(xiàn)了 ViewParent 接口, 所以在這里就將 DecorView 和 ViewRootImpl 綁定起來了.
- 每個Activity 的根布局都是 DecorView, 而 DecorView 的 Parent 又是 ViewRootImpl, 所以在子 View 里執(zhí)行 invalidate() 之類的操作,需要循環(huán)找 parent 的時候, 最后都會走到 ViewRootImpl 里.
現(xiàn)在接著看 ViewRootImpl.requestLayout()
方法.
?
?
1.6 ViewRootImpl.requestLayout()
ViewRootImpl.java 1153 行
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
請求對頁面進行布局, 對View 完成異步刷新, 在其內(nèi)部執(zhí)行 View 的繪制方法.
checkThread();
校驗當(dāng)前所在的線程
scheduleTraversals()
, (當(dāng)我們自定義 View 調(diào)用 invalidate 的時候, 其實最后也是調(diào)用了這個方法), 這個方法是屏幕刷新的關(guān)鍵. 一起去一探究竟.
?
?
1.7 ViewRootImpl.scheduleTraversals()
Traversals /tr??v?rs(?)l/ 遍歷的意思
ViewRootImpl.java 1354 行
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
這里看到調(diào)用了 mChoreographer.postCallback
里面?zhèn)魅肓巳齻€參數(shù), 第二個參數(shù)是一個 Runnable 對象, 繼續(xù)跟蹤到這個 Runnable 中.
記住這個 boolean類型的變量mTraversalScheduled
記住這個方法 postSyncBarrier()
這兩個還有下面的 removeSyncBarrier()
將在以后另起一章來講解 Choreographer 的調(diào)用時機
?
?
1.8 ViewRootImpl.mTraversalRunnable
ViewRootImpl 6730 行
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
這個 Runnable 在 run 中調(diào)用了 doTraversal() 方法, 繼續(xù)跟蹤
?
?
1.9 ViewRootImpl.doTraversal()
ViewRootImpl.java 1377 行
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
//最最關(guān)鍵的方法
performTraversals();
...
}
}
boolean 類型變量 mTraversalScheduled
在1.7 中設(shè)置為 true, 并且調(diào)用了 postSyncBarrier()
,
這里設(shè)置為了 false, 調(diào)用了 removeSyncBarrier()
.
現(xiàn)在進入最最最關(guān)鍵的方法 performTraversals()
?
?
1.10 ViewRootImpl.performTraversals() 從這里開始執(zhí)行三個流程.
ViewRootImpl.java 1576 行
private void performTraversals() {
...
if (...) {
...
if (...) {
if (...) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
//測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
layoutRequested = true;
}
}
} else {
...
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
//擺放
performLayout(lp, mWidth, mHeight);
...
}
...
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
...
//繪制
performDraw();
} else {
...
}
...
}
performMeasure()
執(zhí)行測量
performLayout()
執(zhí)行擺放
performDraw()
執(zhí)行繪制
跟蹤到這里, 終于看到了這三個方法. 這個方法的邏輯很復(fù)雜, 每次都會根據(jù)一些狀態(tài)來判斷走哪個流程, 有時候可能只執(zhí)行某一個, 有時候可能三個都執(zhí)行或者兩個. 但是不管哪個流程, 都會遍歷一遍View 樹, 因此 View 的繪制是需要遍歷很多次 View 樹, 如果界面非常復(fù)雜, 耗時就會久一點. 當(dāng)然這三個流程在遍歷時, 也不一定都會遍歷View 樹, ViewGroup 在傳遞的時候, 還會根據(jù)響應(yīng)的狀態(tài)判斷是否繼續(xù)向下傳遞.
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
getRootMeasureSpec
: 根據(jù) window 的布局參數(shù)計算出 root view 的 measure.
(可以理解為: 基于 phonewindow 的 layout parms, 算出 DecorView 的 measureSpec )
mWidth 和 mHeight 是 window (也就是 PhoneWindow) 的高度和寬度.
lp 是 window 的 LayoutParams 值. lp.width 和 lp.height 默認(rèn)都是 MATCH_PARENT,
getRootMeasureSpec()
中又調(diào)用了 makeMeasureSpec()
將 高度和寬度值與 MATCH_PARENT 傳入. 組合后返回一個采用32位存儲的整型值 measureSpec.
根據(jù)在 Android View 的繪制流程 - 開篇 MeasureSpec 中所學(xué)習(xí)的.
現(xiàn)在已經(jīng)得到了 DecorView 的 MeasureSpec了, 包含的模式與值分別如下.
MeasureSpec | 模式 | 大小 |
---|---|---|
childWidthMeasureSpec | MeasureSpec.EXACTLY | window 的寬度值 |
childHeightMeasureSpec | MeasureSpec.EXACTLY | window 的高度值 |
下一章將會開始學(xué)習(xí) performMeasure() 流程.
?
?
擴展知識
- 知識點1
為什么我們在 Activity.onCreate 與 onResume 中獲取不到 View 寬高 ?
看過上面的流程, 是不是知道了, 打開一個Activity, 當(dāng)它的 onCreate 和 onResume 執(zhí)行完后, 才會將它的 DecorView 與新建的一個 ViewRootImpl 綁定起來, 同時開始執(zhí)行測量, 擺放, 繪制等流程. 執(zhí)行完測量后, 我們自己 View 中控件才能寬高等屬性. 所以, 都還沒有進行測量, 甚至連 ViewRootImpl 都沒創(chuàng)建,(在1.4中創(chuàng)建的) 怎么會有寬高呢.
還能得到一個信息是, Activity 界面的繪制, 是在 onResume 之后.
- 知識點2
當(dāng)我們調(diào)用 View 的 invalidate() 方法, 執(zhí)行重繪的時候, 內(nèi)部也是要層層走到 ViewRootImpl 的 scheduleTraversals 方法里去. 然后這個方法會將遍歷繪制 View 樹的操作 preformTraversals() 封裝到 Runnable 中. 傳給 Chorerographer, 以當(dāng)前的時間戳放進一個 mCallbackQueue 隊列中, 然后調(diào)用了 native 層方法向底層注冊監(jiān)聽下一個屏幕刷新信號事件.