目錄
一、Activity、Window、DecorView的層級關系如下圖所示:
1、Activity
2、Window
3、DecorView? ?
二、DecorView初始化相關源碼??
三、DecorView顯示時機
前言: 不同的Android版本有差異,以下基于Android 11進行講解。
一、Activity、Window、DecorView的層級關系如下圖所示:
? ? ? ? 從上圖可以直觀的看到Activity、Window以及DecorView之間的關系,Activity持有window(Phone Window)、而在window里管理著DecorView,我們一般平時開發對Activity樣式的修改,實際上是對DecorView的修改、系統中提供著幾種默認DecorView樣式。?
?1、Activity
對于應用的開發,我們通常操做Activity來創建我們想要的視圖界面,但實際上對視圖的控制并不是Activity,而是Activity持有的Window。每一個Activity包含一個PhoneWindow,PhoneWindow是window的子類。
如上圖所示,在Activity類中的attach方法中創建了PhoneWindow實例。
2、Window
? ? ? ? Window是視圖界面真正的管理器。Window是一個抽象類,具體的實現在PhoneWindow類,PhoneWindow類繼承于Window抽象類。PhoneWindow類該持有DecorView的實例。PhoneWindow類通過DecorView來加載布局xml文件。
?如上圖所示,在PhoneWindow中給全局變量mDecor負值。mDecor為DecorView實例。
3、DecorView? ?
?根據以上源碼截圖我們發現DecorView是FrameLayout類的子類。
Android原生提供了幾種樣式給DecorView。如下這幾種xml布局都是原生提供的:
其中我們看看screen_title.xml布局代碼:
<?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" /><FrameLayoutandroid: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>
在該布局里,DecorView內部包含一個LinearLayout,在這個LinearLayout里面有上下三個部分,上面是個ViewStub,根據Theme樣式設置ActionBar等。中間的是TitleView,有的xml布局沒有這一部分,例如screen_simple.xml中就沒有這一部分。最下面的是ContentViews,這是最重要的一部分,我們開發應用時在oncreate()函數中調用的setContentView()加載方法,其實就是將其加載到這個ContentViews里。
? ? 如上圖所示,phonewindow中通過DecorView的實例mDecor.onResourcesLoaded()方法將該布局加載到DecorView.
二、DecorView初始化相關源碼??
接下來我們看看從Activity 到 PhoneWindow再到DecorView的初始化以及布局加載源碼:
Activity的完整啟動流程在這里不再細說,直接從ActivityThread.java的handleLaunchActivity()方法開始講解。代碼如下:
\frameworks\base\core\java\android\app\ActivityThread.java
@Overridepublic Activity handleLaunchActivity(ActivityClientRecord r,PendingTransactionActions pendingActions, Intent customIntent) {.................// Hint the GraphicsEnvironment that an activity is launching on the process.GraphicsEnvironment.hintActivityLaunch();final Activity a = performLaunchActivity(r, customIntent);//(1).................return a;}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {..................appContext.getResources().addLoaders(app.getResources().getLoaders().toArray(new ResourcesLoader[0]));appContext.setOuterContext(activity);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,r.assistToken); //(2)
..................}
如上代碼,在Activity的啟動過程中,其中在ActivityThread.java中的與PhoneWindow及DecorView初始化相關部分得調用流程:
handleLaunchActivity()=>?performLaunchActivity( ) =>?activity.attach( )
接下來我們看看一下activity中attach()方法:
frameworks\base\core\java\android\app\Activity.java
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, IBinder assistToken){...................mWindow = new PhoneWindow(this, window, activityConfigCallback); //(3)mWindow.setWindowControllerCallback(mWindowControllerCallback);mWindow.setCallback(this); //(4)...................}
在注釋(3)可以看到Activity持有PhoneWindow并初始化了PhoneWindow實例,在注釋(4)中調用PhoneWindow的setCallback方法將activity實例設置給PhoneWindow,這一點很重要,我們在看DecorView的代碼時候,看到的mWindow.getCallback()方法,實際就是獲取DecorView相對應的Activity實例。
我們在第一節第3點講DecorView的布局的時候講到在oncreate()方法中調用setContentView()方法給activity設置布局是加載到DecorView的ContentViews里。接下來我們看一下這個代碼流程:
frameworks\base\core\java\android\app\Activity.java
public void setContentView(View view) {getWindow().setContentView(view);initWindowDecorActionBar();}
我們在前面注釋(3)中講了Activity持有PhoneWindow并初始化了PhoneWindow實例mWindow。
public Window getWindow() {return mWindow;}
getWindow()是拿到我們初始化好的PhoneWindow。
我們看一下PhoneWindow中的setContentView()方法:
frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java
@Overridepublic 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) { //(5)installDecor(); //(6)} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());transitionTo(newScene);} else {mLayoutInflater.inflate(layoutResID, mContentParent);//(7)}mContentParent.requestApplyInsets();final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}mContentParentExplicitlySet = true;}
以上,注釋(3)中的mContentParent 就是DecorView的ContentViews,在(6)中,如果mContentParent為空即通過installDecor()方法初始化一個DecorView實例、并將此PhoneWindow實例傳給DecorView,installDecor( )方法如下:
private void installDecor() {............if (mDecor == null) {mDecor = generateDecor(-1);mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);mDecor.setIsRootNamespace(true);if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);}} else {mDecor.setWindow(this);}............
}
在注釋(7)中將xml布局文件加載給DecorView的ContentViews。
總結:
Activity創建PhoneWindow對象,并把自己實例傳遞給PhoneWindow;PhoneWindow會創建一個DecorView對象,并把自己實例傳遞給DecorView。DecorView創建過程中根據開發者設置的不同的主題,加載不同的布局到DecorView的ContentViews里。
三、DecorView顯示時機
根據我們平時的開發經驗,我們給activity設置的布局,在activity調用了onResume()方法之后才會顯示出來,那么接下來我們看一下這個流程是否是我們想的這樣。
在以上我們詳述onCreate()方法中調用了setContentView()方法給DecorView設置了布局,此時并不可見。Activity的onResume()方法的調用以及DecorView的顯示都會在ActivityThread的handleResumeActivity()方法實現,我們看一下該方法:
\frameworks\base\core\java\android\app\ActivityThread.java
@Overridepublic void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {.....................final ActivityClientRecord r = performResumeActivity(token,
finalStateRequest, reason); //(8)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); //(9)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) {if (!a.mWindowAdded) {a.mWindowAdded = true;wm.addView(decor, l); //(10)} else {a.onWindowAttributesChanged(l);}}} else if (!willBeVisible) {if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");r.hideForNow = true;}if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {.....................if (r.activity.mVisibleFromClient) {r.activity.makeVisible(); //(11)}}.....................}
?我們先看注釋(8)performResumeActivity()方法的調用。
\frameworks\base\core\java\android\app\ActivityThread.java
public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,String reason){final ActivityClientRecord r = mActivities.get(token);.................r.activity.performResume(r.startsNotResumed, reason);//(12).................}
注釋(12)中,調用了activity的performResume()的方法:
\frameworks\base\core\java\android\app\Activity.java
final void performResume(boolean followedByPause, String reason) {dispatchActivityPreResumed();..................mInstrumentation.callActivityOnResume(this);//(13)..................}
注釋(13)中,調用了Instrumentation的callActivityOnResume()的方法:
public void callActivityOnResume(Activity activity) {activity.mResumed = true;activity.onResume();//(14).......................}
至此,我們知道了在注釋(14)中,我們熟悉的activity中的onResume()方法是被Instrumentation的callActivityOnResume()方法所調起的。
回到前面,也就是說在ActivityThread.java中的注釋(8)中的performResumeActivity()方法里就調起了activity中的onResume()方法。
再看注釋(9)decor.setVisibility(View.INVISIBLE),將DecorView設置為不可見,注釋(10)中wm.addView(decor, l)通過addView的方式將decor視圖添加;最后在注釋(11)r.activity.makeVisible()方法里將decor設置為VISIBLE。makeVisible()方法如下:
\frameworks\base\core\java\android\app\Activity.java
void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();wm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);}
總結:由以上代碼流程我們知道,activity里的decorView的布局的顯示是在activity中的OnResume()執行了之后才展示出來的,但是我們需要注意的是,即使我們發現了在activity中的OnResume()被調用了,但是handleResumeActivity()方法中的r.activity.makeVisible()方法沒有被執行,該布局視圖依然是不可見的。