在個人系列文章上一篇:App 居然是這樣跑起來的 —— Android App/Activity 啓動流程分析中已經分析了一個 App 從點擊它的圖標到 Activity 的 onCreate()、onStart() 和 onResume() 等生命週期被調用的整個流程。咱們都知道,普通 App 屏幕上顯示的內容都是由一個個本身設計的界面被系統加載而來的,而這些界面中的元素又是怎麼被渲染出來的呢?本文將繼續基於 Android Nougat 從源碼的角度來進一步分析整個過程。java
在開始以前,回顧一下上一篇文章中分析的從 ActivityThread 到 Activity 過程的時序圖:android
如上圖所示,在 Activity 的 onCreate()、onStart() 和 onResume() 等生命週期被調用以前,它的 attach() 方法將會先被調用,所以,咱們將 attach() 方法做爲這篇文章主線的開頭:canvas
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window) {
attachBaseContext(context);
...
// mWindow 是一個 PhoneWindow 對象實例
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
// 調用 Window 的 setWindowManager 方法
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
// 從 Window 中獲取 WindowManager
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
複製代碼
mWindow 是一個 Window 類型的變量,在 attach() 方法中,建立了一個 PhoneWindow 對象實例並賦值給了 mWindow,PhoneWindow 直接繼承自 Window 類。而後調用了 Window 的 setWindowManager() 方法:app
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
...
// mWindowManager 就是 WindowManagerImpl 對象的實例
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
複製代碼
所以,Acitvity 中的 mWindow 變量就是 PhoneWindow 類的實例,而 mWindowManager 是 WindowManagerImpl 類的實例,attach() 方法的主要工做就是初始化這兩個變量。less
接下來到了 onCreate 方法,咱們都知道,若是想要讓本身設計的 layout 佈局文件或者 View 顯示在 Activity 中,必需要在 Activity 的 onCreate() 方法中應該調用 setContentView() 方法將咱們的佈局 id 或者 View 傳遞過去,查看其中一個 setContentView() 方法:ide
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
複製代碼
繼續查看 PhoneWindow 類的 setContentView() 方法:oop
public void setContentView(int layoutResID) {
if (mContentParent == null) {// 是否首次調用
// 初始化 Decor
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// 轉場動畫,默認 false
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
// 解析佈局文件
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
複製代碼
若是是首次調用這個方法,則 mContentParent 爲 null,不然若是沒有轉場動畫的話就移除 mContentParent 的所有子 View,繼續跟蹤 installDecor() 方法:源碼分析
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 生成 DecorView 對象
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 調用 generateLayout 方法
mContentParent = generateLayout(mDecor);
...
}
}
}
複製代碼
當 mDecor 爲 null 的時候會調用 generateDecor() 方法建立一個 DecorView 類的實例,DecorView 繼承自 FrameLayout。接下來判斷 mContentParent 是否爲 null(前面已經提到過,首次加載的時候就是 null),若是是則調用 generateLayout() 方法,這個方法就會建立 mContentParent 對象,跟蹤進去:佈局
protected ViewGroup generateLayout(DecorView decor) {
TypedArray a = getWindowStyle();
...
// 經過 WindowStyle 中設置的各類屬性對 Window 進行各類初始化操做
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
....
int layoutResource;
int features = getLocalFeatures();
// 根據設定好的 features 值獲取相應的佈局文件並賦值給 layoutResource
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
// 調用 onResourcesLoaded 方法
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 在 layoutResource 中根據 id:com.android.internal.R.id.content 獲取一個 ViewGroup 並賦值給 contentParent 對象
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
mDecor.finishChanging();
// 返回 contentParent
return contentParent;
}
複製代碼
DecorView 的 onResourcesLoaded() 方法:post
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
mDecorCaptionView = createDecorCaptionView(inflater);
// 解析 layoutResource 文件
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
...
} else {
// 做爲根佈局添加到 mDecor 中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
複製代碼
能夠看到,generateLayout 方法主要分爲四個部分:
關於第四點,可能有人會有疑問,爲何根據 id 爲 com.android.internal.R.id.content 就必定能找到對應的 ViewGroup?答案就在前面咱們分析過的 generateLayout() 方法中,這裏會根據設定好的 features 值獲取相應的佈局文件並賦值給 layoutResource,而這全部的佈局文件中都包括了一個 id 爲 content 的 FrameLayout,除此以外有些佈局文件中還可能有 ActiionBar 和 Title 等,這些佈局文件存放於該目錄下。以 R.layout.screen_simple 爲例,它的內容以下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarTheme" />
<FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
複製代碼
回到 PhoneWindow 的 setContentView() 方法,執行完 installDecor() 以後,mDecor 被初始化了,同時 mContentParent 也被賦了值, 回到 setContentView() 方法,最後一個重要步驟就是經過 mLayoutInflater.inflater 將咱們的 layout 佈局文件壓入 mDecor 中 id 爲 content 的 FrameLayout 中。
public void setContentView(int layoutResID) {
if (mContentParent == null) {// 是否首次調用
// 初始化 Decor
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// 轉場動畫,默認 false
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
// 解析佈局文件
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
複製代碼
至此,setContentView() 方法的流程就走完了,整體來看分爲三個步驟:
這個過程的時序圖以下:
Activity、PhoneWindow、DecorView 和 ContentView 的關係以下圖所示:
可是,此時咱們的佈局尚未顯示出來,接着往下看。
在開篇的時序圖中咱們能夠看到,ActivityThread 在 handleLaunchActivity() 方法中間接調用了 Activity 的 attach() 和 onCreate():
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
WindowManagerGlobal.initialize();
// 執行 performLaunchActivity 方法
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
// 執行 handleResumeActivity 方法,最終調用 onStart 和 onResume 方法
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
r.paused = true;
}
} else {
// 中止該 Activity
ActivityManagerNative.getDefault()
.finishActivity(r.token, Activity.RESULT_CANCELED, null, false);
}
}
複製代碼
接着會調用 handleResumeActivity() 方法:
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
// 最終會調用 onStart() 和 onResume() 等方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
// 獲取 DecorView
View decor = r.window.getDecorView();
// 將 DecorView 設置成不可見
decor.setVisibility(View.INVISIBLE);
// 獲取 ViewManager,這裏是 WindowManagerImpl 實例
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
...
if (a.mVisibleFromClient && !a.mWindowAdded) {
// 標記設置爲 true
a.mWindowAdded = true;
// 調用 WindowManagerImpl 的 addView 方法
wm.addView(decor, l);
}
} else if (!willBeVisible) {
...
}
...
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
...
if (r.activity.mVisibleFromClient) {
// 調用 makeVisible 方法將 DecorView 設置爲可見
r.activity.makeVisible();
}
}
...
} else {
try {
// 在此過程出現異常,則直接殺死 Activity
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
複製代碼
執行完 performResumeActivity() 方法以後,接着會取出 ActivityClientRecord 中的 Activity 對象,並獲得以前在 setContentView 流程中初始化好的 DecorView 對象,而後會將它做爲參數傳入 ViewManager 類型的對象 wm 的 addView 方法,ViewManager 是一個接口,那麼它是由誰來實現的呢?這裏先打個岔,回頭看一下 Activity 的 attach() 方法:
final void attach(...) {
...
// mWindow 是一個 PhoneWindow 對象
mWindow = new PhoneWindow(this, window);
...
// 調用 Window 的 setWindowManager 方法
mWindow.setWindowManager(...);
...
}
複製代碼
跟蹤 Window 的 setWindowManager() 方法:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
...
// mWindowManager 就是 WindowManagerImpl 對象的實例
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
複製代碼
所以,回到主線,Activity 的 getWindowManager() 獲取到的就是 WindowManagerImpl 對象的實例,再看它的 addView() 方法:
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// mGlobal 是 WindowManagerGlobal 對象的實例
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
複製代碼
mGlobal 是 WindowManagerGlobal 對象的實例,查看它的 addView() 方法:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
...
mRoots.add(root);
...
}
try {
// 將傳過來的 DecorView 添加到 ViewRootImpl 中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
}
}
複製代碼
DecorView 添加到 ViewRootImpl 以後,便和 ViewRootImpl 創建了聯繫,再看 ViewRootImpl 的 setView() 方法:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
requestLayout();
...
view.assignParent(this);
...
}
}
}
複製代碼
跟蹤 ViewRootImpl 的 requestLayout() 方法:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
複製代碼
跟蹤 ViewRootImpl 的 scheduleTraversals() 方法:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 設置同步障礙,暫停處理後面的同步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 在下一幀到來的時候執行 mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
複製代碼
首先,設置同步障礙,暫停處理後面的同步消息,而後利用 Choreographer 類在下一繪製幀來臨的時候執行 mTraversalRunnable 對象(關於 Choreographer 原理,可查看Android系統Choreographer機制實現過程)。mTraversalRunnable 是一個 Runnable 對象:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
複製代碼
run() 方法裏面只有一句代碼,doTraversal() 方法以下:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步障礙
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
// 正式進入 View 繪製流程
performTraversals();
...
}
}
複製代碼
移除了同步障礙以後,全部繪製以前的準備工做已經執行完畢,接下來會調用performTraversals() 方法正式進入 View 的繪製流程。
這裏不要忘了,handleResumeActivity() 方法最後還有一句代碼還沒有執行:
r.activity.makeVisible();
別急,主線流程還沒走完呢。
這一步驟主要的工做內容是:
這個過程的時序圖以下:
接上一步驟的內容,繪製前的準備工做完成後,ViewRootImpl 的 performTraversals() 方法將會被調用,這個方法內容至關多,忽略條件判斷,精簡事後以下:
private void performTraversals() {
...
WindowManager.LayoutParams lp = mWindowAttributes;
...
// 獲取 DecorView 寬和高的 MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// 執行 Measure 流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
// 執行 Layout 流程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
// 執行 Draw 流程
performLayout();
...
}
複製代碼
首先,根據 getRootMeasureSpec() 方法獲取到 childWidthMeasureSpec 和 childHeightMeasureSpec 的值,用於 DecorView 的繪製。由於 DecorView 是全部子元素的根元素,子元素的佈局層層嵌套,所以會接着從 DecorView 開始進行一層層地對全部子元素進行測量、佈局和繪製,分別對應 performMeasure()、performLayout() 和 performLayout() 方法,整個過程的示意圖以下:
MeasureSpec 是 View 的一個內部類,簡單來講就是一個 32 位的 int 值,採用它的高 2 位表示三種 SpecMode(測量模式),低 30 位用來表示 SpecSize(某種測量模式下的規格大小)。採用這種表示方法是爲了不建立過多的對象以減小內存分配,MeasureSpec 的定義以下:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// 不限定測量模式:父容器不對 View 做任何限制,View 想要多大給多大,
// 這種模式一般用於系統內部。
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// 精確爲 SpecSize:父容器已經肯定 View 須要的大小,就是 SpecSize,
// 對應佈局參數是 match_parent 或具體數值時的狀況
public static final int EXACTLY = 1 << MODE_SHIFT;
// 最大隻能是 SpecSize:父容器規定 View 最大隻能是 SpecSize,
// 對應佈局參數是 wrap_content 時的狀況
public static final int AT_MOST = 2 << MODE_SHIFT;
// 根據 SpecMode 和 SpecSize 建立一個 MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
// 獲取 SpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
// 獲取 SpecSize
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
// 調整 MeasureSpec
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
if (mode == UNSPECIFIED) {
return make MeasureSpec(0, UNSPECIFIED);
}
int size = getSize(measureSpec) + delta;
if (size < 0) {
size = 0;
}
return makeMeasureSpec(size, mode);
}
}
複製代碼
接着看 getRootMeasureSpec() 方法,傳入的第一個參數是整個屏幕的寬或者高,第二個參數是 Window 的 LayoutParams:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
複製代碼
從代碼邏輯來看,DecorView 的 MeasureSpec 的產生遵循以下規律:
可是對於普通的 View 來講,View 的 measure() 方法是由父容器 ViewGroup 調用的,看一下 ViewGroup 的 measureChildWithMargins() 方法:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
// 獲取子元素的佈局參數
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 產生子元素的 MeasureSpec
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
複製代碼
能夠看出,在調用子元素 的 measure() 方法以前,先要調用 getChildMeasureSpec() 方法產生子元素的 MeasureSpec,子元素的產生除了跟父容器的 MeasureSpec 和子元素自己的 LayoutParams 有關以外,還與子元素的 Margin 和父容器的 Padding 值以及父容器當前佔用空間有關,具體的過程能夠看 getChildMeasureSpec() 方法:
public static int getChildMeasureSpec(int spec, int padding, int childDimesion) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// 子元素最大可用空間爲父容器的尺寸減去父容器中已被佔用的空間的大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (sepcMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimesion == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size....
// find out how big it should be
resultSize = 0;
resultMode == MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
複製代碼
整個過程稍微有點複雜,能夠參考如下表格:
回到 performTraversals() 方法中,獲取到 DecorView 的 MeasureSpec 後接着會調用 performMeasure() 方法:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
複製代碼
mView 就是以前經過 setView() 方法傳遞過來的 DecorView 實例,它繼承自 FrameLayout,而 FrameLayout 又是一個 ViewGroup 同時繼承自 View。View 的 measure() 方法是 final 類型的,不容許子類去重寫,所以這裏調用的其實是 View 的 measure 方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
複製代碼
View 的 onMeasure() 實現:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
複製代碼
能夠看到,View 默認的 onMeasure() 方法首先會調用 getDefaultSize() 獲取寬和高的默認值,而後再調用 setMeasuredDimension() 將獲取的值進行設置,查看 getDefaultSize() 的代碼:
public static int getDefaultSize(int size, int measureSpec) {
int result = 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;
}
複製代碼
不管是 AT_MOST 仍是 EXACTLY,最終返回的都是 measureSpec 中的 specSize,這個 specSize 就是測量後的最終結果。至於 UNSPECIFIED 的狀況,則會返回一個建議的最小值,這個值和子元素設置的最小值它的背景大小有關。
從 onMeasure() 的默認實現能夠看出,若是咱們自定義一個直接繼承自 View 的控件若是不重寫 onMeasure() 方法,在使用這個控件並把 layout_width 或 layout_height 設置成 wrap_content 的時候,效果將會和 match_parent 同樣!由於佈局中使用 wrap_content 的時候,根據上面 getChildMeasureSpec() 方法總結出來的表格能夠知道,此時的 specMode 是 AT_MOST,specSize 是 parentSize,而 parentSize 是父容器當前剩餘的空間大小,此時 getDefaultSize() 就會返回 specSize,所以子元素的寬或高就被設置成了等於當前父容器剩餘空間的大小了,這顯然不符合咱們的預期,如何解決這個問題呢?一個通用的方案就是像以下方式重寫 onMeasure() 方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 默認的寬/高
int mWidth = default_width;
int mHeight = default_height;
// 當佈局參數設置爲 wrap_content 時,使用默認值
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, mHeight);
// 寬 / 高任意一個佈局參數爲 wrap_content 時,都使用默認值
} else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, heightSize);
} else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(widthSize, mHeight);
}
}
複製代碼
由於 DecorView 繼承自 FrameLayout,它是一個 ViewGroup,ViewGroup 是一個抽象類,它並無定義一個具體的測量過程,默認使用 View 的 onMeasure() 進行測量。它的測量過程須要各個子類經過重寫 onMeasure() 方法去實現,由於不一樣的子類具備不一樣的佈局特性,所以須要不同的測量邏輯,DecorView 也天然重寫了 onMeasure() 方法來實現本身的測量邏輯,簡略後方法內容以下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
final int widthMode = getMode(widthMeasureSpec);
final int heightMode = getMode(heightMeasureSpec);
...
if (widthMode == AT_MOST) {
...
}
...
if (heightMode == AT_MOST) {
...
}
...
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
複製代碼
最終會調用父類 FrameLayout 的 onMeasure() 方法,簡略後方法內容以下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
...
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
...
}
}
...
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
...
}
複製代碼
能夠看到,這裏會遍歷它的每個子元素,並調用 measureChildWithMargins() 方法,這個方法其實前面已經出現過,它的做用是計算出子元素的 MeasureSpec 後調用子元素自己的 measure() 方法:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
// 獲取子元素的佈局參數
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 產生子元素的 MeasureSpec
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
複製代碼
此時實際上調用的也是 View 的 measure() 方法,從上面的內容能夠知道,子元素的 onMeasure() 方法又會被調用,這樣便實現了層層遞歸地調用到了每一個子元素的 onMeasure() 方法進行測量。
再次回到 performTraversals() 方法,執行完 performMeasure() 遍歷測量全部子元素以後,接着會調用 performLayout() 方法:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
...
// 調用 DecorView 的 layout() 方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
複製代碼
這裏的 getMeasuredWidth() 和 getMeasuredHeight() 就是 DecorView 在前面 Measure 流程中計算獲得的測量值,它們都被做爲參數傳入 layout() 方法中,這裏調用的是 View 的 layout() 方法:
public void layout(int l, int t, int r, int b) {
...
// View 狀態是否發生了變化
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 若是 View 狀態有變化,則從新佈局
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...
}
...
}
複製代碼
setOpticalFrame() 內部也直接調用了 setFrame() 方法,查看 setFrame() 方法的實現:
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
...
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
// 變量初始化
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
// 更新用於渲染的顯示列表
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
...
if (sizeChanged) {
// 若是 View 大小發生變化,則會在裏面回調 onSizeChanged 方法
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
...
}
// 返回是否發生變化
return changed;
}
複製代碼
setFrame() 方法的主要做用有如下幾點:
回到 layout() 方法,根據 setFrame() 方法返回的狀態判斷是否須要調用 onLayout() 進行從新佈局,查看 onLayout() 方法:
/** * Assign a size and position to a view and all of its * descendants * * <p>This is the second phase of the layout mechanism. * (The first is measuring). In this phase, each parent calls * layout on all of its children to position them. * This is typically done using the child measurements * that were stored in the measure pass().</p> * * <p>Derived classes should not override this method. * Derived classes with children should override * onLayout. In that method, they should * call layout on each of their children.</p> */
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
複製代碼
onLayout() 方法是一個空方法,從註釋最後一段內容能夠了解到,單一的 View 並不須要重寫這個方法,當 View 的子類具備子元素(即 ViewGroup)的時候,應該重寫這個方法並調用每一個子元素的 layout() 方法,所以做爲一個 ViewGroup,咱們查看 DecorView 的 onLayout() 方法:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
...
}
複製代碼
這裏的主要邏輯是調用父類的 onLayout() 方法,繼續跟蹤 FrameLayout 的 onLayout() 方法:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
// 調用每一個子元素的 layout 方法
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
複製代碼
在 layoutChildren() 方法中,根據本身的佈局邏輯,計算出每一個子元素的 left、top、right 和 bottom 值,並調用它們的 layout() 方法。
Layout 流程的做用是 ViewGroup 用來肯定它的子元素的位置, 當 ViewGroup 的位置被肯定後,在它的 onLayout() 方法中就會遍歷調用全部子元素的 layout() 方法,子元素的 layout() 方法被調用的時候它的 onLayout() 方法又會被調用,這樣就實現了層層遞歸。
最後,又一次回到主線中的 performTraversals() 方法,此時,通過 Measure 流程肯定了每一個 View 的大小而且通過 Layout 流程肯定了每一個 View 的擺放位置,下面將進入下一個流程肯定每一個 View 的具體繪製細節。查看 performDraw() 方法內容:
private void performDraw() {
...
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
}
...
}
複製代碼
跟蹤 draw() 方法:
private void draw(boolean fullRedrawNeeded) {
...
// 「髒」區域,即須要重繪的區域
final Rect dirty = mDirty;
...
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
...
// 調用 drawSoftware 方法
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
...
}
複製代碼
查看 drawSoftware() 方法實現:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
// 鎖定canvas區域,由 dirty 區域決定
canvas = mSurface.lockCanvas(dirty);
...
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
try {
...
try {
...
// 調用 DecorView 的 draw 方法
mView.draw(canvas);
...
} finally {
...
}
} finally {
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}
return true;
}
複製代碼
繼續查看 DecorView 的 draw() 方法:
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
複製代碼
主要是調用了父類的 draw 方法,FrameLayout 和 ViewGroup 都沒有重寫 draw() 方法,因此直接看 View 的 draw() 方法:
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
/* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
複製代碼
從代碼註釋中能夠看到,draw() 過程分爲六個步驟,分別是:
其中第二步和第五步通常狀況下不會用到,咱們繼續看第三步,看看它是如何分配子元素的繪製的:
protected void dispatchDraw(Canvas canvas) {
}
複製代碼
顯然,單一的 View 並無子元素,所以,看看 ViewGroup 是怎麼實現這個過程的:
@Override
protected void dispatchDraw(Canvas canvas) {
...
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
...
}
...
}
...
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
複製代碼
在 ViewGroup 的 dispatchDraw() 中,遍歷每一個子元素並調用了它們的 draw() 方法,由此實現了層層遞歸調用,最終完成繪製。
縱觀整個 Measure、Layout 和 Draw 過程,使用流程圖表示以下:
不知道你還記不記得,上一步驟中執行的 View 的 Measure、Layout 和 Draw 流程都是前面 handleResumeActivity() 中的 wm.addView() 方法爲源頭的,回顧 handleResumeActivity() 方法:
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
// 最終會調用 onStart() 和 onResume() 等方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
// 獲取 DecorView
View decor = r.window.getDecorView();
// 將 DecorView 設置成不可見
decor.setVisibility(View.INVISIBLE);
// 獲取 ViewManager,這裏是 WindowManagerImpl 實例
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
...
if (a.mVisibleFromClient && !a.mWindowAdded) {
// 標記設置爲 true
a.mWindowAdded = true;
// 調用 WindowManagerImpl 的 addView 方法
wm.addView(decor, l);
}
} else if (!willBeVisible) {
...
}
...
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
...
if (r.activity.mVisibleFromClient) {
// 調用 makeVisible 方法將 DecorView 設置爲可見
r.activity.makeVisible();
}
}
...
} else {
try {
// 在此過程出現異常,則直接殺死 Activity
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
複製代碼
能夠看到在調用 wm.addView() 方法以前,DecorView 是處於不可見的狀態的,所以,即便通過了 Measure、Layout 和 Draw 流程,咱們的 View 仍然沒有顯示在屏幕上,看看 Activity 的 makeVisible() 方法:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
複製代碼
執行 DecorView 的 setVisibility() 以後,咱們的 View 才正式出如今屏幕上!
系列文章
按下電源鍵後居然發生了這一幕 —— Android 系統啓動流程分析
App 居然是這樣跑起來的 —— Android App/Activity 啓動流程分析
屏幕上內容到底是怎樣畫出來的 —— Android View 工做原理詳解(本文)
參考文章
(3)自定義View Layout過程 - 最易懂的自定義View原理系列
若是你對文章內容有疑問或者有不一樣的意見,歡迎留言,咱們一同探討。