View樹與Window關聯
前面介紹的是View樹的建立,但是建立好的View樹隻有添加到一個Window裡才會顯示到螢幕上,因為
WindowManagerService
服務會為每一個Window建立一塊Surface作為畫布,View樹裡所有的View都會繪畫到這塊Surface上,最後
SurfaceFlinger
服務會将所有Window建立的的Surface傳遞給GPU,最終顯示到螢幕上。
建立Window
通過
WindowManager.addView
接口就可以申請建立一個新的Window并添加一個View樹,彈出菜單、浮動視窗等自定義的視窗都是通過這個接口顯示出來的:
//獲得WindowManager服務
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//設定Window參數
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.setTitle("FloatWindow");
//建立View樹
View rootView = getLayoutInflater().inflate(R.layout.float_layout, null);
//建立Window并關聯View樹
windowManager.addView(rootView, params);
應用中使用的
WindowManager
其實是一個
WindowManagerImpl
類的執行個體:
frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
...
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
}
如上面的代碼,
WindowManagerImpl.addView()
實作會調用
WindowManagerGlobal.addView()
,實際的請求建立Window的邏輯也在這個類裡實作:
frameworks/base/core/java/android/view/WindowManagerGlobal.java
public final class WindowManagerGlobal {
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
...
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
mParams.add(params);
try {
root.setView(view, params, panelParentView);
} catch (RuntimeException e) {
throw e;
}
}
}
}
WindowManagerGlobal
類裡有這三個
ArrayList
清單:
-
儲存着每個Window的根ViewmViews
-
儲存每個根View對應的mRoots
ViewRootImpl
-
儲存的是每個Window的視窗配置參數mParams
WindowManager.LayoutParams
WindowManagerGlobal.addView()
的實作裡會為每一個Window建立一個
ViewRootImpl
執行個體,以及視窗配置參數
WindowManager.LayoutParams
執行個體,并将傳遞過來的根View執行個體儲存在上面提到的三個清單裡。
addView()
實作裡最後調用的
root.setView()
就是通過
ViewRootImpl
将Window與View樹關聯起來,同時
ViewRootImpl
還有控制View樹的重新整理、顯示以及輸入事件分發的作用。
建立Activity主界面
Android 系統中的Activity元件也是通過View樹與Window顯示出來的,隻是将建立View樹與Window的邏輯封裝到了生命周期的處理流程裡了。
所有Activity的視窗都是用一個
PhoneWindow
類的執行個體表示的,每個
PhoneWindow
都會建立一個内部類
DecorView
作為這個視窗中View樹的根View。下面的結構圖表示的就是
Activity
、
PhoneWindow
以及
DecorView
之間的關系,下面我們通過這個結構圖結合相關代碼來研究一下Activity主界面的View樹的建立過程。

- 應用裡Activity一般會在
裡通過調用onCreate
設定自己主界面的View樹布局,如下面的代碼:setContentView()
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
setContentView()
有多個重載實作,既可以傳遞布局資源ID也可以直接傳遞建立好的View。
Activity
裡會将
setContentView()
設定過來的View傳遞給
PhoneWindow
:
frameworks/base/core/java/android/app/Activity.java
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()傳回的就是PhoneWindow的執行個體。
- PhoneWindow裡的setContentView實作如下面的代碼:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
private DecorView mDecor;
private ViewGroup mContentParent;
public void setContentView(int layoutResID) {
...
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}
mContentParent
是用作為
ContentView
的父View的,
setContentView()
設定過來的Layout 資源ID通過
LayoutInflater
解析後會被添加到
mContentParent
的子View樹裡
- 如果這個Activity是第一次啟動,那麼
應該是mContentParent
的,需要在null
裡初始化所有的裝飾View:installDecor()
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
...
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
-
的作用是建立出generateDecor()
的執行個體,如下面:DecorView
protected DecorView generateDecor() {
...
return new DecorView(getContext(), -);
}
-
的作用是根據Activity設定的各種feature、Theme建立generateLayout()
、ContentParent
等Activity的基礎視圖架構,如下面:ActionBar
protected ViewGroup generateLayout(DecorView decor) {
...
// Inflate the window decor.
int layoutResource;
...
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
...
mDecor.finishChanging();
return contentParent;
}
如果
Activity
設定了需要
ActionBar
,那麼就會加載有
ActionBar
的布局資源:
if ((features & ( << FEATURE_ACTION_BAR)) != ) {
layoutResource = R.layout.screen_action_bar;
} else {
layoutResource = R.layout.screen_title;
}
有
ActionBar
的界面會使用
R.layout.screen_action_bar
布局資源,沒有
ActionBar
會使用
R.layout.screen_title
(5.0引入Material主題後有新引入了一個
R.layout.screen_toolbar
):
frameworks/base/core/res/res/layout/screen_action_bar.xml
<com.android.internal.widget.ActionBarOverlayLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/decor_content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:splitMotionEvents="false"
android:theme="?attr/actionBarTheme">
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.android.internal.widget.ActionBarContainer
android:id="@+id/action_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:transitionName="android:action_bar"
android:touchscreenBlocksFocus="true"
android:gravity="top">
......
</com.android.internal.widget.ActionBarContainer>
......
</com.android.internal.widget.ActionBarOverlayLayout>
contentParent
會取其中ID為
R.id.content
的View,應用的Activity裡設定的ContentView會被添加到
contentParent
的子View裡
上面是Activity的View樹的建立過程,而Activity的Window需要等到Activity resume的時候才會建立,同樣也是調用
WindowManager.addView
接口,而根View就是前面建立的
DecorView
:
frameworks/base/core/java/android/app/ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
...
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.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
...
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}
}
}