---
title: 'Window 和 WindowManager'
disqus: kyleAlien
---
Window 和 WindowManager
===
## OverView of Content
除了 Activity 顯示,[**Dialog**](https://hackmd.io/rLN00StPRmqok3zPzIsxjA?view#Android-Source) 再彈出視窗時也會使用到 WindowManager,而 WindowManager 並不是系統服務,而是應用服務 (每一個應用中都有一個)
在這裡我們不會涉及 Binder 對 WMS 的通訊過程,較為關注的是 APP 端的使用、WMS 管理的重點
[TOC]
## Window 概念
1. Window 是一個窗口,它被 Android 包裝成 View 呈現給使用者看,所以基本上你所看到的每個 View 都是一個 Window (為啥呢? 接下來會介紹)
> 
2. 在 Android 中的 [**Window 類**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/Window.java) 是一個抽象類,Window 的具體實現類是 [**PhoneWindow**](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/policy/PhoneWindow.java)
3. Android 中所有的 View 都是通過 Window 成現,其中包括了 `Activity`、`Dialog`、`Toast` 他們的 View 都是附加在 Window 上,**==Window 實際是 View 的直接管理者==**
:::info
其實內部的 Window 跟外部的 Window 是相同的,都是 PhoneWindow (關鍵:WindowManagerImpl#createLocalWindowManager 方法)
:::
> 但每個 View 都一定有各自的 ViewRootImpl + View + IWindow
>
> 
### [WindowManager](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/WindowManager.java) 概念
* WindowManager 是一個子系統,每個 APP 進程都有一個 WindowManager,它的實作在 [**SystemServiceRegistry**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/SystemServiceRegistry.java),分析 請參考 [**SystemServiceRegistry 單例**](https://hackmd.io/qhIoWcSZQ-GzL3G_Sgqb0A?view#ContextImpl-%E5%88%86%E6%9E%90---SystemServiceRegistry-%E5%96%AE%E4%BE%8B%E5%89%B5%E5%BB%BA-PhoneLayoutInflater)
```java=
// SystemServiceRegistry.java
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
```
:::warning
WindowManagerImpl 是 APP 的本地對象,尚未與 WMS 有關係
:::
* WindowManager 是用來 **與 WindowManagerServer 的 IPC 代理類**
> 
* 點擊部分是從 Window (PhoneWindow) 傳入 DecorView,再由 DecorView 傳入每個 View 中
> 
* WindowManager 中有許多為了管理 Window 所拓展的方法,但相對來說我們最常使用的方法都是由它繼承的 [**ViewManager 類**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewManager.java) 來
```java=
// WindowManager.java
public interface WindowManager extends ViewManager {
// 繼承 ViewManager 讓 Window 實現管理 View 的方法
...
}
// ViewManager.java
public interface ViewManager
{
// 方法其實都是在管理 View
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
```
:::success
WindowManager 雖與 WMS 中的方法不同,但 WindowManager 更像是 WindowManager 的約束協議
:::
### WindowManagerServer 簡介
* 簡單理解起來 WindowManagerServer 就是在管理大大小小 Window 的 z 軸,z-index 越大,放置越前面 (渲染也是通過 WMS 不過並不是 WMS 來渲染)
> 
:::success
* 詳細請參考另外一篇 [**WMS 文章**](https://hackmd.io/ymvebB0DTtyKluRpbSo_cg?view#WindowManagerService)
:::
### Window & WindowManager 關係
* 先了解 APP 端如何透過 WindowManager 添加一個 Window
1. 透過程式創建 View 物件
2. 設定 WindowManager.LayoutParams 參數:其中較為重要的是 type (窗口類型)、flags (窗口特性)
3. 取得 WindowManager 來訪問 WMS#addView 方法
```java=
// WindowManager 簡易使用
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addNewWindow();
}
// 直接透過 WindowManager 添加 View
private void addNewWindow() {
// 1. 創建 View 物件
Button button = new Button(this);
button.setText("HelloWorldBtn");
// 跟 ViewGroup#LayoutParams 類似,不過這邊多添加了 Type
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
// 2. Type: TYPE_APPLICATION 單獨應用
WindowManager.LayoutParams.TYPE_APPLICATION ,
0,
PixelFormat.TRANSPARENT);
// 2. 設定 Flag (Window 顯示特性)
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// (100, 300)
layoutParams.gravity = Gravity.CENTER | Gravity.TOP;
layoutParams.x = 100;
layoutParams.y = 300;
// 3. 取得 WindowManager 作為代理,與 WMS 通訊
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
// 透過代理呼叫 addView 方法
windowManager.addView(button, layoutParams);
}
}
```
> 下圖:有顯示一個 Button,並且透過 WindowManager 添加到螢幕上 (100, 300) 的位置
>
> 
* 其中在設定 **WindowManager.LayoutParams**,有兩個重點,flag、type 設定
1. **Flag**:可以控制 **Window 顯示的 ++特性++**,這些特性在 `WindowManager.LayoutParams` 中,以下列出幾個常用的
| Flag | 特性 | 數值 |
| -------- | -------- | -------- |
| FLAG_NOT_FOCUSABLE | 這個 Window 不需要獲取焦點、不需要任何輸入事件,直接往下傳送,**該 Flag 會同時啟動 FLAG_NOT_TOUCH_MODAL** | 0x00000008 |
| FLAG_NOT_TOUCH_MODAL | 會將當前 Window 以外事件往下傳遞,如果是 Window 內事件就自己處理 | 0x00000020 |
| FLAG_SHOW_WHEN_LOCKED | 讓該 Window 顯示在鎖屏的介面上 | 0x00080000 |
2. **Type**:**代表 Window 的類型**,這些類型在 `WindowManager.LayoutParams` 中,其也有許多類型,但 **大致分三類**
| Type | 特性 | 數值 | 例子 | 特性 |
| -------- | -------- | -------- | -------- | -------- |
| TYPE_APPLICATION | 應用 Window | 2 | Activity | 對應 Activity |
| TYPE_APPLICATION_PANEL | Child Window | 1000 | Dialog | 不能單個存在 |
| FIRST_SYSTEM_WINDOW | 系統 Window | 2000 | Toast | **必須聲明權限** 才能創建 Window |
:::info
* 若是系統 Window 但我們沒有聲明權限,則會拋出錯誤
```java=
// 設定 系統 Window
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
```
拋出 `WindowManager$BadTokenException` 錯誤 (permission denied)
> 
* 若要正常使用 SystemWindow 則需要在 **AndroidManifest** 中聲明 SystemWindow 權限
```xml=
# AndroidManifest.xml
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
```
:::
### 拖拉 Window
* 拖拉 Window 其實很簡單,監聽你要滑動的 View#onTouch 事件,在滑動時透過 WindowManager#`updateViewLayout` 更新 Window
```java=
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
if(event.getAction() == MotionEvent.ACTION_MOVE) {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.x = rawX;
layoutParams.y = rawY;
getWindowManager().updateViewLayout(button, layoutParams);
}
return false;
}
```
## Window 內部機制
Window 是一個抽象概念,**==每一個 Window 都對應一個 View==,而連接 View & Window 的則是 ViewRootImpl**
> 
並且介紹 Window 操控 View 的主要方法
```java=
// ViewManager.java
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
```
### WindowManager 實作類 - WindowManagerImpl
* 沒辦法透過 View 直接訪問 WindowManager,要訪問 View 必須通過 WindowManager 對象,但 WindowManager 是一個接口,它的實作類是 **WindowManagerImpl**
* Window 還有 WindowManagerImpl 是何時被創建出來,Window & WindowManager 的關係又是如何呢 ? 這裡我們來查看 Activity 的創建
1. 收到 AMS 創建 Acitivty 的通知時,會先觸發 APP 端的 [**ActivityThread**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ActivityThread.java)#performLaunchActivity 方法:主要是創建 Activity 對象,若 ActivityRecord 中有可使用的 Window 才會使用
> 這裡假設沒有可使用的 window
```java=
// ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
... 省略部分
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
... 省略 Instrumentationt 創建 Acitivty
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
...
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
// 如果 AcitivtyRecord 中 有還可以使用的 Window 才會使用
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
...
// 假設這裡傳入的 window 是 null
// @ 查看 attach 方法
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken, r.shareableActivityToken);
}
} /* 省略 catch */
return activity;
}
```
2. [**Activity**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Activity.java)#attach: 對於 Window 創建有以下幾個步驟
1. 創建 Window 實例對象 PhoneWindow
```java=
// Activity.java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, /* 省略部分參數 */) {
...
// 創建 PhoneWindow (非重點)
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
... 省略部分
// 替 PhoneWindow 設定 WindowManagerImpl
// @ ContextImpl#getSystemService
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE), // Context.WINDOW_SERVICE 就是 "window"
mToken, mComponent.flattenToString(), // mToken 就是 IApplicationThread
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
...
// 設定給成員變數,方便之後使用
mWindowManager = mWindow.getWindowManager();
}
```
:::info
* Window#**setWindowManager** 內其實會在做一層 WindowManagerImpl 包裝,在這 **之後透過 Acitivity 取得的 Window 就是有 Parent Window (PhoneWindow) 的 WindowManagerImpl 的引用** !!
> 也就是之後添加的 Window 都有 ParentWindow
```java=
// Window.java
// 第一個參數傳入 WindowManagerImpl
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
// @ 查看 createLocalWindowManager
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
// ----------------------------------------------------
// WindowManagerImpl.java
public final Context mContext;
private final Window mParentWindow;
private WindowManagerImpl(Context context, Window parentWindow,
@Nullable IBinder windowContextToken) {
mContext = context;
mParentWindow = parentWindow;
mWindowContextToken = windowContextToken;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);
}
```
> 
:::
2. 分析 context#**getSystemService**:取得 WindowManagerImpl 對象
```java=
// ContextImpl.java
@Override
public Object getSystemService(String name) {
...
// @ SystemServiceRegistry#getSystemService
return SystemServiceRegistry.getSystemService(this, name);
}
// -----------------------------------------------------
// SystemServiceRegistry.java
public final class SystemServiceRegistry {
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new ArrayMap<String, ServiceFetcher<?>>();
static {
... 省略部分
// 懶加載模式
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
// 真正創建 WindowManagerImpl 對象
return new WindowManagerImpl(ctx);
}});
}
public static Object getSystemService(ContextImpl ctx, String name) {
if (name == null) {
return null;
}
// 搜尋透過靜態加載的 ServiceFetcher 對象
final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
...
// 懶加載的真正創建時機
final Object ret = fetcher.getService(ctx);
...
return ret;
}
}
```
> 
* 到這裡就可以看到 App 端的 Window 創建 (PhoneWindow),還有 **每一個 Window 都有一個 WindowManagerImpl 對象**,而這個 WindowManagerImpl 就會用來添加、更新、移除 View
```java=
// 在 Activity 中使用 WindowManager 用法如下
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
```
:::info
PhoneWindow 就是透過 WindowManagerImpl 跟 WMS 通訊
:::
### [WindowManagerImpl](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/WindowManagerImpl.java) 添加 View 過程 - 本地創建 ViewRootImpl
* 我們前面知道 WindowManager 對於 View 的操控主要是在 ViewManager 中,這邊我們來看看 ViewManager#**addView** 方法,其實現就在 WindowManagerImpl 中 (如上面分析)
```java=
// WindowManagerImpl.java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
// 查詢 WindowManagerGlobal#addView 方法
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
```
:::success
* 從這裡可以看出來 WindowManager 並沒有太大的實作,但是它就是一個 **==橋接模式的設計==**,可以在橋接模式中調整需要的參數,再呼叫真正的實作類
> 在這裡真正的實作類是 WindowManagerGlobal
:::
* **WindowManagerGlobal 是每個 APP 進程的單例**,WindowManagerGlobal#`addView` 方法又會執行以下行為
1. 檢查 LayoutParams 參數是否合法,它必須是 WindowManager.LayoutParams 的子類
```java=
// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
// LayoutParams 參數必須是 WindowManager.LayoutParams
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
... 省略部分
}
```
2. **創建 [ViewRootImpl](**https://**) 對象**
```java=
// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
... 檢查參數
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
// 該 View 是否是 Sub Window
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
// 創建 ViewRootImpl 對象
root = new ViewRootImpl(view.getContext(), display);
// 將 Window#LayoutParams 設定給 View
view.setLayoutParams(wparams);
... 省略
}
}
```
:::info
* 在 ViewRootImpl 創建時,就會透過 WindowManagerGlobal#**getWindowSession** 取得一個 Session 對象,這個 **Session 對象是匿名的系統服務 (藏在 WMS 中)**
1. **==一個 APP 應用中,只有一個 Session==**
2. **這個 Session 對象 (IWindowSession),是 Client 端的代理 ! 之後 APP 可通過置這個代理與 WMS 通訊**
```java=
// ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(),
false /* useSfChoreographer */);
}
// -----------------------------------------------------------
// WindowManagerGlobal.java
// 單例中的靜態 member
private static IWindowSession sWindowSession;
// 每個 View 對應一個 IWindowSession
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
...
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
```
:::
> 
3. 將 ViewRootImpl、View、LayoutParams 加進 WindowManagerGlobal 的成員變數作緩存 (從這裡可以知道 **WindowManagerGlobal 中掌管了所有該 App 中所有的 View、ViewRootImpl**)
```java=
// WindowManagerGlobal.java
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, int userId) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
... 創建 ViewRootImpl
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
...
}
}
```
:::info
這三個 List 是保持一致,也就是說同個 index 取出的目標是描述同一個 View
:::
4. 呼叫 [**ViewRootImpl**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/ViewRootImpl.java)#`setView` 來 ^1.^ 繪製 View、^2.^ 透過 Session 通知 WMS 添加 Window
```java=
// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
... 緩存物件
try {
// 繪製 View
root.setView(view, wparams, panelParentView, userId);
} /* 省略 catch */
}
}
```
* 到 [**ViewRootImpl**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewRootImpl.java)#**setView** 步驟後就會通過
1. **透過 ==異步== 來完成 View 刷新的步驟**
* Session#addToDisplayAsUser 方法會傳入一個 mWindow 參數,該參數是讓 WMS 做回調
:::success
透過 Session 創建添加視窗之前,必須先呼叫 `requestLayout`,**確保 View 已經準備好接受事件**
:::
```java=
// ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
// 若該 ViewRootImpl 尚未綁定一個 View
if (mView == null) {
...
// 發起 Layout 請求
requestLayout(); // 本地 View 的繪製 (異步刷新 !)
...
try {
mOrigWindowType = mWindowAttributes.type;
...
adjustLayoutParamsForCompatibility(mWindowAttributes);
controlInsetsForCompatibility(mWindowAttributes);
// WMS 返回的資料
// @ 重點是看 addToDisplayAsUser 方法
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
mTempControls);
... 省略部分
} /* 省略 catch finally */
}
}
}
```
* 判斷 WMS 返回的結果,像是較常見的是 `ADD_BAD_APP_TOKEN`、`ADD_NOT_APP_TOKEN`... 等等
```java=
if (res < WindowManagerGlobal.ADD_OKAY) {
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
... 其他 case
}
}
```
2. 當 APP 端調用 mWindowSession#**addToDisplayAsUser** 方法後,就已經是跨進程訪問 [**Session**](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/wm/Session.java) 服務
```java=
// Session.java
final WindowManagerService mService;
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
// @ 終於到 WMS#addWindow 方法中
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
requestedVisibilities, outInputChannel, outInsetsState, outActiveControls);
}
```
> 
### Window 刪除 View 過程 - removeView
* 這邊我們就跳過 WindowManagerImpl 類 (上面分析過),直接來看 WindowGlobalManager#**removeView** 方法
```java=
// WindowManagerGlobal.java
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
// 已經死亡的 View
private final ArraySet<View> mDyingViews = new ArraySet<View>();
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) { // 同步鎖
// findViewLocked 用來找到要刪除的 View 的 index
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
private void removeViewLocked(int index, boolean immediate) {
// 從成員變數中移除該 View
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (root != null) {
root.getImeFocusController().onWindowDismissed();
}
// @ 分析 die 方法
boolean deferred = root.die(immediate);
if (view != null) {
// 斷開與 Parent 的連結
view.assignParent(null);
if (deferred) {
// 加入被銷毀的 View 列表 (該 View 尚未移除)
mDyingViews.add(view);
}
}
}
```
:::success
* Locked 方法取名方式
當方法中有 Locked 的標示,則是提醒使用者在呼叫該 Function 時,記得先鎖住線程
:::
* [**ViewRootImpl**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewRootImpl.java)#die 方法,該方法有分異步、同步,但最終都會走到 doDie 這個方法
```java=
// ViewRootImpl.java
boolean die(boolean immediate) {
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
// 透過 Handler 傳送 MSG_DIE 訊息,該訊息會觸發 doDie 函數
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
void doDie() {
// 檢查 Thread,Thread 必須要跟創建 ViewRootImpl 相同
checkThread();
...log 訊息
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
// @ 1. 主要方法是 dispatchDetachedFromWindow
dispatchDetachedFromWindow();
}
... 省略部分
mAdded = false;
}
// @ 2. 查看 doRemoveView 方法
WindowManagerGlobal.getInstance().doRemoveView(this);
}
```
1. dispatchDetachedFromWindow:^1.^ 通知 View 即將被移除、^2.^ 訪問 Session 系統服務移除該 Window
```java=
// ViewRootImpl.java
void dispatchDetachedFromWindow() {
mInsetsController.onWindowFocusLost();
mFirstInputStage.onDetachedFromWindow();
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
// 通知 View 即將被移除
// 如果 View#onDetachedFromWindow 有被覆寫 就會被通知到
mView.dispatchDetachedFromWindow();
}
... 省略部分
try {
// @ 分析 Session#remove 方法
mWindowSession.remove(mWindow);
} /* 省略 catch */
... 省略部分
// 移除繪製任務
unscheduleTraversals();
}
```
2. doRemoveView 方法從 **WindowManagerGlobal 中移除該 View 緩存**
```java=
// WindowManagerGlobal.java
void doRemoveView(ViewRootImpl root) {
boolean allViewsRemoved;
synchronized (mLock) {
// 找到 view index
final int index = mRoots.indexOf(root);
if (index >= 0) {
// 移除 WindowManagerGlobal 與該 View 相關的所有緩存
mRoots.remove(index);
mParams.remove(index);
final View view = mViews.remove(index);
mDyingViews.remove(view); // 真正移除
}
allViewsRemoved = mRoots.isEmpty();
}
...
}
```
* [**Session**](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/wm/Session.java)#**remove** 方法會在內部在調用 WMS#removeWindow 方法
```java=
final WindowManagerService mService;
@Override
public void remove(IWindow window) {
// 呼叫 @ WMS#removeWindow 方法
mService.removeWindow(this, window);
}
```
> 
### Window 更新 View 過程 - updateViewLayout
* 略過上面講的 WindowManagerImpl 類,直接看 WindowGlobalManager#updateViewLayout 方法
```java=
// WindowManagerGlobal.java
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
// 重新設定 View 的 LayoutParams
view.setLayoutParams(wparams);
synchronized (mLock) {
// 找到對應 View 的 index
int index = findViewLocked(view, true);
// 找到 View 對應的 ViewRootImpl 對象
ViewRootImpl root = mRoots.get(index);
// 移除舊設定,改為新設定
mParams.remove(index);
mParams.add(index, wparams);
// @ 呼叫 ViewRootImpl#setLayoutParams 方法
root.setLayoutParams(wparams, false);
}
}
```
* ViewRootImpl#setLayoutParams 會觸發 App 端的 View 重新繪製 (跑 measure、layout、draw 流程),繪製完成後同樣會呼叫 Session 去 WMS 通知 Window 已更新 View
```java=
// ViewRootImpl.java
public void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
... 省略部分
// 繪製完成後會調用 WMS 的 方法
scheduleTraversals(); // 觸發 "異步" 的更新 View 流程
}
}
private void performTraversals() {
final View host = mView;
... 省略部分
if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
|| mForceNextWindowRelayout) {
... 省略部分
try {
...
// @ 分析 relayoutWindow 方法
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
} /* 省略 catch */
...
}
}
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
...
// @ 呼叫 Session#relayout 方法
int relayoutResult = mWindowSession.relayout(mWindow, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mSurfaceSize);
...
}
```
* 每個 View 都有一個 IWindow,將 IWindow 傳給 WMS & [**Session**](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/wm/Session.java) 做 View 的更新後續動作
```java=
// Session.java
@Override
public int relayout(IWindow window, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
...
int res = mService.relayoutWindow(this, window, attrs,
requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
outActiveControls, outSurfaceSize);
...
return res;
}
```
> 
## Window 常見使用
這裡會依照 Window Type 的特性來舉例,以最重點的三個分類 ApplicationWindow、SubWindow、SystemWindow 介紹
### [Activity](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Activity.java) 創建 - ApplicationWindow
* 我們在 Activity#onCreate 加載 xml 布局文件時,會透過 Activity#setContentView 方法,而其實它就是調用了 PhoneWindow#**setContentView**
```java=
// Activity.java
public void setContentView(@LayoutRes int layoutResID) {
// getWindow 實際上實作類是 PhoneWindow
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
// ---------------------------------------------------------------
// PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 如果還沒有 Decor View 就會創建一個 DecorView 對象
// @ installDecor 方法
installDecor();
}
...
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
// 通知 Acitivty 布局已經加載完成 (但不代表 Window 已經加載到 WMS)
cb.onContentChanged();
}
...
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// @ 透過 generateDecor 創建 DecorView 中
mDecor = generateDecor(-1);
...
} else {
// 設定 DecorView 的 Window 為 PhoneWindow
mDecor.setWindow(this);
}
if (mContentParent == null) {
// @ generateLayout 對應使用者設定主題,加載布局到 R.id.content
mContentParent = generateLayout(mDecor);
...
}
}
```
:::info
* PhoneWindow 加載完成後會呼叫,CallBack 表示當前布局已經加載,Activity 本來就有這個 CallBack,所以可以知道這個布局何時被加載完 (這不代表 Window 已經加入 WMS 中)
:::
1. PhoneWindow#generateDecor 方法:會判斷是否已經加載 DecorView,如果尚未加載,就透過 generateDecor 方法創建 DecorView 對象
```java=
// PhoneWindow.java
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
```
2. PhoneWindow#generateLayout 方法:會按照你對 APP 主題 (style) 的設定,挑選相對應的布局做加載,而每個布局之中都會有 `R.id.content` ViewGroup (用來加載使用者布局)
```java=
// Window.java
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
// ----------------------------------------------------------------
// PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
... 省略部分
WindowManager.LayoutParams params = getAttributes();
... 省略部分
// Inflate the window decor. (查找指定主題)
int layoutResource;
if() {
... 省略主題判斷
} else if() {
... 省略主題判斷
} else {
// 最基礎的布局
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
// 加載指定布局
// @ Decor#onResourcesLoaded 方法
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 找到指定主題中的 R.id.content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 找不到 R.id.content 會直接拋錯
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
... 省略部分
return contentParent;
}
```
3. 呼叫 [**DecorView**](https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/policy/DecorView.java)#onResourcesLoaded 方法:透過 inflater 加載 xml 布局,在將加載好的 View 添加進 ViewTree (目前尚未添加到 Window 中)
```java=
// DecorView.java
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
...
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
```
:::warning
到 DecorView#onResourcesLoaded 這裡只是將使用者布局加入 ViewTree,還 **尚未** 把該 Window 添加到 WindowManager 中,Window 無法提供具體功能
:::
* Activity 被 AMS 呼叫 Resume 時,會通知到 [**ActivityThread**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ActivityThread.java)#**handleResumeActivity** 方法,**最終會通知 [Acitivty](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Activity.java) 顯示 DecorView**
```java=
// ActivityThread.java
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
...
// 透過 Instrumentation 呼叫 Activity#onResume 方法
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
...
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
...
if (r.activity.mVisibleFromClient) {
// @ 呼叫 makeVisible 方法
r.activity.makeVisible();
}
}
...
}
// ----------------------------------------------------------------
// Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
// @ PhoneWindow#getAttributes
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
// ----------------------------------------------------------------
// Window.java
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
public final WindowManager.LayoutParams getAttributes() {
return mWindowAttributes;
}
```
* 在這裡我們似乎沒有看到 WindowManager#LayoutParams 的 Type 被設定為 TYPE_APPLICATION 類型,這是因為 [**WindowManager**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/WindowManager.java)#LayoutParams 預設類型就是
```java=
// WindowManager.javav
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
// 預設為 TYPE_APPLICATION 類型
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
... 省略部分
}
```
### Dialog 創建 - ApplicationWindow
* Dialog 分析之前有寫過另外一篇有關 [**Dialog 的文章**](https://hackmd.io/ZMoSaWeeRTGWQnW3M3y_6Q),這裡會提及它是何時轉為 SubWindow Type
* 當 Dialog 要顯示時會呼叫 Dialog#show 方法,該方法會呼叫 WindowManagerImpl#addView 方法,在這邊 Dialog 就有 Parent,所以會傳入 Parent's Window
```java=
// 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);
// @ addView 方法
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
}
```
* 如果沒有特別設定 Dialog 的 WindowManager#LayoutParams,它的 Window#Type 就是 `TYPE_APPLICATION`
> 
### PopupWindow 創建 - SubWindow
// TODO:
### Toast 創建 - SystemWindow
// TODO:
### StatusBar 創建 - SystemWindow
## Appendix & FAQ
:::info
:::
###### tags: `Android Framework`