關(guān)于Android中View視圖的呈現(xiàn),我們平常看到最多的就是在Activity中通過(guò)
setContentView(R.layout.activity_main);
設(shè)置來(lái)顯示。然而其實(shí)視圖的顯示并不是由Activity來(lái)完成的,其中涉及到了Window、DecorView、ViewRoot。四者之間關(guān)系交互復(fù)雜,共同完成視圖的顯示以及與用戶之間的交互。本篇文章簡(jiǎn)單介紹四者的各自職能,以及視圖的顯示過(guò)程。由于程度有限,認(rèn)識(shí)可能不到位,歡迎指正。
一、職能簡(jiǎn)介
Activity
Activity并不負(fù)責(zé)視圖控制,它只是控制生命周期和處理事件。真正控制視圖的是Window。一個(gè)Activity包含了一個(gè)Window,Window才是真正代表一個(gè)窗口。Activity就像一個(gè)控制器,統(tǒng)籌視圖的添加與顯示,以及通過(guò)其他回調(diào)方法,來(lái)與Window、以及View進(jìn)行交互。
Window
Window是視圖的承載器,內(nèi)部持有一個(gè) DecorView,而這個(gè)DecorView才是 view 的根布局。Window是一個(gè)抽象類,實(shí)際在Activity中持有的是其子類PhoneWindow。PhoneWindow中有個(gè)內(nèi)部類DecorView,通過(guò)創(chuàng)建DecorView來(lái)加載Activity中設(shè)置的布局R.layout.activity_main
。Window 通過(guò)WindowManager將DecorView加載其中,并將DecorView交給ViewRoot,進(jìn)行視圖繪制以及其他交互。
DecorView
DecorView是FrameLayout的子類,它可以被認(rèn)為是Android視圖樹(shù)的根節(jié)點(diǎn)視圖。DecorView作為頂級(jí)View,一般情況下它內(nèi)部包含一個(gè)豎直方向的LinearLayout,在這個(gè)LinearLayout里面有上下三個(gè)部分,上面是個(gè)ViewStub,延遲加載的視圖(應(yīng)該是設(shè)置ActionBar,根據(jù)Theme設(shè)置),中間的是標(biāo)題欄(根據(jù)Theme設(shè)置,有的布局沒(méi)有),下面的是內(nèi)容欄。具體情況和Android版本及主體有關(guān),以其中一個(gè)布局為例,如下所示:
<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>
在Activity中通過(guò)setContentView所設(shè)置的布局文件其實(shí)就是被加到內(nèi)容欄之中的,成為其唯一子View,就是上面的id為content的FrameLayout中,在代碼中可以通過(guò)content來(lái)得到對(duì)應(yīng)加載的布局。
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
ViewGroup rootView = (ViewGroup) content.getChildAt(0);
ViewRoot
ViewRoot可能比較陌生,但是其作用非常重大。所有View的繪制以及事件分發(fā)等交互都是通過(guò)它來(lái)執(zhí)行或傳遞的。
ViewRoot對(duì)應(yīng)ViewRootImpl類,它是連接WindowManagerService和DecorView的紐帶,View的三大流程(測(cè)量(measure),布局(layout),繪制(draw))均通過(guò)ViewRoot來(lái)完成。
ViewRoot并不屬于View樹(shù)的一份子。從源碼實(shí)現(xiàn)上來(lái)看,它既非View的子類,也非View的父類,但是,它實(shí)現(xiàn)了ViewParent接口,這讓它可以作為View的名義上的父視圖。RootView繼承了Handler類,可以接收事件并分發(fā),Android的所有觸屏事件、按鍵事件、界面刷新等事件都是通過(guò)ViewRoot進(jìn)行分發(fā)的。
下面結(jié)構(gòu)圖可以清晰的揭示四者之間的關(guān)系:
二、DecorView的創(chuàng)建
這部分內(nèi)容主要講DecorView是怎么一層層嵌套在Actvity,PhoneWindow中的,以及DecorView如何加載內(nèi)部布局。
setContentView
先是從Activity中的setContentView()開(kāi)始。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到實(shí)際是交給Window裝載視圖。下面來(lái)看看Activity是怎么獲得Window對(duì)象的?
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) {
..................................................................
mWindow = new PhoneWindow(this, window);//創(chuàng)建一個(gè)Window對(duì)象
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);//設(shè)置回調(diào),向Activity分發(fā)點(diǎn)擊或狀態(tài)改變等事件
mWindow.setOnWindowDismissedCallback(this);
.................................................................
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//給Window設(shè)置WindowManager對(duì)象
....................................................................
}
在Activity中的attach()方法中,生成了PhoneWindow實(shí)例。既然有了Window對(duì)象,那么我們就可以設(shè)置DecorView給Window對(duì)象了。
public void setContentView(int layoutResID) {
if (mContentParent == null) {//mContentParent為空,創(chuàng)建一個(gè)DecroView
installDecor();
} else {
mContentParent.removeAllViews();//mContentParent不為空,刪除其中的View
}
mLayoutInflater.inflate(layoutResID, mContentParent);//為mContentParent添加子View,即Activity中設(shè)置的布局文件
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();//回調(diào)通知,內(nèi)容改變
}
}
看了下來(lái),可能有一個(gè)疑惑:mContentParent到底是什么?
就是前面布局中@android:id/content
所對(duì)應(yīng)的FrameLayout。
通過(guò)上面的流程我們大致可以了解先在PhoneWindow中創(chuàng)建了一個(gè)DecroView,其中創(chuàng)建的過(guò)程中可能根據(jù)Theme不同,加載不同的布局格式,例如有沒(méi)有Title,或有沒(méi)有ActionBar等,然后再向mContentParent中加入子View,即Activity中設(shè)置的布局。到此位置,視圖一層層嵌套添加上了。
下面具體來(lái)看看installDecor();
方法,怎么創(chuàng)建的DecroView,并設(shè)置其整體布局?
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(); //生成DecorView
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor); // 為DecorView設(shè)置布局格式,并返回mContentParent
...
}
}
}
再來(lái)看看 generateDecor()
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
很簡(jiǎn)單,創(chuàng)建了一個(gè)DecorView。
再看generateLayout
protected ViewGroup generateLayout(DecorView decor) {
// 從主題文件中獲取樣式信息
TypedArray a = getWindowStyle();
...................
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
................
// 根據(jù)主題樣式,加載窗口布局
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if(...){
...
}
View in = mLayoutInflater.inflate(layoutResource, null); //加載layoutResource
//往DecorView中添加子View,即文章開(kāi)頭介紹DecorView時(shí)提到的布局格式,那只是一個(gè)例子,根據(jù)主題樣式不同,加載不同的布局。
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 這里獲取的就是mContentParent
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks();
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
...
return contentParent;
}
雖然比較復(fù)雜,但是邏輯還是很清楚的。先從主題中獲取樣式,然后根據(jù)樣式,加載對(duì)應(yīng)的布局到DecorView中,然后從中獲取mContentParent。獲得到之后,可以回到上面的代碼,為mContentParent添加View,即Activity中的布局。
以上就是DecorView的創(chuàng)建過(guò)程,其實(shí)到installDecor()
就已經(jīng)介紹完了,后面只是具體介紹其中的邏輯。
三、DecorView的顯示
以上僅僅是將DecorView建立起來(lái)。通過(guò)setContentView()設(shè)置的界面,為什么在onResume()之后才對(duì)用戶可見(jiàn)呢?
這就要從ActivityThread開(kāi)始說(shuō)起了,由于個(gè)人也是一知半解,僅僅闡述下個(gè)人理解吧。
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//就是在這里調(diào)用了Activity.attach()呀,接著調(diào)用了Activity.onCreate()和Activity.onStart()生命周期,
//但是由于只是初始化了mDecor,添加了布局文件,還沒(méi)有把
//mDecor添加到負(fù)責(zé)UI顯示的PhoneWindow中,所以這時(shí)候?qū)τ脩魜?lái)說(shuō),是不可見(jiàn)的
Activity a = performLaunchActivity(r, customIntent);
......
if (a != null) {
//這里面執(zhí)行了Activity.onResume()
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
try {
r.activity.mCalled = false;
//執(zhí)行Activity.onPause()
mInstrumentation.callActivityOnPause(r.activity);
}
}
}
}
重點(diǎn)看下handleResumeActivity(),在這其中,DecorView將會(huì)顯示出來(lái),同時(shí)重要的一個(gè)角色:ViewRoot也將登場(chǎng)。
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
//這個(gè)時(shí)候,Activity.onResume()已經(jīng)調(diào)用了,但是現(xiàn)在界面還是不可見(jiàn)的
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
//decor對(duì)用戶不可見(jiàn)
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//被添加進(jìn)WindowManager了,但是這個(gè)時(shí)候,還是不可見(jiàn)的
wm.addView(decor, l);
}
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
//在這里,執(zhí)行了重要的操作,使得DecorView可見(jiàn)
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
}
當(dāng)我們執(zhí)行了Activity.makeVisible()方法之后,界面才對(duì)我們是可見(jiàn)的。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());//將DecorView添加到WindowManager
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);//DecorView可見(jiàn)
}
到此DecorView便可見(jiàn),顯示在屏幕中。但是在這其中,wm.addView(mDecor, getWindow().getAttributes());
起到了重要的作用,因?yàn)槠鋬?nèi)部創(chuàng)建了一個(gè)ViewRootImpl對(duì)象,負(fù)責(zé)繪制顯示各個(gè)子View。
具體來(lái)看addView()
方法,因?yàn)閃indowManager是個(gè)接口,具體是交給WindowManagerImpl來(lái)實(shí)現(xiàn)的。
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
}
交給WindowManagerGlobal 的addView()方法去實(shí)現(xiàn)
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
......
synchronized (mLock) {
ViewRootImpl root;
//實(shí)例化一個(gè)ViewRootImpl對(duì)象
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
......
try {
//將DecorView交給ViewRootImpl
root.setView(view, wparams, panelParentView);
}catch (RuntimeException e) {
}
}
}
看到其中實(shí)例化了ViewRootImpl對(duì)象,然后調(diào)用其setView()方法。其中setView()方法經(jīng)過(guò)一些列折騰,最終調(diào)用了performTraversals()方法,然后依照下圖流程層層調(diào)用,完成繪制,最終界面才顯示出來(lái)。
其實(shí)ViewRootImpl的作用不止如此,還有許多功能,如事件分發(fā)。
要知道,當(dāng)用戶點(diǎn)擊屏幕產(chǎn)生一個(gè)觸摸行為,這個(gè)觸摸行為則是通過(guò)底層硬件來(lái)傳遞捕獲,然后交給ViewRootImpl,接著將事件傳遞給DecorView,而DecorView再交給PhoneWindow,PhoneWindow再交給Activity,然后接下來(lái)就是我們常見(jiàn)的View事件分發(fā)了。
硬件 -> ViewRootImpl -> DecorView -> PhoneWindow -> Activity
不詳細(xì)介紹了,如果感興趣,可以看這篇文章。
由此可見(jiàn)ViewRootImpl的重要性,是個(gè)連接器,負(fù)責(zé)WindowManagerService與DecorView之間的通信。
四、總結(jié)
以上通過(guò)源碼形式介紹了Window、Activity、DecorView以及ViewRoot之間的錯(cuò)綜關(guān)系,以及如何創(chuàng)建并顯示DecorView。
通過(guò)以上了解可以知道,Activity就像個(gè)控制器,不負(fù)責(zé)視圖部分。Window像個(gè)承載器,裝著內(nèi)部視圖。DecorView就是個(gè)頂層視圖,是所有View的最外層布局。ViewRoot像個(gè)連接器,負(fù)責(zé)溝通,通過(guò)硬件的感知來(lái)通知視圖,進(jìn)行用戶之間的交互。
參考文章:
Android View源碼解讀:淺談DecorView與ViewRootImpl
window、Activity、DecorView、ViewRoot關(guān)系
Android 源碼解析 之 setContentView
淺析 android 應(yīng)用界面的展現(xiàn)流程(二)布局與視圖的創(chuàng)建
【凱子哥帶你學(xué)Framework】Activity界面顯示全解析(下)