View繪制流程(二)
最近在學習View的繪制流程,看了幾篇不錯的博客(ViewRootImpl的獨白,我不是一個View(布局篇)、Android應用層View繪制流程與源碼分析)自己對照源碼,梳理了一遍。
相關類
- Activity:一個Activity是一個應用程序組件,提供一個屏幕,用戶可以用來交互為了完成某項任務,例如撥號、拍照。
- View:作為所有圖形的基類。
- ViewGroup:對View繼承擴展為視圖容器類。
- Window:它概括了Android窗口的基本屬性和基本功能。(抽象類)
- PhoneWindow:Window的子類。
- DecorView:界面的根View,PhoneWindow的內部類。
- ViewRootImpl:ViewRoot是GUI管理系統與GUI呈現系統之間的橋梁。
- WindowManangerService:簡稱WMS,它的作用是管理所有應用程序中的窗口,并用于管理用戶與這些窗口發生的的各種交互。
View樹的繪制流程是在ViewRootImpl類的performTraversals()方法開始的
ViewRootImpl簡介
ViewRootImpl是View中的最高層級,屬于所有View的根(但ViewRootImpl不是View,只是實現了ViewParent接口),實現了View和WindowManager之間的通信協議。
ViewRootImpl的初始化
WindowManager
繼承ViewManger
,從ViewManager
這個類名來看就是用來對View類進行管理的,從ViewManager
接口中的添加、更新、刪除View的方法也可以看出來WindowManager
對View的管理。
WindowManagerImpl
為WindowManager
的實現類。WindowManagerImpl
內部方法實現都是由代理類WindowManagerGlobal
完成,而WindowManagerGlobal
是一個單例,也就是一個進程中只有一個WindowManagerGlobal
對象服務于所有頁面的View。
public final class WindowManagerGlobal {
/*******部分代碼省略**********/
//所有Window對象中的View
private final ArrayList<View> mViews = new ArrayList<View>();
//所有Window對象中的View所對應的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//所有Window對象中的View所對應的布局參數
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
/*******部分代碼省略**********/
}
WindowManagerGlobal
在其內部存儲著ViewRootImpl
和View
實例的映射關系(順序存儲)。
在Activity的onResume之后,當前Activity的Window對象中的View會被添加在WindowManager中。
public final class ActivityThread {
/*******部分代碼省略**********/
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
/*******部分代碼省略**********/
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
/*******部分代碼省略**********/
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//window的類型:一個應用窗口類型(所有的應用窗口類型都展現在最頂部)。
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//將decor添加在WindowManager中
wm.addView(decor, l);
}
/*******部分代碼省略**********/
} else {
try {
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null, false);
} catch (RemoteException ex) {
}
}
}
}
wm.addView(decor, l);
方法的具體實現是在WindowManager
的代理類WindowManagerGlobal
中
public final class WindowManagerGlobal {
/*******部分代碼省略**********/
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
/*******部分代碼省略**********/
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
//聲明ViwRootImpl
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
/*******部分代碼省略**********/
//創建ViwRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//將Window所對應的View、ViewRootImpl、LayoutParams順序添加在WindowManager中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
try {
//把將Window所對應的View設置給創建的ViewRootImpl
//通過ViewRootImpl來更新界面并完成Window的添加過程。
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
/*******部分代碼省略**********/
}
}
}
創建ViewRootImpl
實例后,將將Window
所對應的View
、ViewRootImpl
、LayoutParams
順序添加在WindowManager
中,然后將Window
所對應的View
設置給創建的ViewRootImpl
: root.setView(view, wparams, panelParentView);
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
/*******部分代碼省略**********/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//ViewRootImpl成員變量view進行復制,以后操作的都是mView。
mView = view;
/*******部分代碼省略**********/
//Window在添加完之前先進行一次布局,確保以后能再接受系統其它事件之后重新布局。
//對View完成異步刷新,執行View的繪制方法。
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//將該Window添加到屏幕。
//mWindowSession實現了IWindowSession接口,它是Session的客戶端Binder對象.
//addToDisplay是一次AIDL的跨進程通信,通知WindowManagerService添加IWindow
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
} catch (RemoteException e) {
/*******部分代碼省略**********/
} finally {
if (restore) {
attrs.restore();
}
}
/*******部分代碼省略**********/
//設置當前View的mParent
view.assignParent(this);
/*******部分代碼省略**********/
}
}
}
}
requestLayout();
方法請求view繪制,其過程主要是在ViewRootImpl
的performTraversals
方法中。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
/*******部分代碼省略**********/
//請求對界面進行布局
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
/*******部分代碼省略**********/
//安排任務
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
//做任務
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
try {
//執行任務
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
}
整個View樹的繪圖流程是在ViewRootImpl
類的performTraversals()
方法(這個方法巨長)開始的,該方法做的執行過程主要是根據之前設置的狀態,判斷是否重新計算視圖大小(measure)
、是否重新放置視圖的位置(layout)
、以及是否重繪 (draw)
,其核心也就是通過判斷來選擇順序執行這三個方法。
private void performTraversals() {
......
//最外層的根視圖的widthMeasureSpec和heightMeasureSpec由來
//lp.width和lp.height在創建ViewGroup實例時等于MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
......
performDraw();
......
}
這里的最外層根視圖是
DecorView
,也就是mView
,在WindowManagerGlobal
中的addview
中傳遞過來的。
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);
break;
......
}
return measureSpec;
}
該方法的是用來測Root View的。上面傳入參數后這個函數走的是
MATCH_PARENT
,使用MeasureSpec.makeMeasureSpec
方法組裝一個MeasureSpec
,MeasureSpec
的specMode
等于EXACTLY,specSize
等于windowSize,也就是為何根視圖總是全屏的原因。
View的測量
ViewRootImpl
調用performMeasure
執行Window對應的View的測量。
- ViewRootImpl的
performMeasure
;- DecorView(FrameLayout)的
measure
;- DecorView(FrameLayout)的
onMeasure
;- DecorView(FrameLayout)所有子View的
measure
;
private fun performMeasure(childWidthMeasureSpec: Int, childHeightMeasureSpec: Int) {
if (mView == null) {
return
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure")
try {
//mView在Activity中為DecorView(FrameLayout)
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW)
}
}
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
......
//final方法,子類不可重寫
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
//回調onMeasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
}
為整個View樹計算實際的大小,然后設置實際的高和寬,每個View控件的實際寬高都是由父視圖和自身決定的。實際的測量是在
onMeasure
方法進行,所以在View的子類需要重寫onMeasure
方法,這是因為measure
方法是final的,不允許重載,所以View子類只能通過重載onMeasure
來實現自己的測量邏輯。
int widthMeasureSpec
:他由兩部分組成,高2位表示MODE,定義在MeasureSpec類(View的內部類)中,有三種類型,MeasureSpec.EXACTLY表示確定大小, MeasureSpec.AT_MOST表示最大大小, MeasureSpec.UNSPECIFIED不確定。低30位表示size,也就是父View的大小。對于系統Window類的DecorVIew對象Mode一般都為MeasureSpec.EXACTLY ,而size分別對應屏幕寬高。對于子View來說大小是由父View和子View共同決定的。
//View的onMeasure默認實現方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure
默認的實現僅僅調用了setMeasuredDimension
,它對View的成員變量mMeasuredWidth
和mMeasuredHeight
變量賦值,measure
的主要目的就是對View樹中的每個View的mMeasuredWidth
和mMeasuredHeight
進行賦值,所以一旦這兩個變量被賦值意味著該View的測量工作結束。
默認的尺寸大小即傳入的參數都是通過getDefaultSize
返回的,我們就看一下該方法的實現。
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
//通過MeasureSpec解析獲取mode與size
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
specMode
等于AT_MOST或EXACTLY就返回specSize
。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
建議的最小寬度和高度都是由View的Background尺寸與通過設置View的
miniXXX
屬性共同決定的。也只有當Mode為MeasureSpec.UNSPECIFIED時才會使用該尺寸。
到此一次最基礎的元素View的measure
過程就完成了。
View實際是嵌套的,而且measure是遞歸傳遞的,所以每個View都需要measure
,能夠嵌套的View都是ViewGroup的子類,所以在ViewGroup中定義了measureChildren
, measureChild
, measureChildWithMargins
方法來對子視圖進行測量,measureChildren
內部實質只是循環調用measureChild
,measureChild
和measureChildWithMargins
的區別就是是否把margin和padding也作為子視圖的大小。ViewGroup本身不調用measureChildWithMargins
和measureChildren
方法,由繼承類通過for循環調用此方法進行子View的測量。下面看一下ViewGroup中稍微復雜的measureChildWithMargins
方法。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//獲取子視圖的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//調整MeasureSpec
//通過這兩個參數以及本身的LayoutParams來共同決定子視圖的測量規則
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//調運子View的measure方法,子View的measure中會回調子View的onMeasure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
該方法就是對父視圖提供的
measureSpec
參數結合子視圖的LayoutParams參數進行了調整,然后再來調用child.measure()
方法,具體通過方法getChildMeasureSpec
來進行參數調整。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//獲取當前Parent View的Mode和Size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//獲取Parent size與padding差值(也就是Parent剩余大小),若差值小于0直接返回0
int size = Math.max(0, specSize - padding);
//定義返回值存儲變量
int resultSize = 0;
int resultMode = 0;
//依據當前Parent的Mode進行switch分支邏輯
switch (specMode) {
// Parent has imposed an exact size on us
//默認Root View的Mode就是EXACTLY
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//如果child的layout_wOrh屬性在xml或者java中給予具體大于等于0的數值
//設置child的size為真實layout_wOrh屬性值,mode為EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果child的layout_wOrh屬性在xml或者java中給予MATCH_PARENT
// Child wants to be our size. So be it.
//設置child的size為size,mode為EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果child的layout_wOrh屬性在xml或者java中給予WRAP_CONTENT
//設置child的size為size,mode為AT_MOST
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
......
//其他Mode分支類似
}
//將mode與size通過MeasureSpec方法整合為32位整數返回
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
getChildMeasureSpec
的邏輯是通過其父View提供的MeasureSpec
參數得到specMode
和specSize
,然后根據計算出來的specMode
以及子View的childDimension
(layout_width或layout_height)來計算自身的measureSpec
,如果其本身包含子視圖,則計算出來的measureSpec
將作為調用其子視圖measure
函數的參數,同時也作為自身調用setMeasuredDimension
的參數,如果其不包含子視圖則默認情況下最終會調用onMeasure
的默認實現,并最終調用到setMeasuredDimension
。
最終決定View的
measure
大小是View的setMeasuredDimension
方法,所以我們可以通過setMeasuredDimension
設定死值來設置View的mMeasuredWidth和mMeasuredHeight的大小,但是一個好的自定義View應該會根據子視圖的measureSpec
來設置mMeasuredWidth和mMeasuredHeight的大小,這樣的靈活性更大
View測量總結
在Activity的onResume
之后,當前Activity的Window對象中的View(DecorView)會被添加在WindowManager中。也就是在ActivityThread的handleResumeActivity
方法中調用wm.addView(decor, l);
將DecorView添加到WindowManager中;
WindowManager繼承ViewManager,它的實現類為WindowManagerImpl,該類中的方法的具體實現是由其代理類WindowManagerGlobal實現的;
在它的addView
方法中會創建ViewRootImpl的實例,然后將Window對應的View(DecorView),ViewRootImpl,LayoutParams順序添加在WindowManager中,最后將Window所對應的View設置給創建的ViewRootImpl,通過ViewRootImpl來更新界面并完成Window的添加過程;
設置view調用的是ViewRootImpl的setView
方法,在該方法中調用requestLayout();
方法來異步執行view的繪制方法;之后將Window添加到屏幕,通過WMS(跨進程通信)
在requestLayout
方法中最終會調用ViewRootImpl的performTraversals();
方法,該方法做的執行過程主要是根據之前設置的狀態,判斷是否重新計算視圖大小(measure)
、是否重新放置視圖的位置(layout)
、以及是否重繪 (draw)
,其核心也就是通過判斷來選擇順序執行這三個方法:performMeasure
、performLayout
、performDraw
;
在performMeasure
方法中調用的是View的measure
方法,該方法是final修飾,不能被子類重寫,在該方法中實際調用的是View的onMeasure
方法,子類可以重寫onMeasure
方法來實現自己的測量規則。
View默認的onMeasure
方法很簡單只是調用了setMeasuredDimension
方法,該方法的作用是給View的成員變量mMeasuredWidth和mMeasuredHeight賦值,View的測量主要就是給這兩個變量賦值,這兩個變量一旦賦值,也就意味著測量過程的結束。
setMeasuredDimension
方法傳入的尺寸是通過getDefaultSize(int size, int measureSpec);
方法返回的,在
getDefaultSize
方法中解析measureSpec的Mode和Size,如果Mode為MeasureSpec.AT_MOST或者MeasureSpec.EXACTLY,最終的size的值為解析后的size;如果Mode為MeasureSpec.UNSPECIFIED,最終的size為建議的最小值=getSuggestedMinimumWidth
,該方法的具體實現為return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
,建議的最小寬度和高度都是由View的Background尺寸與通過設置View的miniXXX
屬性共同決定的
measureSpec是由getRootMeasureSpec
方法決定的:measureSpec = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);
根布局的大小是Window的大小,Window大小是不能改變的,總是全屏的。
View實際是嵌套的,而且measure是遞歸傳遞的,所以每個View都需要measure,能夠嵌套的View都是ViewGroup的子類,所以在ViewGroup中定義了measureChildren
, measureChild
, measureChildWithMargins
方法來對子視圖進行測量,measureChildren
內部實質只是循環調用measureChild
,measureChild
和measureChildWithMargins
的區別就是是否把margin和padding也作為子視圖的大小。
measureChildWithMargins
方法的作用就是對父View提供的measureSpec參數結合子View的LayoutParams參數進行了調整,然后再來調用child.measure()
方法,具體通過方法getChildMeasureSpec
方法來進行參數調整。計算出來自身的measureSpec作為調用其子視圖measure
方法的參數,同時也作為自身調用setMeasuredDimension
的參數,如果其不包含子視圖則默認情況下最終會調用onMeasure
的默認實現,并最終調用到setMeasuredDimension
。
最終決定View的measure大小是View的setMeasuredDimension
方法,該方法就是設置mMeasuredWidth和mMeasuredHeight的大小,ViewGroup在onMeasure
方法調用setMeasuredDimension
之前調整了measureSpec。