前言
我們都知道DecorView是最頂層View(根View),它是怎么創(chuàng)建和使用的呢?
通過(guò)本篇文章,你將了解到:
1、DecorView創(chuàng)建過(guò)程。
2、DecorView與Window/Activity關(guān)系
3、DecorView各個(gè)子View創(chuàng)建
DecorView創(chuàng)建過(guò)程
來(lái)回顧一下Activity創(chuàng)建過(guò)程:
AMS管理著Activity生命周期,每當(dāng)切換Activity狀態(tài)時(shí)通過(guò)Binder告訴ActivityThread,ActivityThread通過(guò)Handler切換到主線程(UI線程)執(zhí)行,最終分別調(diào)用到我們熟知的onCreate(xx)/onResume()方法。
更多細(xì)節(jié)請(qǐng)移步:Android Activity創(chuàng)建到View的顯示過(guò)程
本次關(guān)注的重點(diǎn)是setContentView(xx)方法。
簡(jiǎn)單布局分析
相信大家都知道,該方法是將我們布局(layout)文件添加到一個(gè)id為“android.R.id.content”的ViewGroup里,我們只需要關(guān)心layout的內(nèi)容。那么R.id.content是整個(gè)View樹(shù)的根布局嗎?來(lái)看看一個(gè)最簡(jiǎn)單的Activity的布局:
my_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/colorGreen"
android:id="@+id/my_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btn"
android:text="hello"
android:onClick="onClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Button>
</FrameLayout>
效果圖:
把布局觀察器打開(kāi):
可以看出,"content"布局對(duì)應(yīng)的是ContentFrameLayout,它并不是View樹(shù)的根布局,根布局是DecorView,DecorView和ContentFrameLayout之間還有幾個(gè)View,接下來(lái)我們就來(lái)了解上圖的這些View是怎么確定的。
setContentView(xx)源碼解析
從Activity onCreate(xx)看起
AppCompatDelegateImpl
@Override
protected void onCreate(Bundle savedInstanceState) {
//先調(diào)用父類構(gòu)造函數(shù)
//初始化一些必要變量
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_path);
}
創(chuàng)建及初始化AppCompatDelegateImpl
AppCompatActivity.java
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
//交給AppCompatDelegate代理
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
AppCompatActivity將工作交給了AppCompatDelegate處理,而AppCompatDelegate是抽象類,真正工作的是其子類:AppCompatDelegateImpl。
@Override
public void onCreate(Bundle savedInstanceState) {
ensureWindow();
}
private void ensureWindow() {
//省略
//mHost為創(chuàng)建此代理的Activity
if (mWindow == null && mHost instanceof Activity) {
attachToWindow(((Activity) mHost).getWindow());
}
}
private void attachToWindow(@NonNull Window window) {
if (mWindow != null) {
throw new IllegalStateException(
"AppCompat has already installed itself into the Window");
}
//接收各種事件的window callback,實(shí)際就是將callback再包了一層
mAppCompatWindowCallback = new AppCompatWindowCallback(callback);
window.setCallback(mAppCompatWindowCallback);
//省略
//使用activity的window
mWindow = window;
}
上面代碼的主要工作是關(guān)聯(lián)AppCompatDelegateImpl和Activity的window變量。
接著來(lái)看看setContentView(R.layout.layout_path)本尊
Activity的setContentView(xx)最終調(diào)用了AppCompatDelegateImpl里的setContentView(xx):
AppCompatDelegateImpl.java
@Override
public void setContentView(int resId) {
//創(chuàng)建SubDecor,從名字可以猜測(cè)一下是DecorView的子View
ensureSubDecor();
//找到R.id.content布局
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
//清空content里的子View
contentParent.removeAllViews();
//加載在activity里設(shè)置的layout,并添加到content里
LayoutInflater.from(mContext).inflate(resId, contentParent);
}
可以看出,我們自定義的layout最后被添加到contentParent里,而contentParent是從mSubDecor里找尋的,因此我們重點(diǎn)來(lái)看看ensureSubDecor()方法。
ensureSubDecor里調(diào)用的是createSubDecor()方法來(lái)創(chuàng)建subDecor。
subDecor創(chuàng)建
AppCompatDelegateImpl.java
private ViewGroup createSubDecor() {
//根據(jù)Activity的主題設(shè)置不同設(shè)置window的feature
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//確保window已經(jīng)關(guān)聯(lián)
ensureWindow();
//獲取DecorView,沒(méi)有則創(chuàng)建
mWindow.getDecorView();
ViewGroup subDecor = null;
//有標(biāo)題
if (!mWindowNoTitle) {
//根據(jù)條件給subDecor加載不同的布局文件
if (mIsFloating) {
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
} else if (mHasActionBar) {
//加載subDecor布局
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
//尋找subDecor子布局
mDecorContentParent = (DecorContentParent) subDecor
.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());
}
} else {
//省略
}
//標(biāo)題欄標(biāo)題
if (mDecorContentParent == null) {
mTitleView = (TextView) subDecor.findViewById(R.id.title);
}
//尋找subDecor子布局,命名為contentView
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//找到window里content布局,實(shí)際上找的是DecorView里名為content的布局
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
//挨個(gè)移除windowContentView的子View,并將之添加到contentView里
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
//把windowContentView id去掉,之前名為content
windowContentView.setId(View.NO_ID);
//將"content"名賦予contentView
contentView.setId(android.R.id.content);
}
//把subDecor添加為Window的contentView,實(shí)際上添加為DecorView的子View。該方法后面再具體分析
mWindow.setContentView(subDecor);
return subDecor;
}
該方法較長(zhǎng),省略了一些細(xì)枝末節(jié),主體功能是:
- 根據(jù)不同的前置條件,加載不同的布局文件作為subDecor
- 將subDecor加入到DecorView里,subDecor本身是ViewGroup
subDecor創(chuàng)建了,那么DecorView啥時(shí)候創(chuàng)建呢?createSubDecor()有段代碼:
mWindow.getDecorView()
DecorView創(chuàng)建
mWindow之前分析過(guò)是Activity的window變量,它的實(shí)現(xiàn)類是PhoneWindow。
PhoneWindow.java
View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
private void installDecor() {
//mDecor作為PhoneWindow變量
if (mDecor == null) {
//新建DecorView,并關(guān)聯(lián)window
mDecor = generateDecor(-1);
}
if (mContentParent == null) {
//創(chuàng)建DecorView子布局
mContentParent = generateLayout(mDecor);
}
}
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
//applicationContext是Application
//getContext 是Activity
//DecorContext 繼承自ContextThemeWrapper
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
//this 指的是phoneWindow,賦值給mWindow變量
return new DecorView(context, featureId, this, getAttributes());
}
從上可知:
DecorView繼承自FrameLayout
注:DecorContext 有關(guān)請(qǐng)移步:Android各種Context的前世今生
重點(diǎn)來(lái)看看
mContentParent = generateLayout(mDecor);
PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
//獲取WindowStyle
TypedArray a = getWindowStyle();
//根據(jù)style設(shè)置各種flags和feature
//省略...
WindowManager.LayoutParams params = getAttributes();
//待加載的布局資源id
int layoutResource;
int features = getLocalFeatures();
//根據(jù)不同的feature確定不同的布局
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if (features) {
//省略
} else {
//默認(rèn)加載該布局
layoutResource = R.layout.screen_simple;
}
//實(shí)例化上面確定的布局,并將該布局添加到DecorView里
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//獲取名為ID_ANDROID_CONTENT的子View
//public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
//實(shí)際上就是content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
將確定的布局加載,并添加到DecorView里。
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
//實(shí)例化布局
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
//省略
} else {
//添加到DecorView里
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
//記錄root
mContentRoot = (ViewGroup) root;
}
總結(jié)來(lái)說(shuō),mWindow.getDecorView()做了哪些事呢?
- 創(chuàng)建DecorView,并關(guān)聯(lián)PhoneWindow與DecorView(互相持有)
- 根據(jù)feature確定DecorView子布局,并添加填充滿DecorView
可能看到上面的分析還是一頭霧水,沒(méi)關(guān)系先來(lái)理一下DecorView和SubDecor關(guān)系。
1、首先創(chuàng)建DecorView,并添加其子View,該子View在DecorView里稱為:mContentRoot。當(dāng)前場(chǎng)景下使用的子View資源id:R.layout.screen_simple;
2、mContentRoot里有名為"R.id.content"的子View,其在PhoneWindow里稱作為:mContentParent
3、當(dāng)前場(chǎng)景下使用的subDecor資源id:R.layout.abc_screen_toolbar,實(shí)例化該資源文件獲得subDecor實(shí)例
4、該subDecor里有子View資源id:R.id.action_bar_activity_content,在AppCompatDelegateImpl里命名為:contentView
5、DecorView添加subDecor過(guò)程:取出DecorView里的mContentParent,并移除里面的子View,并將移除的子View添加到subDecor里的contentView里。
6、將contentView更名為“R.id.content”
7、最后通過(guò)mWindow.setContentView(subDecor),將subDecor添加到DecorView里
繼續(xù)分析mWindow.setContentView(subDecor)
PhoneWindow.java
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
//為空,說(shuō)明DecorView沒(méi)構(gòu)造
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//清空子View
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//省略
} else {
//View添加到mContentParent
mContentParent.addView(view, params);
}
//省略
}
以上就是DecorView的創(chuàng)建過(guò)程。
DecorView創(chuàng)建完成后,再將自定義布局添加到DecorView里名為R.id.content布局里。至此setContentView分析完畢,老規(guī)矩用圖表示整個(gè)流程。
setContentView圖示
DecorView與Window/Activity關(guān)系
Activity:
我們平時(shí)把Activity當(dāng)做一個(gè)頁(yè)面,直觀上看這個(gè)頁(yè)面承載著我們的布局顯示以及相應(yīng)的邏輯處理。Activity有生命周期,在不同的狀態(tài)下做不同的處理。
Window
顧名思義,就是我們所見(jiàn)的窗口,Activity顯示的內(nèi)容實(shí)際上是展示在Window上的,對(duì)應(yīng)的是PhoneWindow。
DecorView
Window本身是比較抽象的,可以想象成為一個(gè)管理的東西,它管理的就是ViewTree,而DecorView是ViewTree的根。我們具體的界面展示是通過(guò)View完成的,把設(shè)計(jì)好的View添加到DecorView里,整個(gè)ViewTree就構(gòu)建起來(lái)了,最終添加到Window里。
因此:
- Activity管理著Window,Window管理著DecorView,Activity間接管理著DecorView
- Activity處在“create"狀態(tài)的時(shí)候創(chuàng)建對(duì)應(yīng)的Window,并在setContentView里創(chuàng)建DecorView,最終添加自定義布局到DecorView里。此時(shí),Activity創(chuàng)建完畢,Window創(chuàng)建完畢,DecorView創(chuàng)建完畢,ViewTree構(gòu)建成功,三者關(guān)系已建立。
- Acitivity處在“resume"狀態(tài)的時(shí)候,通過(guò)Window的管理類將DecorView添加到Window里,并提交更新DecorView的請(qǐng)求。當(dāng)下次屏幕刷新信號(hào)到來(lái)之時(shí),執(zhí)行ViewTree三大流程(測(cè)量,布局,繪制),最終的效果就是我們的自定義布局顯示在屏幕上。
“注”:指的狀態(tài)執(zhí)行時(shí)機(jī)是處在ActivityThread里的。
這么看起來(lái)還是不太順,我們所說(shuō)的三者的聯(lián)系實(shí)際上在代碼里來(lái)看就是:“不是你持有我就是我引用你”,通過(guò)類圖來(lái)看看三者之間的引用情況:
可以看出,Window作為Activity和DecorView的聯(lián)結(jié)者。
注:Activity也持有DecorView的引用,只是不對(duì)外提供獲取的接口。因此一般通過(guò)Activity獲取Window,再獲取DecorView。
在UI上看來(lái),可以這么理解:
注:這圖只是便于理解抽象關(guān)系,實(shí)際上對(duì)于Activity來(lái)說(shuō)并沒(méi)有尺寸的說(shuō)法。
后續(xù)
限于當(dāng)前篇幅,后續(xù)內(nèi)容重開(kāi)一篇:
Android DecorView 一窺全貌(下)
注:以上關(guān)于DecorView、subDecor、標(biāo)題欄、布局文件和區(qū)塊尺寸的選擇是基于當(dāng)前的demo的。可能你所使用的主題、設(shè)置的屬性和本文不同導(dǎo)致布局效果差異,請(qǐng)注意甄別。
源碼基于:Android 10.0