--- 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 (為啥呢? 接下來會介紹) > ![](https://i.imgur.com/UakZPob.png) 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 > > ![](https://i.imgur.com/JgrYEbx.png) ### [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 代理類** > ![](https://i.imgur.com/6rYpyo5.png) * 點擊部分是從 Window (PhoneWindow) 傳入 DecorView,再由 DecorView 傳入每個 View 中 > ![](https://i.imgur.com/NvKZAR8.png) * 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 來渲染) > ![](https://i.imgur.com/MCsG4RK.png) :::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) 的位置 > > ![](https://i.imgur.com/ovhRXEY.png) * 其中在設定 **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) > ![](https://i.imgur.com/sq5JtI9.png) * 若要正常使用 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** > ![](https://i.imgur.com/davUJA5.png) 並且介紹 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); } ``` > ![](https://i.imgur.com/xXuSjpI.png) ::: 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; } } ``` > ![](https://i.imgur.com/KBz1Img.png) * 到這裡就可以看到 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; } } ``` ::: > ![](https://i.imgur.com/WSF5Vuh.png) 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); } ``` > ![](https://i.imgur.com/LfBpib8.png) ### 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); } ``` > ![](https://i.imgur.com/ewnzQ1A.png) ### 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; } ``` > ![](https://i.imgur.com/v92sj7d.png) ## 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` > ![](https://i.imgur.com/JuFJjeq.png) ### PopupWindow 創建 - SubWindow // TODO: ### Toast 創建 - SystemWindow // TODO: ### StatusBar 創建 - SystemWindow ## Appendix & FAQ :::info ::: ###### tags: `Android Framework`