Android,androidsdk
DecorView
開發中,通常都是在onCreate()中調用setContentView(R.layout.custom_layout)來實現想要的頁面配置。頁面都是依附在視窗之上的,而DecorView即是視窗最頂層的視圖。Android frameworks中,與視窗視圖處理相關的類,主要是Window及其實作類別PhoneWindow
public class PhoneWindow extends Window implements MenuBuilder.Callback { //... //視窗頂層View private DecorView mDecor; //所有自訂View的根View, id="@android:id/content" private ViewGroup mContentParent; //...}
DecorView其實是PhoneWindow中的一個內部類,本質上也是一個View,其只是擴充了FrameLayout的實現
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
添加至視窗流程
public void setContentView(int layoutResID) { //getWindow()擷取的即是PhoneWindow對象 getWindow().setContentView(layoutResID); }
看一下window類
public abstract class Window { //... //指定Activity視窗的風格類型 public static final int FEATURE_NO_TITLE = 1; public static final int FEATURE_INDETERMINATE_PROGRESS = 5; //設定布局檔案 public abstract void setContentView(int layoutResID); public abstract void setContentView(View view); //請求指定Activity視窗的風格類型 public boolean requestFeature(int featureId) { final int flag = 1<<featureId; mFeatures |= flag; mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag; return (mFeatures&flag) != 0; } //... }
- PhoneWindow執行setContentView(int layoutResource)
PhoneWindow該類繼承於Window類,是Window類的具體實現,即我們可以通過該類具體去繪製視窗。並且,該類內部包含了一個DecorView對象,該DectorView對象是所有應用視窗(Activity介面)的根View。 簡而言之,PhoneWindow類是把一個FrameLayout類即DecorView對象進行一定的封裝,將它作為應用視窗的根View,並提供一組通用的視窗操作介面。
public void setContentView(int layoutResID) { //初始,mContentParent為空白 if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } //inflate自訂layout, 並將mContentParent作為其根視圖 mLayoutInflater.inflate(layoutResID, mContentParent); //...}
該方法根據首先判斷是否已經由setContentView()了擷取mContentParent即View對象, 即是否是第一次調用該PhoneWindow對象setContentView()方法。如果是第一次調用,則調用installDecor()方法,否則,移除該mContentParent內所有的所有子View。最後將我們的資源檔通過LayoutInflater對象轉換為View樹,並且添加至mContentParent視圖中(在應用程式裡,我們可以多次調用setContentView()來顯示我們的介面。)。
- PhoneWindow.installDecor()
private void installDecor() { if (mDecor == null) { //new一個DecorView mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } if (mContentParent == null) { //這一步會設定視窗的修飾檔案,並將id為ID_ANDROID_CONTENT的view find出來作為傳回值賦值給mContentParent mContentParent = generateLayout(mDecor); //...}
- PhoneWindow.generateLayout(DecorView decor)
protected ViewGroup generateLayout(DecorView decor) { //1,擷取<Application android:theme=""/>, <Activity/>節點指定的themes或者代碼requestWindowFeature()中指定的Features, 並設定 TypedArray a = getWindowStyle(); //... //2,擷取視窗Features, 設定相應的修飾布局檔案,這些xml檔案位於frameworks/base/core/res/res/layout下 int layoutResource; int features = getLocalFeatures(); if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute(com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = com.android.internal.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 = com.android.internal.R.layout.screen_progress; //... mDecor.startChanging(); //3, 將上面選定的布局檔案inflate為View樹,添加到decorView中 View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); //將視窗修飾布局檔案中id="@android:id/content"的View賦值給mContentParent, 後續自訂的view/layout都將是其子View ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } //...}
該方法會做如下事情:
根據視窗的風格修飾類型為該視窗選擇不同的視窗布局檔案(根視圖)。這些視窗修飾布局檔案指定一個用來存放Activity自訂布局檔案的ViewGroup視圖,一般為FrameLayout 其id 為: android:id="@android:id/content"。
例如視窗修飾類型包括FullScreen(全屏)、NoTitleBar(不含標題列)等。選定視窗修飾類型有兩種:
①指定requestFeature()指定視窗修飾符,PhoneWindow對象調用getLocalFeature()方法擷取值;
②為我們的Activity配置相應屬性,即android:theme=“”,PhoneWindow對象調用getWindowStyle()方法擷取值。
舉例如下,隱藏標題列有如下方法:
requestWindowFeature(Window.FEATURE_NO_TITLE);
或者為Activity配置xml屬性:
android:theme="@android:style/Theme.NoTitleBar"
因此,在Activity中必須在setContentView之前調用requestFeature()方法。
確定好視窗風格之後,選定該風格對應的布局檔案,這些布局檔案位於 frameworks/base/core/res/layout/ ,
典型的視窗布局檔案有:
R.layout.dialog_titile_icons R.layout.screen_title_iconsR.layout.screen_progress R.layout.dialog_custom_titleR.layout.dialog_title R.layout.screen_title // 最常用的Activity視窗修飾布局檔案R.layout.screen_simple //全屏的Activity視窗布局檔案
- 最後頁面中設定的自訂layout會被添加到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
整個過程主要是如何把Activity的布局檔案添加至視窗裡,上面的過程可以概括為:
最後,當AMS(ActivityManagerService)準備resume一個Activity時,會回調該Activity的handleResumeActivity()方法,該方法會調用Activity的makeVisible方法 ,顯示我們剛才建立的mDecor 視圖族。
//系統resume一個Activity時,調用此方法 final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { ActivityRecord r = performResumeActivity(token, clearHide); //... if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } }
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); // 擷取WindowManager對象 wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); //使其處於顯示狀況 }布局階層
@Overrideprotected void onCreate(Bundle savedInstanceState) { //設定視窗無標題列 requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); setContentView(R.layout.activity_decor);}
activity_decor.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".DecorActivity"> <TextView android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true"/></RelativeLayout>
onCreate()中設定的Window.FEATURE_NO_TITLE對應的視窗修飾布局檔案為screen_simple.xml, 源碼如下
<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" /> <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>
源碼中id為"@android:id/content"的FrameLayout就是內容地區,在整個流程中,其會賦值給PhoneWindow類中的屬性mContentParent, 運行應用後,使用SDK提供的hierarchyviewer工具查看頁面的ViewTree結構,可以看到結構如下:
我是天王蓋地虎的分割線
參考:http://www.cnblogs.com/yogin/p/4061050.html