前言
我們都知道DecorView是最頂層View(根View),它是怎么創(chuàng)建和使用的呢?
通過本篇文章黄橘,你將了解到:
1、DecorView創(chuàng)建過程屈溉。
2塞关、DecorView與Window/Activity關(guān)系
3、DecorView各個子View創(chuàng)建
DecorView創(chuàng)建過程
來回顧一下Activity創(chuàng)建過程:
AMS管理著Activity生命周期子巾,每當(dāng)切換Activity狀態(tài)時通過Binder告訴ActivityThread帆赢,ActivityThread通過Handler切換到主線程(UI線程)執(zhí)行小压,最終分別調(diào)用到我們熟知的onCreate(xx)/onResume()方法。
更多細(xì)節(jié)請移步:Android Activity創(chuàng)建到View的顯示過程
本次關(guān)注的重點是setContentView(xx)方法椰于。
簡單布局分析
相信大家都知道怠益,該方法是將我們布局(layout)文件添加到一個id為“android.R.id.content”的ViewGroup里,我們只需要關(guān)心layout的內(nèi)容廉羔。那么R.id.content是整個View樹的根布局嗎溉痢?來看看一個最簡單的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>
效果圖:
把布局觀察器打開:
可以看出,"content"布局對應(yīng)的是ContentFrameLayout憋他,它并不是View樹的根布局孩饼,根布局是DecorView,DecorView和ContentFrameLayout之間還有幾個View竹挡,接下來我們就來了解上圖的這些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,實際就是將callback再包了一層
mAppCompatWindowCallback = new AppCompatWindowCallback(callback);
window.setCallback(mAppCompatWindowCallback);
//省略
//使用activity的window
mWindow = window;
}
上面代碼的主要工作是關(guān)聯(lián)AppCompatDelegateImpl和Activity的window變量好啰。
接著來看看setContentView(R.layout.layout_path)本尊
Activity的setContentView(xx)最終調(diào)用了AppCompatDelegateImpl里的setContentView(xx):
AppCompatDelegateImpl.java
@Override
public void setContentView(int resId) {
//創(chuàng)建SubDecor轩娶,從名字可以猜測一下是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里找尋的,因此我們重點來看看ensureSubDecor()方法椰弊。
ensureSubDecor里調(diào)用的是createSubDecor()方法來創(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,沒有則創(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布局贤重,實際上找的是DecorView里名為content的布局
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
//挨個移除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并蝗,實際上添加為DecorView的子View。該方法后面再具體分析
mWindow.setContentView(subDecor);
return subDecor;
}
該方法較長秸妥,省略了一些細(xì)枝末節(jié)借卧,主體功能是:
- 根據(jù)不同的前置條件,加載不同的布局文件作為subDecor
- 將subDecor加入到DecorView里筛峭,subDecor本身是ViewGroup
subDecor創(chuàng)建了铐刘,那么DecorView啥時候創(chuàng)建呢?createSubDecor()有段代碼:
mWindow.getDecorView()
DecorView創(chuàng)建
mWindow之前分析過是Activity的window變量影晓,它的實現(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)請移步:Android各種Context的前世今生
重點來看看
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;
}
//實例化上面確定的布局疤祭,并將該布局添加到DecorView里
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//獲取名為ID_ANDROID_CONTENT的子View
//public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
//實際上就是content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
將確定的布局加載盼产,并添加到DecorView里。
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
//實例化布局
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é)來說勺馆,mWindow.getDecorView()做了哪些事呢戏售?
- 創(chuàng)建DecorView,并關(guān)聯(lián)PhoneWindow與DecorView(互相持有)
- 根據(jù)feature確定DecorView子布局草穆,并添加填充滿DecorView
可能看到上面的分析還是一頭霧水灌灾,沒關(guān)系先來理一下DecorView和SubDecor關(guān)系。
1悲柱、首先創(chuàng)建DecorView锋喜,并添加其子View,該子View在DecorView里稱為:mContentRoot豌鸡。當(dāng)前場景下使用的子View資源id:R.layout.screen_simple;
2嘿般、mContentRoot里有名為"R.id.content"的子View,其在PhoneWindow里稱作為:mContentParent
3涯冠、當(dāng)前場景下使用的subDecor資源id:R.layout.abc_screen_toolbar炉奴,實例化該資源文件獲得subDecor實例
4、該subDecor里有子View資源id:R.id.action_bar_activity_content蛇更,在AppCompatDelegateImpl里命名為:contentView
5瞻赶、DecorView添加subDecor過程:取出DecorView里的mContentParent,并移除里面的子View械荷,并將移除的子View添加到subDecor里的contentView里共耍。
6虑灰、將contentView更名為“R.id.content”
7吨瞎、最后通過mWindow.setContentView(subDecor),將subDecor添加到DecorView里
繼續(xù)分析mWindow.setContentView(subDecor)
PhoneWindow.java
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
//為空穆咐,說明DecorView沒構(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)建過程颤诀。
DecorView創(chuàng)建完成后,再將自定義布局添加到DecorView里名為R.id.content布局里对湃。至此setContentView分析完畢崖叫,老規(guī)矩用圖表示整個流程。
setContentView圖示
DecorView與Window/Activity關(guān)系
Activity:
我們平時把Activity當(dāng)做一個頁面拍柒,直觀上看這個頁面承載著我們的布局顯示以及相應(yīng)的邏輯處理心傀。Activity有生命周期,在不同的狀態(tài)下做不同的處理拆讯。
Window
顧名思義脂男,就是我們所見的窗口养叛,Activity顯示的內(nèi)容實際上是展示在Window上的,對應(yīng)的是PhoneWindow宰翅。
DecorView
Window本身是比較抽象的弃甥,可以想象成為一個管理的東西,它管理的就是ViewTree汁讼,而DecorView是ViewTree的根淆攻。我們具體的界面展示是通過View完成的,把設(shè)計好的View添加到DecorView里嘿架,整個ViewTree就構(gòu)建起來了瓶珊,最終添加到Window里。
因此:
- Activity管理著Window眶明,Window管理著DecorView艰毒,Activity間接管理著DecorView
- Activity處在“create"狀態(tài)的時候創(chuàng)建對應(yīng)的Window,并在setContentView里創(chuàng)建DecorView搜囱,最終添加自定義布局到DecorView里丑瞧。此時,Activity創(chuàng)建完畢蜀肘,Window創(chuàng)建完畢绊汹,DecorView創(chuàng)建完畢,ViewTree構(gòu)建成功扮宠,三者關(guān)系已建立西乖。
- Acitivity處在“resume"狀態(tài)的時候,通過Window的管理類將DecorView添加到Window里坛增,并提交更新DecorView的請求获雕。當(dāng)下次屏幕刷新信號到來之時,執(zhí)行ViewTree三大流程(測量收捣,布局届案,繪制),最終的效果就是我們的自定義布局顯示在屏幕上罢艾。
“注”:指的狀態(tài)執(zhí)行時機(jī)是處在ActivityThread里的楣颠。
這么看起來還是不太順,我們所說的三者的聯(lián)系實際上在代碼里來看就是:“不是你持有我就是我引用你”咐蚯,通過類圖來看看三者之間的引用情況:
可以看出童漩,Window作為Activity和DecorView的聯(lián)結(jié)者。
注:Activity也持有DecorView的引用春锋,只是不對外提供獲取的接口矫膨。因此一般通過Activity獲取Window,再獲取DecorView。
在UI上看來侧馅,可以這么理解:
注:這圖只是便于理解抽象關(guān)系直奋,實際上對于Activity來說并沒有尺寸的說法。
后續(xù)
限于當(dāng)前篇幅施禾,后續(xù)內(nèi)容重開一篇:
Android DecorView 一窺全貌(下)
注:以上關(guān)于DecorView脚线、subDecor、標(biāo)題欄弥搞、布局文件和區(qū)塊尺寸的選擇是基于當(dāng)前的demo的邮绿。可能你所使用的主題攀例、設(shè)置的屬性和本文不同導(dǎo)致布局效果差異船逮,請注意甄別。
源碼基于:Android 10.0