起因
昨天被人問起Activity的啟動過程,我一陣心虛,說實話,N年前看過一回別人寫的文章,但是自己從來沒有跟著源碼去研究過Activity的啟動過程,所以別人問到后,我只能把從PhoneWindow到DecorView到SetContentView到ViewRootImpl調用performTraversals()方法,再調用其內部的performMeasure()、performLayout()、performDraw(),從而將布局文件繪制并加載到父類為FrameLayout的DecorView上,這個過程雖然沒什么打錯,但是這個其實只是View的繪制流程分支,與Activity的界面加載有部分重合,真正的Activity啟動后,界面加載的流程是要比這個復雜的,懷著慚愧的心情,今天趕緊打開AndroidStudio,從源碼開始,一點一點的扣整個啟動過程。
不過這里先說一下,我無法從Activity的StartActivity()方法開始講,因為這部分內容特別多,而且相當復雜,我還沒有完全吃透,所以我的源碼分析過程是從ActivityThread的StartActivityNow開始講解并分析的,如果這個過程很熟悉,只是為了看前半部分的朋友,可以轉戰到這篇文章下:
過程
現在開始分析,首先打開android.app.ActivityThread類,找到startActivityNow()
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的實例;
然后第10行調用這個activiy實例的attach方法;
然后第16行開始,通過判斷是否啟用了PersistableBundle,來判斷Instrumentation對象mInstrumentation調用哪個Activity的onCreate()方法,不了解PersistableBundle的可以看這篇文章:
android-1.0-四大組件-PersistableBundle
這里主要看一下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) {
...
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的測量過程詳解
總結
千言萬語的總結不如最后繪成一張圖來的清晰明了: