昨天被人問起Activity的啓動過程,我一陣心虛,說實話,N年前看過一回別人寫的文章,可是本身歷來沒有跟着源碼去研究過Activity的啓動過程,因此別人問到後,我只能把從PhoneWindow到DecorView到SetContentView到ViewRootImpl調用performTraversals()方法,再調用其內部的performMeasure()、performLayout()、performDraw(),從而將佈局文件繪製並加載到父類爲FrameLayout的DecorView上,這個過程雖然沒什麼打錯,可是這個其實只是View的繪製流程分支,與Activity的界面加載有部分重合,真正的Activity啓動後,界面加載的流程是要比這個複雜的,懷着慚愧的心情,今天趕忙打開AndroidStudio,從源碼開始,一點一點的扣整個啓動過程。php
不過這裏先說一下,我沒法從Activity的StartActivity()方法開始講,由於這部份內容特別多,並且至關複雜,我尚未徹底吃透,因此個人源碼分析過程是從ActivityThread的StartActivityNow開始講解並分析的,若是這個過程很熟悉,只是爲了看前半部分的朋友,能夠轉戰到這篇文章下:java
(Android 9.0)Activity啓動流程源碼分析android
如今開始分析,首先打開android.app.ActivityThread類,找到startActivityNow()git
package android.app.ActivityThread public final Activity startActivityNow(Activity parent, String id, Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, Activity.NonConfigurationInstances lastNonConfigurationInstances) {
...
return performLaunchActivity(r, null /* customIntent */);
}
複製代碼
這個方法主要的做用就是初始化一些參數後,並調用同類的performLaunchActivity()程序員
package android.app.ActivityThread private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try{
...
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
...
}
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);
...
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
}
複製代碼
首先第7行,先建立一個activity的實例;github
而後第10行調用這個activiy實例的attach方法;bash
而後第16行開始,經過判斷是否啓用了PersistableBundle,來判斷Instrumentation對象mInstrumentation調用哪一個Activity的onCreate()方法,不瞭解PersistableBundle的能夠看這篇文章:app
android-1.0-四大組件-PersistableBundle框架
這裏主要看一下attach方法:ide
package android.app.Activity;
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, ActivityConfigCallback activityConfigCallback) {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
...
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());
}
mWindowManager = mWindow.getWindowManager();
...
}
複製代碼
要了解這裏,首先要了解一下Activity的組成結構:
一個Activity的內部包含一個PhoneWindow,PhoneWindow有包含一個DecorView,DecorView其實就是一個FrameLayout的子類,它的內部又包含了TitleActionBar和ContentView,而attach這個方法,其中一個重要目的就是初始化PhoneWindow對象。
如今回到源碼部分,上面這個方法,我羅列出的代碼主要作了三件事:
1.將Activity.mWindow對象初始化
2.給mWindow設置WindowManager
3.給mWindowManager賦值。
好了,attach方法看完後,咱們回到performLaunchActivity方法裏,如今該mInstrumentation調用callActivityOnCreate方法了:
package com.app.Instrumentation;
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
複製代碼
這裏咱們主要看activity.performCreate(icicle);這行代碼,進入performCreate方法:
package com.app.Activity;
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
if (persistentState != null) {
onCreate(icicle, persistentState);
} else {
onCreate(icicle);
}
}
複製代碼
最後發現,執行的就是onCreate方法,而咱們寫Activity方法的時候,通常都會寫一個setContentView(layoutId)來設置界面佈局,這時咱們再看看setContentView方法:
package com.app.Activity;
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
複製代碼
這個方法其實作兩件事,第一件事是調用getWindow()的setContentView方法,而getWindow()返回的是一個android.app.Window對象,這個對象就是剛剛在attach()中賦值的mWindow成員變量。
後面的initWindowDecorActionBar方法看名字就是初始化DecorView的ActionBar,這也印證了前面咱們講的Activity的框架結構,這個分支就走到這裏,咱們仍是繼續看getWindow().setContentView,因爲Window是一個抽象類,而Window的setContentView方法實際上是一個抽象方法,並無具體的實現,因此咱們要看的是window的子類:PhoneWindow的setContentView方法:
package com.android.internal.policy.PhoneWindow;
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
//若是調用的是同類構造方法:setContentView(View view, ViewGroup.LayoutParams params)的話
//則這裏的代碼是:mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
...
}
複製代碼
知識點:FEATURE_CONTENT_TRANSITIONS是標記當前內容加載有沒有使用過渡動畫,也就是轉場動畫。
首先咱們注意一個變量,mContentParent是一個ViewGroup,而學過自定義View的同窗,確定知道ViewGroup就是FrameLayout,LinearLayout,RelativeLayout等佈局文件的父類,因此這裏有一個執行判斷就是,若是mContentParent不爲空,而且沒有過分動畫就執行mContentParent.removeAllViews();來清理界面,以後經過判斷,沒有過渡動畫後,給mContentParent這個ViewGroup中添加view和佈局文件。
如今咱們來看看installDecor方法:
package com.android.internal.policy.PhoneWindow;
private void installDecor() {
...
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
複製代碼
上面的方法主要作的其實就是初始化DecorView,並將DecorView和PhoneWindow進行關聯,並初始化mContentParent.
看看generateDecor方法:
package com.android.internal.policy.PhoneWindow;
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
複製代碼
這裏注意一下最後new DecorView的時候傳入了this,這就說明DecorView與PhoneWindow確實關聯了,並返回了一個DecorView的實例。
咱們再看看generateLayout方法:
package com.android.internal.policy.PhoneWindow;
protected ViewGroup generateLayout(DecorView decor) {
...
layoutResource = R.layout.screen_title;
...
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
mDecor.finishChanging();
return contentParent;
}
複製代碼
這個方法很長,注意看onResourcesLoaded方法:
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
複製代碼
細節太多就不細說了,這個方法主要是建立mDecorCaptionView,而後將傳遞進來的佈局文件inflate到這個DecorView中去。
再回到generateLayout方法,ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); 而ID_ANDROID_CONTENT的常量值就是:com.android.internal.R.id.content;
而後根據:layoutResource = R.layout.screen_title;咱們打開此佈局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<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:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize" style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title" style="?android:attr/windowTitleStyle" android:background="@null" android:fadingEdge="horizontal" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
複製代碼
發現最下面的FrameLayout的id屬性就是android:id="@android:id/content",也就是說這個FrameLayout其實就是咱們的變量contentParent。
最後放一個前輩對於DecorView的總結:
綜合以上的探究,加上本身的一些思考和猜想。對PhoneWindow作一下小小的總結:
1.一個Activity對應着一個PhoneWindow對象,是一對一的關係,若是從Activity A啓動到Activity B,那麼Activity B會建立一個本身的PhoneWindow對象。
2.PhoneWindow管理着整個屏幕的內容,不包括屏幕最頂部的系統狀態條。因此,PhoneWindow或者Window是與應用的一個頁面所相關聯。
3.PhoneWindow同時管理着ActionBar和下面的內容主題,setContentView()方法是用來設置內容主體的,而setTitle()等其餘方法就是操做ActionBar的,Window中定義的requestFeature()等方法,有不少與ActionBar屬性相關的設置。另外這些方法都是公有方法,顯然是爲了給客戶端程序員調用的,也進一步佐證了這些操做的意義與做用。
4.PhoneWindow本身並非一個視圖(View),它的成員變量mDecor纔是整個界面的視圖,mDecor是在generateLayout()的時候被填充出來的,而actionBar和contentParent兩個視圖都是經過findViewById()直接從mDecor中獲取出來的。
講到這裏,算是把方法installDecor講完了,如今繼續回到代碼塊:com.android.internal.policy.PhoneWindow的setContentView中去繼續從installDecor方法往下看,mContentParent.removeAllViews();簡單的說過了,這裏就不復述了,以後PhoneWindow類的setContentView方法最後經過調用mLayoutInflater.inflate(layoutResID, mContentParent);或者mContentParent.addView(view, params);將咱們的xml或者java View插入到了mContentParent(id爲content的FrameLayout對象)ViewGroup中,最後setContentView還會調用一個Callback接口的成員函數onContentChanged來通知對應的Activity組件視圖內容發生了變化。
如今從新放一下setContentView這個代碼段:
package com.android.internal.policy.PhoneWindow;
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
//若是調用的是同類構造方法:setContentView(View view, ViewGroup.LayoutParams params)的話
//則這裏的代碼是:mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
...
}
複製代碼
注意最後幾行代碼,是由Callback的實例對象調用的onContentChanged方法,進入Callback的源碼咱們得知,Callback就是定義在抽象類Window中的一個接口,而**getCallback()**也僅僅是獲取Callback接口的實例,可是這個Callback具體在哪裏實現的,咱們還得繼續查,這裏分享一下個人查詢方式,我是經過在對應接口上按Ctrl+T的方式羅列出該接口的實現類,以下:
這時咱們就注意到了,一個親切的傢伙就出如今咱們面前了, Activity呀!對呀,若是PhoneWindow沒有實現這個接口,那麼做爲組合類的Activity應該就會實現呀,並且咱們回憶一下Activity的attach方法,呃,不用回憶了,直接貼再貼一次源碼:
package android.app.Activity;
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, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
....
}
複製代碼
注意,mWindow.setCallback(this);這行代碼,這個this不就表明的是Activity自己嗎?那麼cb.onContentChanged();方法不就是Activity的onContentChanged()方法嗎?咱們看一下:
package android.app.Activity;
public void onContentChanged() {
}
複製代碼
Activity的onContentChanged()是一個空方法,這就是說,etContentView()或者addContentView()方法執行完畢時就會調用該方法,那麼咱們知道這個邏輯後,之後有什麼佈局二次變化的需求後,就能夠將組件初始化的代碼,如:findViewById()或者一些參數的初始化等業務代碼放在咱們App對應的Activity重寫的onContentChanged()方法中去,讓系統幫忙回調。
如今來總結一下setContentView作的事:
建立一個DecorView的對象mDecor,該mDecor對象將做爲整個應用窗口的根視圖。
依據Feature等style theme建立不一樣的窗口修飾佈局文件,而且經過findViewById獲取Activity佈局文件該存放的地方(窗口修飾佈局文件中id爲content的FrameLayout)。
將Activity的佈局文件添加至id爲content的FrameLayout內。
題目叫作Activity的加載和顯示,前面講的都是Activity的加載,如今講講Activity的顯示吧,至於爲何會是這個調用順序或執行過程,那個須要單開一篇文章細說,這裏只分析具體的加載和顯示的源碼過程,如今咱們來看Activity中的handleResumeActivity方法:
package android.app.ActivityThread;
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
if (r == null) {
// We didn't actually resume the activity, so skipping any follow-up actions.
return;
}
...
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;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
...
複製代碼
performResumeActivity()這個方法追到底,其主要就是Instrumentation調用了Acitivy的onResume()方法,咱們瞭解就好,而後主要要看的是wm.addView(decor, l);,這裏就是要動真格的了,咱們繼續往下追:
package android.view;
public interface ViewManager {
/** * Assign the passed LayoutParams to the passed View and add the view to the window. * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a * secondary {@link Display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
複製代碼
addView是接口ViewManager的一個方法,可是咱們很詫異的是wm沒記錯的話應該是windowManager的實例啊,怎麼成了ViewManager了?咱們看一下handleResumeActivity的這行代碼:
ViewManager wm = a.getWindowManager();
複製代碼
經過追蹤這個方法的調用,發現其實這裏的設計是這樣的:
package android.view;
@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {
...
}
複製代碼
WindowManager接口繼承了ViewManager接口,從而在加載View時就使用了ViewManager中的addView方法,如今能夠知道的是addView只是一個抽象方法,咱們須要找到WindowManager的實現類,查看addView的源碼而WindowManagerImpl就是WindowManager的實現類,咱們查看這個類的addView方法:
package android.view;
public final class WindowManagerImpl implements WindowManager {
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);
}
...
}
複製代碼
其實本質上調用的是WindowManagerGlobal的addView方法,咱們進去看一下:
package android.view;
public final class WindowManagerGlobal {
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display);
...
root.setView(view, wparams, panelParentView);
...
}
}
複製代碼
這段代碼主要的一個做用是調用了ViewRootImpl的setView方法,咱們繼續追蹤:
package android.view.ViewRootImpl;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
requestLayout();
...
view.assignParent(this);
}
}
複製代碼
首先,此方法會把以前傳進來的參數view賦值給mView,mView其實就是handleResumeActivity中的wm.addView時傳進來的DecorView,而DecorView又是一個FrameLayout,這裏其實就是將setContentView所作的一切初始化操做的DecorView設置成這個Activity的最基礎的視圖框架,具體見代碼:
view.assignParent(this);
而後調用了**requestLayout()**方法來顯示界面內容:
package android.view.ViewRootImpl;
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
複製代碼
先判斷當前線程是否是主線程,而後就調用了**scheduleTraversals()**方法,繼續跟進:
package android.view.ViewRootImpl;
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
複製代碼
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);經過跟進源碼得知其本質就是經過handler發送消息,那麼咱們關注的重點就應該是mTraversalRunnable這個Runnable接口:
package android.view.ViewRootImpl;
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
複製代碼
進doTraversal方法看看:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
複製代碼
繼續跟進performTraversals方法:
private void performTraversals() {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}
複製代碼
這個方法的邏輯很是長,反正我是沒看完,可是咱們能夠注意到,這三個方法和兩個屬性的初始化,其主要做用其實就是對基礎的根節點View進行View的初始化操做,也就是咱們常說的onMeasure(測量)、onLayout(佈局)和onDraw(繪製),而childWidthMeasureSpec和childHeightMeasureSpec主要的做用就是爲測量提供測量規格,這裏具體的內容能夠看個人另外一篇文章:Android自定義View的測量過程詳解
千言萬語的總結不如最後繪成一張圖來的清晰明瞭: