一、前言
繪制流程可以說是Android進階中必不可少的一個內(nèi)容,也是面試中被問得最多的問題之一。這方面優(yōu)秀的文章也已經(jīng)是非常之多,但是小盆友今天還是要以自己的姿態(tài)來炒一炒這冷飯,或許就是蛋炒飯了??。話不多說,老規(guī)矩先上實戰(zhàn)圖,然后開始分享。
標簽布局

二、我們的目標是啥
其實這篇文章,小盆友糾結(jié)了挺久,因為繪制流程涉及的東西非常之多,并非一篇文章可以寫完,所以這篇文章我先要確定一些目標,防止因為追查源碼過深,而迷失于源碼中,最后導致一無所獲。我們的目標是:
- 繪制流程從何而起
- Activity 的界面結(jié)構(gòu)在哪里開始形成
- 繪制流程如何運轉(zhuǎn)起來
接下來我們就一個個目標來 conquer。
三、繪制流程從何而起
我們一說到繪制流程,就會想到或是聽過onMeasure、onLayout、onDraw這三個方法,但是有沒想過為什么我們開啟一個App或是點開一個Activity,就會觸發(fā)這一系列流程呢?想知道繪制流程從何而起,我們就有必要先解釋 App啟動流程 和 Activity的啟動流程。
我們都知道 ActivityThread 的 main 是一個App的入口。我們來到 main 方法看看他做了什么啟動操作。
ActivityThread 的
main方法是由 ZygoteInit 類中最終通過 RuntimeInit類的invokeStaticMain方法進行反射調(diào)用。有興趣的童鞋可以自行查閱下,限于篇幅,就不再展開分享。
// ActivityThread 類
public static void main(String[] args) {
// ...省略不相關(guān)代碼
// 準備主線程的 Looper
Looper.prepareMainLooper();
// 實例化 ActivityThread,用于管理應用程序進程中主線程的執(zhí)行
ActivityThread thread = new ActivityThread();
// 進入 attach 方法
thread.attach(false);
// ...省略不相關(guān)代碼
// 開啟 Looper
Looper.loop();
// ...省略不相關(guān)代碼
}
進入 main 方法,我們便看到很熟悉的 Handler機制。在安卓中都是以消息進行驅(qū)動,在這里也不例外,我們可以看到先進行 Looper 的準備,在最后開啟 Looper 進行循環(huán)獲取消息,用于處理傳到主線程的消息。
這也是為什么我們在主線程不需要先進行 Looper 的準備和開啟,emmm,有些扯遠了。
回過頭,可以看到夾雜在中間的 ActivityThread 類的實例化并且調(diào)用了 attach 方法。具體代碼如下,我們接著往下走。
// ActivityThread 類
private void attach(boolean system) {
// ...省略不相關(guān)代碼
// system 此時為false,進入此分支
if (!system) {
// ...省略不相關(guān)代碼
// 獲取系統(tǒng)的 AMS 服務的 Proxy,用于向 AMS 進程發(fā)送數(shù)據(jù)
final IActivityManager mgr = ActivityManager.getService();
try {
// 將我們的 mAppThread 傳遞給 AMS,AMS 便可控制我們 App 的 Activity
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
// ...省略不相關(guān)代碼
} else {
// ...省略不相關(guān)代碼
}
// ...省略不相關(guān)代碼
}
// ActivityManager 類
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
// ActivityManager 類
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
// 在這里獲取 AMS 的binder
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
// 這里獲取 AMS 的 proxy,可以進行發(fā)送數(shù)據(jù)
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
我們進入attach 方法,方法內(nèi)主要是通過 ActivityManager 的 getService 方法獲取到了 ActivityManagerService(也就是我們所說的AMS) 的 Proxy,達到與AMS 進行跨進程通信的目的。
文中所說的 Proxy 和 Stub,是以系統(tǒng)為我們自動生成AIDL時的類名進行類比使用,方便講解。Proxy 代表著發(fā)送信息,Stub 代表著接收信息。
在 mgr.attachApplication(mAppThread); 代碼中向 AMS 進程發(fā)送信息,攜帶了一個類型為 ApplicationThread 的 mAppThread 參數(shù)。這句代碼的作用,其實就是把 我們應用的 “控制器” 上交給了 AMS,這樣使得 AMS 能夠來控制我們應用中的Activity的生命周期。為什么這么說呢?我們這就有必要來了解下 ApplicationThread 類的結(jié)構(gòu),其部分代碼如下:
// ActivityThread$ApplicationThread 類
private class ApplicationThread extends IApplicationThread.Stub {
// 省略大量代碼
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
// 會將 AMS 發(fā)來的信息封裝在 ActivityClientRecord 中,然后發(fā)送給 Handler
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = ident;
r.intent = intent;
r.referrer = referrer;
r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
r.persistentState = persistentState;
r.pendingResults = pendingResults;
r.pendingIntents = pendingNewIntents;
r.startsNotResumed = notResumed;
r.isForward = isForward;
r.profilerInfo = profilerInfo;
r.overrideConfig = overrideConfig;
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}
// 省略大量代碼
}
從 ApplicationThread 的方法名,我們會驚奇的發(fā)現(xiàn)大多方法名以 scheduleXxxYyyy 的形式命名,而且和我們熟悉的生命周期都挺接近。上面代碼留下了我們需要的方法 scheduleLaunchActivity ,它們包含了我們 Activity 的 onCreate、onStart 和 onResume。
scheduleLaunchActivity 方法會對 AMS 發(fā)來的信息封裝在 ActivityClientRecord 類中,最后通過 sendMessage(H.LAUNCH_ACTIVITY, r); 這行代碼將信息以 H.LAUNCH_ACTIVITY 的信息標記發(fā)送至我們主線程中的 Handler。我們進入主線程的 Handler 實現(xiàn)類 H。具體代碼如下:
// ActivityThread$H 類
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
// 省略大量代碼
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
// 省略大量代碼
}
}
// 省略大量代碼
}
我們從上面的代碼可以知道消息類型為 LAUNCH_ACTIVITY,則會進入 handleLaunchActivity 方法,我們順著往里走,來到下面這段代碼
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
if (r.profilerInfo != null) {
mProfiler.setProfiler(r.profilerInfo);
mProfiler.startProfiling();
}
// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
// Initialize before creating the activity
WindowManagerGlobal.initialize();
// 獲得一個Activity對象,會進行調(diào)用 Activity 的 onCreate 和 onStart 的生命周期
Activity a = performLaunchActivity(r, customIntent);
// Activity 不為空進入
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
// 該方法最終回調(diào)用到 Activity 的 onResume
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
performPauseActivityIfNeeded(r, reason);
if (r.isPreHoneycomb()) {
r.state = oldState;
}
}
} else {
// If there was an error, for any reason, tell the activity manager to stop us.
try {
ActivityManager.getService()
.finishActivity(r.token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
我們先看這行代碼 performLaunchActivity(r, customIntent); 最終會調(diào)用 onCreate 和 onStart 方法。眼見為實,耳聽為虛,我們繼續(xù)進入深入。來到下面這段代碼
// ActivityThread 類
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// 省略不相關(guān)代碼
// 創(chuàng)建 Activity 的 Context
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// ClassLoader 加載 Activity類,并創(chuàng)建 Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
// 省略不相關(guān)代碼
} catch (Exception e) {
// 省略不相關(guān)代碼
}
try {
// 創(chuàng)建 Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
// 省略不相關(guān)代碼
if (activity != null) {
// 省略不相關(guān)代碼
// 調(diào)用了 Activity 的 attach
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
// 這個 intent 就是我們 getIntent 獲取到的
if (customIntent != null) {
activity.mIntent = customIntent;
}
// 省略不相關(guān)代碼
// 調(diào)用 Activity 的 onCreate
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
// 省略不相關(guān)代碼
if (!r.activity.mFinished) {
// zincPower 調(diào)用 Activity 的 onStart
activity.performStart();
r.stopped = false;
}
if (!r.activity.mFinished) {
// zincPower 調(diào)用 Activity 的 onRestoreInstanceState 方法,數(shù)據(jù)恢復
if (r.isPersistable()) {
if (r.state != null || r.persistentState != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
// 省略不相關(guān)代碼
}
// 省略不相關(guān)代碼
}
// 省略不相關(guān)代碼
return activity;
}
// Instrumentation 類
public void callActivityOnCreate(Activity activity, Bundle icicle,
PersistableBundle persistentState) {
prePerformCreate(activity);
activity.performCreate(icicle, persistentState);
postPerformCreate(activity);
}
// Activity 類
final void performCreate(Bundle icicle) {
restoreHasCurrentPermissionRequest(icicle);
// 調(diào)用了 onCreate
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
// Activity 類
final void performStart() {
// 省略不相關(guān)代碼
// 進行調(diào)用 Activity 的 onStart
mInstrumentation.callActivityOnStart(this);
// 省略不相關(guān)代碼
}
// Instrumentation 類
public void callActivityOnStart(Activity activity) {
// 調(diào)用了 Activity 的 onStart
activity.onStart();
}
進入 performLaunchActivity 方法后,我們會發(fā)現(xiàn)很多我們熟悉的東西,小盆友已經(jīng)給關(guān)鍵點打上注釋,因為不是文章的重點就不再細說,否則篇幅過長。
我們直接定位到 mInstrumentation.callActivityOnCreate 這行代碼。進入該方法,方法內(nèi)會調(diào)用 activity 的 performCreate 方法,而 performCreate 方法里會調(diào)用到我們經(jīng)常重寫的 Activity 生命周期的 onCreate 方法。??至此,找到了 onCreate 的調(diào)用地方,這里需要立個 FLAG1,因為目標二需要的開啟便是這里,我下一小節(jié)分享,勿急。
回過頭來繼續(xù) performLaunchActivity 方法的執(zhí)行,會調(diào)用到 activity 的 performStart 方法,而該方法又會調(diào)用到 mInstrumentation.callActivityOnStart 方法,最后在該方法內(nèi)便調(diào)用了我們經(jīng)常重寫的 Activity 生命周期的 onStart 方法。??至此,找到了 onStart 的調(diào)用地方。
找到了兩個生命周期的調(diào)用地方,我們需要折回到 handleLaunchActivity 方法中,繼續(xù)往下運行,便會來到 handleResumeActivity 方法,具體代碼如下:
// ActivityThread 類
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
// 省略部分代碼
r = performResumeActivity(token, clearHide, reason);
// 省略部分代碼
if (r.window == null && !a.mFinished && willBeVisible) {
// 將 Activity 中的 Window 賦值給 ActivityClientRecord 的 Window
r.window = r.activity.getWindow();
// 獲取 DecorView,這個 DecorView 在 Activity 的 setContentView 時就初始化了
View decor = r.window.getDecorView();
// 此時為不可見
decor.setVisibility(View.INVISIBLE);
// WindowManagerImpl 為 ViewManager 的實現(xiàn)類
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 往 WindowManager 添加 DecorView,并且?guī)?WindowManager.LayoutParams
// 這里面便觸發(fā)真正的繪制流程
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
// 省略不相關(guān)代碼
}
performResumeActivity方法最終會調(diào)用到 Activity 的 onResume 方法,因為不是我們該小節(jié)的目標,就不深入了,童鞋們可以自行深入,代碼也比較簡單。至此我們就找齊了我們一直重寫的三個 Acitivity 的生命周期函數(shù) onCreate、onStart 和 onResume 。按照這一套路,童鞋們可以看看 ApplicationThread 的其他方法,會發(fā)現(xiàn) Activity 的生命周期均在其中可以找到影子,也就證實了我們最開始所說的 我們將應用 “遙控器” 交給了AMS。而值得一提的是,這一操作是處于一個跨進程的場景。
繼續(xù)往下運行來到 wm.addView(decor, l); 這行代碼,wm 的具體實現(xiàn)類為 WindowManagerImpl,繼續(xù)跟蹤深入,來到下面這一連串的調(diào)用
// WindowManagerImpl 類
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// tag:進入這一行
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
// WindowManagerGlobal 類
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 省略不相關(guān)代碼
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 省略不相關(guān)代碼
// 初始化 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
// 將 view 和 param 交于 root
// ViewRootImpl 開始繪制 view
// tag:進入這一行
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
// ViewRootImpl 類
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// 省略不相關(guān)代碼
// 進入繪制流程
// tag:進入這一行
requestLayout();
// 省略不相關(guān)代碼
}
}
}
// ViewRootImpl 類
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
// tag:進入這一行
scheduleTraversals();
}
}
// ViewRootImpl 類
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 提交給 編舞者,會在下一幀繪制時調(diào)用 mTraversalRunnable,運行其run
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
中間跳轉(zhuǎn)的方法比較多,小盆友都打上了 // tag:進入這一行 注釋,童鞋們可以自行跟蹤,會發(fā)現(xiàn)最后會調(diào)用到編舞者,即 Choreographer 類的 postCallback方法。Choreographer 是一個會接收到垂直同步信號的類,所以當下一幀到達時,他會調(diào)用我們剛才提交的任務,即此處的 mTraversalRunnable,并執(zhí)行其 run 方法。
值得一提的是通過 Choreographer 的 postCallback 方法提交的任務并不是每一幀都會調(diào)用,而是只在下一幀到來時調(diào)用,調(diào)用完之后就會將該任務移除。簡而言之,就是提交一次就會在下一幀調(diào)用一次。
我們繼續(xù)來看 mTraversalRunnable 的具體內(nèi)容,看看每一幀都做了寫什么操作。
// ViewRootImpl 類
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
// ViewRootImpl 類
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
// ViewRootImpl 類
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 進入此處
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
// ViewRootImpl 類
private void performTraversals() {
// 省略不相關(guān)代碼
if (!mStopped || mReportNextDraw) {
// 省略不相關(guān)代碼
// FLAG2
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 省略不相關(guān)代碼
// 進行測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 省略不相關(guān)代碼
// 進行擺放
performLayout(lp, mWidth, mHeight);
// 省略不相關(guān)代碼
// 布局完回調(diào)
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
// 省略不相關(guān)代碼
// 進行繪制
performDraw();
}
調(diào)用了 mTraversalRunnable 的 run 方法之后,會發(fā)現(xiàn)也是一連串的方法調(diào)用,后來到 performTraversals,這里面就有我們一直提到三個繪制流程方法的起源地。這三個起源地就是我們在上面看到的三個方法 performMeasure、performLayout 、performDraw 。
而這三個方法會進行如下圖的一個調(diào)用鏈(??還是手繪,勿噴),從代碼我們也知道,會按照 performMeasure、performLayout 、performDraw 的順序依次調(diào)用。
performMeasure 會觸發(fā)我們的測量流程,如圖中所示,進入第一層的 ViewGroup,會調(diào)用 ?measure 和 onMeasure,在 onMeasure 中調(diào)用下一層級,然后下一層級的 View或ViewGroup 會重復這樣的動作,進行所有 View 的測量。(這一過程可以理解為書的深度遍歷)
performLayout 和 performMeasure 的流程大同小異,只是方法名不同,就不再贅述。
performDraw 稍微些許不同,當前控件為ViewGroup時,只有需要繪制背景或是我們通過 setWillNotDraw(false) 設置我們的ViewGroup需要進行繪制時,會進入 onDraw 方法,然后通過 dispatchDraw 進行繪制子View,如此循環(huán)。而如果為View,自然也就不需要繪制子View,只需繪制自身的內(nèi)容即可。

至此,繪制流程的源頭我們便了解清楚了, onMeasure 、 onLayout、onDraw 三個方法我們會在后面進行詳述并融入在實戰(zhàn)中。
四、Activity 的界面結(jié)構(gòu)在哪里開始形成

上圖是 Activity 的結(jié)構(gòu)。我們先進行大致的描述,然后在進入源碼體會這一過程。
我們可以清晰的知道一個 Activity 會對應著有一個 Window,而 Window 的唯一實現(xiàn)類為 PhoneWindow,PhoneWindow 的初始化是在 Activity 的 attach 方法中,我們前面也有提到 attach 方法,感興趣的童鞋可以自行深入。
在往下一層是一個 DecorView,被 PhoneWindow 持有著,DecorView 的初始化在 setContentView 中,這個我們待會會進行詳細分析。DecorView 是我們的頂級View,我們設置的布局只是其子View。
DecorView 是一個 FrameLayout。但在 setContentView 中,會給他加入一個線性的布局(LinearLayout)。該線性布局的子View 則一般由 TitleBar 和 ContentView 進行組成。TitleBar 我們可以通過 requestWindowFeature(Window.FEATURE_NO_TITLE); 進行去除,而 ContentView 則是來裝載我們設置的布局文件的 ViewGroup 了。
現(xiàn)在我們已經(jīng)有一個大概的印象,接下來進行詳細分析。在上一節(jié)中(FLAG1處),我們最先會進入的生命周期為onCreate,在該方法中我們都會寫上這樣一句代碼setContentView(R.layout.xxxx) 進行設置布局。經(jīng)過上一節(jié)我們也知道,真正的繪制流程是在 onResume 之后(忘記的童鞋請倒回去看一下),那么 setContentView 起到一個什么作用呢?我進入源碼一探究竟吧。
進入 Activity 的 setContentView 方法,可以看到下面這段代碼。getWindow 返回的是一個 Window 類型的對象,而通過Window的官方注釋可以知道其唯一的實現(xiàn)類為PhoneWindow, 所以我們進入 PhoneWindow 類查看其 setContentView 方法,這里值得我們注意有兩行代碼。我們一一進入,我們先進入 installDecor 方法。
// Activity 類
public void setContentView(@LayoutRes int layoutResID) {
// getWindow 返回的是 PhoneWindow
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
// Activity 類
public Window getWindow() {
return mWindow;
}
// PhoneWindow 類
@Override
public void setContentView(int layoutResID) {
// 此時 mContentParent 為空,mContentParent 是裝載我們布局的容器
if (mContentParent == null) {
// 進行初始化 頂級View——DecorView 和 我們設置的布局的裝載容器——ViewGroup(mContentParent)
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 加載我們設置的布局文件 到 mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
installDecor 方法的作用為初始化了我們的頂級View(即DecorView)和初始化裝載我們布局的容器(即 mContentParent 屬性)。具體代碼如下
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 會進行實例化 一個mDecor
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) {
// 初始化 mContentParent
mContentParent = generateLayout(mDecor);
// 省略不相關(guān)代碼
}
在 generateDecor 中會進行 DecorView 的創(chuàng)建,具體代碼如下,較為簡單
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
緊接著是generateLayout 方法,核心代碼如下,如果我們在 onCreate 方法前通過requestFeature 進行設置一些特征,此時的 getLocalFeatures 就會獲取到,并根據(jù)其值選擇合適的布局賦值給 layoutResource 屬性。最后將該布局資源解析,賦值給 DecorView,緊接著將 DecorView 中 id 為 content 的控件賦值給 contentParent,而這個控件將來就是裝載我們設置的布局資源。
protected ViewGroup generateLayout(DecorView decor) {
// 省略不相關(guān)代碼
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} 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;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
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;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
// 進行加載 DecorView 的布局
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 這里就獲取了裝載我們設置的內(nèi)容容器 id 為 R.id.content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 省略不相關(guān)代碼
return contentParent;
}
我們折回到 setContentView 方法,來到 mLayoutInflater.inflate(...); 這行代碼,layoutResID 為我們設置的布局文件,而 mContentParent 就是我們剛剛獲取的id 為 content 的控件, 這里便是把他從 xml 文件解析成一棵控件的對象樹,并且放入在 mContentParent 容器內(nèi)。
至此我們知道,Activity 的 setContentView 是讓我們布局文件從xml “翻譯” 成對應的控件對象,形成一棵以 DecorView 為根結(jié)點的控件樹,方便我們后面繪制流程進行遍歷。
五、繪制流程如何運轉(zhuǎn)起來的
終于來到核心節(jié),我們來繼續(xù)分析第三節(jié)最后說到的三個方法onMeasure、onLayout、onDraw,這便是繪制流程運轉(zhuǎn)起來的最后一道門閥,是我們自定義控件中可操作的部分。我們接下來一個個分析
1、onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
要解釋清楚這個方法,我們需要先說明兩個參數(shù)的含義和構(gòu)成。兩個參數(shù)都是 MeasureSpec 的類型
MeasureSpec是什么
MeasureSpec 是一個 32位的二進制數(shù)。高2位為測量模式,即SpecMode;低30位為測量數(shù)值,即SpecSize。我們先看下源碼,從源碼中找到這兩個值的含義。
以下是 MeasureSpec 類的代碼(刪除了一些不相關(guān)的代碼)
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
// 最終結(jié)果為:11 ...(30位)
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// 父View 不對 子View 施加任何約束。 子View可以是它想要的任何尺寸。
// 二進制:00 ...(30位)
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// 父View 已確定 子View 的確切大小。子View 的大小便是父View測量所得的值
// 二進制:01 ...(30位)
public static final int EXACTLY = 1 << MODE_SHIFT;
// 父View 指定一個 子View 可用的最大尺寸值,子View大小 不能超過該值。
// 二進制:10 ...(30位)
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
// API 17 之后,sUseBrokenMakeMeasureSpec 就為 false
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
@MeasureSpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
(1)測量模式
類中有三個常量: UNSPECIFIED 、EXACTLY 、AT_MOST,他們對應著三種測量模式,具體含義我們在注釋中已經(jīng)寫了,小盆友整理出以下表格方便我們查閱。
| 名稱 | 含義 | 數(shù)值(二進制) | 具體表現(xiàn) |
|---|---|---|---|
| UNSPECIFIED | 父View不對子View 施加任何約束,子View可以是它想要的任何尺寸 | 00 ...(30個0) | 系統(tǒng)內(nèi)部使用 |
| EXACTLY | 父View已確定子View 的確切大小,子View的大小為父View測量所得的值 | 01 ...(30個0) | 具體數(shù)值、match_parent |
| AT_MOST | 父View 指定一個子View可用的最大尺寸值,View大小 不能超過該值。 | 10 ...(30個0) | wrap_content |
(2)makeMeasureSpec
makeMeasureSpec 方法,該方法用于合并測量模式和測量尺寸,將這兩個值合為一個32位的數(shù),高2位為測量模式,低30位為尺寸。
該方法很簡短,主要得益于 (size & ~MODE_MASK) | (mode & MODE_MASK) 的位操作符,但也帶來了一定的理解難度。我們拆解下
-
size & ~MODE_MASK剔除 size 中的測量模式的值,即將高2位置為00 -
mode & MODE_MASK保留傳入的模式參數(shù)的值,同時將低30位置為 0...(30位0) -
(size & ~MODE_MASK) | (mode & MODE_MASK)就是 size的低30位 + mode的高2位(總共32位)
至于 &、~、|這三個位操作為何能做到如此的騷操作,請移步小盆友的另一博文——Android位運算簡單講解。(內(nèi)容很簡短,不熟悉這塊內(nèi)容的童鞋,強烈推薦瀏覽一下)
(3)getMode
getMode 方法用于獲取我們傳入的 measureSpec 值的高2位,即測量模式。
(4)getSize
getSize 方法用于獲取我們傳入的measureSpec 值的低30位,即測量的值。
解釋完 MeasureSpec 的是什么,我們還有兩個問題需要搞清楚:
- 這兩個參數(shù)值從哪來
- 這兩個參數(shù)值怎么使用
這兩個參數(shù)值從哪來
借助下面這張簡圖,設定當前運行的 onMeasure 方法處于B控件,則其兩個MeasureSpec值是由其父視圖(即A控件)計算得出,計算的規(guī)則ViewGroup 有對應的方法,即 getChildMeasureSpec。

getChildMeasureSpec 的具體代碼如下。我們繼續(xù)使用上面的情景, B中所獲得的值,是 A使用自身的MeasureSpec 和 B 的 LayoutParams.width 或 LayoutParams.height 進行計算得出B的MeasureSpec。
// ViewGroup 類
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// 父視圖為確定的大小的模式
case MeasureSpec.EXACTLY:
/**
* 根據(jù)子視圖的大小,進行不同模式的組合:
* 1、childDimension 大于 0,說明子視圖設置了具體的大小
* 2、childDimension 為 {@link LayoutParams.MATCH_PARENT},說明大小和其父視圖一樣大
* 3、childDimension 為 {@link LayoutParams.WRAP_CONTENT},說明子視圖想為其自己的大小,但
* 不能超過其父視圖的大小。
*/
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 (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;
// 父視圖已經(jīng)有一個最大尺寸限制
case MeasureSpec.AT_MOST:
/**
* 根據(jù)子視圖的大小,進行不同模式的組合:
* 1、childDimension 大于 0,說明子視圖設置了具體的大小
* 2、childDimension 為 {@link LayoutParams.MATCH_PARENT},
* -----說明大小和其父視圖一樣大,但是此時的父視圖還不能確定其大小,所以只能讓子視圖不超過自己
* 3、childDimension 為 {@link LayoutParams.WRAP_CONTENT},
* -----說明子視圖想為其自己的大小,但不能超過其父視圖的大小。
*/
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;
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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
我們將這段代碼整理成表格
| 子LayoutParams(縱向) \ 父類的SpecMode(橫向) | EXACTLY | AT_MOST | UNSPECIFIED |
|---|---|---|---|
| dp/px (確定的值) | EXACTLY | ||
| ChildSize | EXACTLY | ||
| ChildSize | EXACTLY | ||
| ChildSize | |||
| MATCH_PARENT | EXACTLY | ||
| ParentSize | AT_MOST | ||
| ParentSize | UNSPECIFIED | ||
| 0 | |||
| WRAP_CONTENT | AT_MOST | ||
| ParentSize | AT_MOST | ||
| ParentSize | UNSPECIFIED | ||
| 0 |
所以最終,B的 onMeasure 方法獲得的兩個值,便是 父視圖A 對 B 所做的約束建議值。
你可能會有一個疑惑, 頂級DecorView 的約束哪里來,我們切回 FLAG2 處,在進入 performMeasure 方法時,攜帶的兩個MeasureSpec 是由 WindowManager 傳遞過來的 Window 的 Rect 的寬高 和 Window 的 WindowManager.LayoutParam 共同決定。簡而言之,DecorView的約束從 Window的參數(shù)得來。
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 進行測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
這兩個參數(shù)值怎么使用
我們上面一直提到的一個詞叫做 “建議”,是因為到達B的兩個維度(橫縱)的 MeasureSpec,不是就已經(jīng)決定了控件B的寬高。
這里我們可以類比為 父母總是語重心長的跟自己的孩子說,你要怎么做怎么做(即算出了子View 的 MeasureSpec),懂事的孩子會知道聽從父母的建議可以讓自己少走彎路(即遵循傳遞下來的MeasureSpec約束),而調(diào)皮一點的孩子,覺得打破常規(guī)更加好玩(即不管 MeasureSpec 的規(guī)則約束)。
按照約定,我們是要遵循父View給出的約束。而B控件再進行計算其自己子View的MeasureSpec(如果有子View),子View 會再進行測量 孫View,這樣一層層的測量(這里能感受到樹結(jié)構(gòu)的魅力了吧??)。
B控件完成子View的測量,調(diào)用setMeasuredDimension 將自身最終的 測量寬高 進行設置,這樣就完成B控件的測量流程就完畢了。
2、onLayout
protected void onLayout(boolean changed, int l, int t, int r, int b)
onLayout 則是進行擺放,這一過程比較簡單,因為我們從 onMeasure 中已經(jīng)得到各個子View 的寬高。父View 只要按照自己的邏輯負責給定各個子View 的 左上坐標 和 右下坐標 即可。
3、onDraw
protected void onDraw(Canvas canvas)
繪制流程中,onDraw 應該說是童鞋們最為熟悉的,只要在 canvas 繪制自身需要繪制的內(nèi)容便可以。
六、實戰(zhàn)
上一節(jié)總結(jié)起來,就是我們在面試時總會說的那句話,onMeasure負責測量、onLayout負責擺放、onDraw負責繪制,但理論總是過于空洞,我們現(xiàn)在將理論融入到操作中來。我們用標簽的流式布局來說明進一步解釋這一切。
1、效果圖

Github入口:傳送門
2、編碼思路
在這種標簽流式布局的情景中,我們會往控件TagFlowLayout中放入標簽TextView(當然也可以是更復雜的布局,這里為了方便講清楚思路)。 我們放入四個標簽,分別為 “大Android”、“猛猛的小盆友”、“JAVA”、“ PHP是最好的語言”。
我們借助這張小盆友手繪的流程圖,來講清楚這繪制流程。
(1) onMeasure
最開始,控件是空的,也就是第一幅小圖。
接著將第一個標簽 “大Android” 放入,此時不超出 TagFlowLayout 的寬,如第二幅小圖所示。
然后將第二個標簽 “猛猛的小盆友” 放入,此時如第三幅小圖所示,超出了 TagFlowLayout 的寬, 所以我們進行換行,將 “猛猛的小盆友” 放入第二行。
在接著將第三個標簽 “JAVA” 放入,此時不超出 TagFlowLayout 的寬,如第四幅小圖所示。
最后把剩下的 “PHP是最好的語言” 也放入,當此時有個問題,即使一行放一個也容不下(第五幅小圖),因為 “ PHP是最好的語言” 的寬已經(jīng)超出 TagFlowLayout 的寬,所以我們在給 “PHP是最好的語言” 測量的MeasureSpec時,需要進行“糾正”,使其寬度為 TagFlowLayout 的寬,最終形成了第六幅小圖的樣子。
最后還需要將我們測量的結(jié)果通過 setMeasuredDimension 設置我們自身的 TagFlowLayout 控件的寬高。
(2) onLayout
經(jīng)過 onMeasure ,TagFlowLayout 心中已經(jīng)知道自己的 每個孩子的寬高 和 每個孩子要“站”在哪一行,但具體的坐標還是需要進行計算。
“大Android” 的標簽比較坐標比較容易(我們這里討論思路的時候不考慮padding和margin),(l1,t1) 就是 (0,0),而 (r1,b1) 則是 (0+ width, 0+height)。
“猛猛的小盆友” 的坐標需要依賴 “大Android”,(l2,t2) 則為 (0, 第一行的高度) ,(r2,b2) 為 (自身的Width,第一行的高度+自身的Height)。
“JAVA” 的坐標則需要依賴“猛猛的小盆友” 和 “大Android”, (l3,t3) 為 (“猛猛的小盆友”的Width, 第一行的高度) ,(r3,b3) 為 (“猛猛的小盆友”的Width + 自身的Width, 第一行的高度+自身的Height)。
“PHP是最好的語言” 需要依賴前兩行的總高度,具體看坐標的計算。 (l4,t4) 為 (0,第一行高+第二行高), (r4,b4) 為 (自身的Width,第一行高+第二行高+自身的Height)。
(3) onDraw
這個方法在我們這個控件中不需要,因為繪制的任務是由各個子View負責。確切的說 onDraw 在我們的 TagFlowLayout 并不會被調(diào)用,具體原因我們在前面已經(jīng)說了,這里就不贅述了。
3、小結(jié)
雖然鋪墊了很多,但是 TagFlowLayout 的代碼量并不多,這里也不再粘貼出來,需要的進入傳送門。我們只需要在onMeasure 中進行測量,然后將測量的值進行存儲,最后在 onLayout 依賴測量的結(jié)果進行擺放即可。
七、寫在最后
距離上篇博文的發(fā)布也有接近三個星期了,這次耗時比較久原因挺多,繪制流程涉及的知識點很多,這里講述的只是比較接近于我們開發(fā)者的部分,所以導致小盆友在寫這篇文章的時候有些糾結(jié)。還有另一個原因是小盆友的一些私人事情,需要些時間來平復,但最終也堅持著寫完。如果童鞋們發(fā)現(xiàn)有那些欠妥的地方,請留言區(qū)與我討論,我們共同進步。如果覺得這碗“蛋炒飯”別有一番滋味,給我一個贊吧。
更多大廠面試資料以及Android資料可以進群免費領(lǐng)?。海ˋndroid進階學習⑥群:345659112)
作者:猛猛的小盆友
鏈接:https://juejin.cn/post/6844903807860604935
來源:稀土掘金