Android 8.0 原始碼分析 (十) WindowManagerService 的視窗管理

DevYK發表於2019-11-12

前言

上一篇文章我們分析了 WindowManager, 該篇文章我們就得趁熱打鐵繼續分析 WindowManager 的管理者 WindowManagerService 簡稱 WMS ,WMS 不只是 WindowManager 的管理者,它還有很多重要的職責,該篇文章將為大家分析 WMS 的職責、WMS 的建立過程、WMS 的成員以及 Window 在 WMS 中的新增和刪除操作,在閱讀該篇文章之前,請保證已經對 WindowManager 有過了解,如果沒有了解請先看 Android 8.0 原始碼分析 (九) WindowManager 的內容。

Android 8.0 原始碼分析 (一) SystemServer 程式啟動

Android 8.0 原始碼分析 (二) Launcher 啟動

Android 8.0 原始碼分析 (三) 應用程式程式建立到應用程式啟動的過程

Android 8.0 原始碼分析 (四) Activity 啟動

Android 8.0 原始碼分析 (五) Service 啟動

Android 8.0 原始碼分析 (六) BroadcastReceiver 啟動

Android 8.0 原始碼分析 (七) ContentProvider 啟動

Android 8.0 原始碼分析 (八) ActivityManagerService

Android 8.0 原始碼分析 (九) WindowManager

Android 8.0 原始碼分析 (十) WindowManagerService 的視窗管理

WMS 的職責

很多開發者應該都知道 WMS 是 Android 中很重要的一個服務,它是 WindowManager 的管理者,WMS 無論對於應用開發還是 Framework 開發都是重要的知識點,其原因是因為 WMS 有很多職責,每個職責都會涉及重要且複雜的系統,這使得 WMS 就像一個十字路口的交通燈一樣,沒有了這個交通燈,十字路口就無法正常通車。WMS 的職責用簡潔的語言介紹主要有以下幾點。

1. 視窗管理

WMS 是視窗的管理者,它負責視窗的啟動、新增和刪除。另外視窗的大小和層級也是由 WMS 進行管理的。視窗管理的核心成員有 DisplayContentWindowTokenWindowState.

2. 視窗動畫

視窗間進行切換時,使用動畫可以顯得更炫一些,視窗動畫由 WMS 的動畫子系統來負責,動畫子系統的管理者為 WindowAnimator

3. 輸入系統中轉站

通過對視窗的觸控從而產生觸控事件,InputManagerService(IMS) 會對觸控事件進行處理,它會尋找一個最合適的視窗來處理觸控反饋資訊,WMS 是視窗的管理者,它作為輸入系統的中轉站再合適不過了。

4. Surface 管理

視窗不具備繪製功能,因此每個視窗都需要有一塊 Surface 來供自己繪製,為每個視窗分配 Surface 是由WMS 來完成的。

小總結:

WMS 的職責可以簡單總結為下圖所示:

M8kDOS.png

從上圖我們瞭解到 WMS 整個是很複雜的,與它關聯的有視窗管理、視窗動畫、輸入系統中轉站和 Surface 管理, 它們每一個都是重要且複雜的系統,本章就對視窗管理來進行分析,因為它跟我們應用開發關係真的是太緊密了。

WMS 建立過程

在介紹 WMS 之前我們還是非常有必要知道 WMS 是何時建立的,如果對 SystemServer 瞭解的話應該知道 WMS 是在系統程式中進行啟動,感興趣的可以看 Android 8.0 原始碼分析 (一) SystemServer 程式啟動 裡面有詳細的介紹, 下面我們直接看 SystemServer 的入口 main 方法,程式碼如下:

//SystemServer.java
    /**
     * 這裡的 main 函式 主要是 zygote 通過反射呼叫
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }
複製程式碼

在 main 方法中呼叫了 SystemServer 的 run 方法,程式碼如下:

//SystemServer.java
    private void run() {
        try {
           ...
            /**
             * 建立 主執行緒的訊息 Looper
             */

            Looper.prepareMainLooper();

            // Initialize native services.
            /**
             * 1. 載入動態庫 libandroid_servers .so
             */
            System.loadLibrary("android_servers");

      			...
            /**
             * 建立系統級別的 Context
             */
            createSystemContext();

            /**
             * 2. 建立 SystemServiceManager 它會對系統服務進行建立、啟動和生命週期管理
             */
            mSystemServiceManager = new SystemServiceManager(mSystemContext);
            mSystemServiceManager.setRuntimeRestarted(mRuntimeRestart);
            LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
            // Prepare the thread pool for init tasks that can be parallelized
            SystemServerInitThreadPool.get();
        } finally {
            traceEnd();  // InitBeforeStartServices
        }

        // Start services.
        try {
            traceBeginAndSlog("StartServices");
            /**
             * 3. SystemServiceManager啟動了 AMS 、PowerMS、PackageMS 等服務
             */
            startBootstrapServices();
            /**
             * 4. 啟動了 DropBoxManagerService、BatteryService、UsageStatsService 和 WebViewUpdateService
             */
            startCoreServices();
            /**
             * 5. 啟動了CameraService、AlarmManagerService、VrManagerService 等服務。
             */
            startOtherServices();
            SystemServerInitThreadPool.shutdown();
        } catch (Throwable ex) {
          ...
        } finally {
            traceEnd();
        }
				...
    }
複製程式碼

如果看過我之前分析的 Android 8.0 原始碼應該知道這裡的 main 函式,我們們已經遇見過很多次了吧,上面註釋也很清楚那麼我們就直接來到 WMS 初始化的地方吧,請看註釋 5 的 startOtherServices 方法,程式碼如下:

//SystemServer.java
    private void startOtherServices() {
     ... 
      
      			/**
             * 1. 得到 Watchdog,它是用來監控系統的一些關鍵服務的執行狀況
             */
            final Watchdog watchdog = Watchdog.getInstance();
            /**
             * 2. 對 Watchdog 進行一些初始化
             */
            watchdog.init(context, mActivityManagerService);
            traceEnd();

            traceBeginAndSlog("StartInputManagerService");
            /**
             * 3. 建立 IMS 服務,並賦值給 inputManager
             */
            inputManager = new InputManagerService(context);
            traceEnd();

            traceBeginAndSlog("StartWindowManagerService");
            // WMS needs sensor service ready
            ConcurrentUtils.waitForFutureNoInterrupt(mSensorServiceStart, START_SENSOR_SERVICE);
            mSensorServiceStart = null;
            /**
             * 4. 執行 WMS main 方法,其內部會初始化 WMS
             */
            wm = WindowManagerService.main(context, inputManager,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                    !mFirstBoot, mOnlyCore, new PhoneWindowManager());
            /**
             * 5. 將 WMS 註冊到 ServiceManager 中
             */
            ServiceManager.addService(Context.WINDOW_SERVICE, wm);
            /**
             * 6.將 IMS 註冊到 ServiceManager 中
             */
            ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
            traceEnd();
      
      ...
        
         try {
            /**
             * 7. 初始化螢幕顯示資訊
             */
            wm.displayReady();
        } catch (Throwable e) {
         ...
        }
      ...
        try {
            /**
             * 8. 用來通知 WMS ,系統的初始化已經完成,其內部呼叫 WindowManagerPolicy 的 systemReady 方法
             */
            wm.systemReady();
        } catch (Throwable e) {
         ...
        }
      ...
    }
複製程式碼

startOtherServices 方法主要用於啟動系統其它服務,啟動的服務大概有 100 個左右,上面程式碼只是列出跟本章相關的核心程式碼,我們直接看註釋 1,2 分別得到 Watchdog 並對它初始化,Watchdog 主要用來監控系統的一些關鍵服務的執行狀況。註釋 3 建立了 IMS, 註釋 4 執行 WMS 的 main 方法,其內部會建立 WMS, 註釋 5 ,6 分別把 IMS、WMS 註冊到 ServiceManager 中,這樣的話如果某個客戶端想要使用 WMS ,就需要先去 ServiceManager 中查詢資訊,然後根據與 WMS 所在的程式建立通訊鏈路,客戶端就可以使用 WMS 了。在註釋 7 處是用來初始化螢幕顯示資訊,最後註釋 8 則使用來通知 WMS ,告知系統初始化工作已經完成,其內部會呼叫 WindowManagerPolicy 的 systemReady 方法。因為該小節主要分析 WMS 啟動,那麼我們直接分析註釋 4 ,看它的呼叫,程式碼如下:

//WMS.java
    public static WindowManagerService main(final Context context, final InputManagerService im,
            final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore,
            WindowManagerPolicy policy) {
        /**
         * 1. 得到 DisplayThead 中的 handler 例項,呼叫 handler 的 runWithScissors 方法,使用來處理需要低延遲顯示的相關操作,並
         * 只能由 WindowManger, DisplayManager 和 InputManager 實時執行快速操作
         */
        DisplayThread.getHandler().runWithScissors(() ->
                /**
                 * 2. 建立 WMS 例項執行在 Runnable 的 run 方法中
                 */
                sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
                        onlyCore, policy), 0);
        return sInstance;
    }
複製程式碼

可以看到上面的程式碼是一個 Java8 中的一個特性使用了 lambda 表示式,在註釋一處拿到了 "android.display” 該執行執行緒的 Handler 物件,呼叫 runWithScissors 方法,它是用來執行一些低延遲的相關操作,並只能由 WindowManager、DisplayManager、和 InputManager 來實時執行,註釋 2 建立了 WMS 例項,這裡 HandlerrunWithScissors 函式比較陌生,待會我們會介紹它,注意第二個引數 timeout 傳遞的是 0 ,,既然比較陌生那我們就先去看看它到底是什麼?如果有對 Handler 原始碼不瞭解的可以看我之前對 Handler 的分析移動架構 (二) Android 中 Handler 架構分析,並實現自己簡易版本 Handler 框架 程式碼如下:

//Handler.java
    public final boolean runWithScissors(final Runnable r, long timeout) {
        if (r == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout must be non-negative");
        }

        /**
         * 1. 判斷當前的執行緒是否是 Handler 所指向的執行緒
         */
        if (Looper.myLooper() == mLooper) {
            r.run();
            return true;
        }
        /**
         * r: 外部傳遞進來的 Runnable
         */
        BlockingRunnable br = new BlockingRunnable(r);
        return br.postAndWait(this, timeout);
    }
複製程式碼

上面程式碼主要判斷傳遞進來的 Runnable 是否為空,和延遲時間是否小於 0 ,如果不滿足就丟擲異常。註釋一處就是判斷當前 SystemServer 所執行的執行緒是否是 Handler 所指向的執行緒(android.dispaly)執行緒,如果是直接執行 run 方法,如果不是則呼叫 BlockingRunnable 的 postAndWait 方法並把當前的 Handler 和延遲時間傳遞進去,BlockingRunnableHandler 的內部類,我們來看它的具體實現,程式碼如下:

//Handler.java

    private static final class BlockingRunnable implements Runnable {
        private final Runnable mTask;
        private boolean mDone;

        public BlockingRunnable(Runnable task) {
            mTask = task;
        }

        @Override
        public void run() {
            try {
                /**
                 * 1.
                 */
                mTask.run();
            } finally {
                synchronized (this) {
                    mDone = true;
                    notifyAll();
                }
            }
        }

        public boolean postAndWait(Handler handler, long timeout) {
            /**將當前的 BlockingRunnable 新增到 Handler 的任務佇列中。
             * 2. 
             */
            if (!handler.post(this)) {
                return false;
            }

            synchronized (this) {
                if (timeout > 0) {
                    final long expirationTime = SystemClock.uptimeMillis() + timeout;
                    while (!mDone) {
                        long delay = expirationTime - SystemClock.uptimeMillis();
                        if (delay <= 0) {
                            return false; // timeout
                        }
                        try {

                            wait(delay);
                        } catch (InterruptedException ex) {
                        }
                    }
                } else {
                    while (!mDone) {
                        try {
                            /**
                             * 3. 
                             */
                            wait();
                        } catch (InterruptedException ex) {
                        }
                    }
                }
            }
            return true;
        }
    }

複製程式碼

根據上面程式碼我們分析得出,在註釋 2 中將當前的 BlockingRunnable 物件新增到 Handler 任務佇列中。前面呼叫 runWithScissors 方法的第二個引數傳遞的是 0 ,因此 timeout > 0 不成立,這樣如果 mDone 為 false 的話就會執行註釋 3 讓當前執行緒進入等待狀態,那麼等待的是哪個執行緒呢?我們往上看,在註釋 1 處執行了傳入的 Runnable 的 run 方法(執行在 android.display 執行緒),執行完之後將 mDone 設定為 true, 並呼叫 notifyAll 方法將等待狀態的執行緒進行喚醒,這樣就不會繼續呼叫註釋 3 處的等待方法了,因此得出結論,SystemServer 所執行的執行緒等待的就是 android.display 執行緒,一直到 android.display 執行緒執行完畢在執行 SystemServer 所在的執行緒,這是因為內部執行了 WMS 的建立,而 WMS 的建立優先順序要更高。WMS 的建立就講到這裡,最後我們檢視 WMS 的建構函式,程式碼如下:

//WMS.java

    private WindowManagerService(Context context, InputManagerService inputManager,
            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
            WindowManagerPolicy policy) {
    ...
        /**
         * 1. 將傳遞進來的 IMS 賦值給 mInputManager
         */
        mInputManager = inputManager; // Must be before createDisplayContentLocked.
        mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
        mDisplaySettings = new DisplaySettings();
        mDisplaySettings.readSettingsLocked();

       ...
        /**
         * 2. 拿到 DisplayManager 物件,呼叫它的 getDisplays 方法拿到 Displays陣列 每個顯示裝置都有一個 Display 例項
         */
        mDisplays = mDisplayManager.getDisplays();

        for (Display display : mDisplays) {
            /**
             * 3. 遍歷 Display 陣列,將 Display 物件封裝成 DiplayContent,DisplayContent 用來描述一塊螢幕
             */
            createDisplayContentLocked(display);
        }

      ...
        /**
         * 4. 得到 AMS 例項並賦值給 WMS 中成員變數 mActivityManager
         */
        mActivityManager = ActivityManager.getService();
 				...

        /**
         * 5. 建立 WindowAnimator 物件它用於管理所有視窗的動畫
         */
        mAnimator = new WindowAnimator(this);

        mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
                com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);



        LocalServices.addService(WindowManagerInternal.class, new LocalService());
        /**
         * 6. 初始化視窗管理策略的介面類
         */ 
      	initPolicy();

        /**
         * 7.將自身的 WMS 通過 addmonitor 方法新增到 Watchdog 中,
         * 它是用來監控系統的一些關鍵服務的執行狀況,這些被監控的服務都會實現 Watchdog.Monitor 介面。
         * Watchdog 每分鐘都會對被監控的系統服務進行檢查,如果被監控的系統服務出現了死鎖,則會殺死 Watchdog 所在的程式,也就是 SystemServer 程式
         */
        Watchdog.getInstance().addMonitor(this);


    }
複製程式碼

在上面建構函式中大概做了 7 件事兒

  1. 註釋 1 處用來儲存傳進來的 IMS, 這樣就相當於 WMS 持有了 IMS 引用
  2. 註釋 2 處通過 DisplayManagergetDisplays 方法得到 Display 陣列(每個顯示裝置都有一個 Display 例項)接著遍歷 Display 陣列
  3. 註釋 3 遍歷 Display 陣列,將 Display 物件封裝成 DiplayContent,DisplayContent 用來描述一塊螢幕
  4. 註釋 4 得到 AMS 代理物件並賦值給 WMS 中成員變數 mActivityManager,這樣 WMS 就持有了 AMS 的引用
  5. 註釋 5 建立 WindowAnimator 物件,它用於管理所有視窗的動畫
  6. 初始化視窗管理策略的介面類 WindowManagerPolicy,它用來定義一個視窗策略索要遵循的通用規範
  7. 將自身的 WMS 通過 addMonitor 方法新增到 Watchdog 中,它是用來監控系統的一些關鍵服務的執行狀況,這些被監控的服務都會實現 Watchdog.Monitor 介面。 Watchdog 每分鐘都會對被監控的系統服務進行檢查,如果被監控的系統服務出現了死鎖,則會殺死 Watchdog 所在的程式,也就是 SystemServer 程式,

我們看註釋 6 處的實現:

//WMS.java

    private void initPolicy() {
        UiThread.getHandler().runWithScissors(new Runnable() {
            @Override
            public void run() {
                WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
								//1. 
                mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
            }
        }, 0);
    }
複製程式碼

這裡是不是跟之前介紹 DisplayThread 處理的方式有點相似吧?我們看註釋 1 處執行了 WMP 的 init 方法,init 方法具體執行在 PhoneWindowManager 中實現。PMW 的 init 方法執行在 android.ui 執行緒中,它的優先順序高於 android.display 執行緒,因此 android.display 執行緒需要等待 PMWinit 方法執行完畢後,處理等待狀態的 android.display 執行緒才會被喚醒從而繼續執行下面的程式碼。到目前為止我們們已經涉及到了 3 個執行緒(SystemSerer 所線上程也就是 system_server、android.display、android.ui)便於理解,我這裡繪製了一個簡圖,請看下面:

MKjr59.png

從上圖可以看出,三個執行緒之前的關係分為三個步驟來實現:

  1. 首先在 system_server 執行緒中執行了 SystemServerstartOtherServices 方法,在 startOtherServices 方法中會呼叫 WMS 的 main 方法,main 方法會建立 WMS ,建立的過程在 android.display 執行緒中實現,建立 WMS 的優先順序更高,因此 system_server 執行緒要等待 WMS 建立完成後,處於等待狀態的 system_server 執行緒才會被喚醒從而繼續執行下面的程式碼。
  2. 在 WMS 的構造方法中會呼叫 WMS 的 initPolicy 方法,在 initPolicy 方法中又會呼叫 PWM 的 init 方法, PWM 的 init 方法在 android.ui 執行緒中執行,它的優先順序要高於 android.display 執行緒,因此 android.display 執行緒要等待 PWMinit 方法執行完畢後,處於等待狀態的 android.display 執行緒才會被喚醒從而繼續執行下面的程式碼。
  3. PWMinit 方法執行完成後,android.display 執行緒就完成了 WMS 的建立,等待的 system_server 執行緒被喚醒後繼續執行 WMSmain 方法後的邏輯,比如 WMSdisplayReady 方法用來初始化螢幕顯示資訊(SystemServer 的 startOtherServices 方法註釋 7 處)

WMS 重要成員

想要更好地理解 WMS ,不但要了解 WMS 是如何建立的,還要知道 WMS 的重要成員,這裡的重要成員指的是 WMS 的部分成員變數,下面以一張表格來具體描述它們的作用,如下所示:

成員 型別 說明
mPolicy WindowManagerPolicy 是視窗管理策略的介面類,用來定義一個視窗策略索要遵循的通用規範,並提供了 WindowManager 所有特定的 UI 行為。它的具體實現類為 PhoneWindowManager, 這個實現類在 WMS 建立時被建立。WMP 允許定製視窗層級和特殊視窗型別以及關鍵的排程和佈局。
mSessions ArraySet 主要用於程式間通訊,其它應用程式程式想要和 WMS 程式進行通訊就需要經過 Session, 並且每個應用程式程式都會對應一個 Session, WMS 儲存這些 Session 用來記錄所有向 WMS 提出視窗管理服務的客戶端。
mWindowMap WindowHashMap 它繼承自 HashMap, 限制了 HashMap 的 key 值的型別為 IBinder , value 的值的型別為 WindowState . WindowState 用於儲存視窗的資訊,在 WMS 中它用來描述一個視窗。綜上得出結論,它就是用來儲存 WMS 中各種視窗的集合。
mFinishedStarting ArrayList 它的元素型別為 AppWindowToken, 它是 WindowToken 的子類,mFinishedStarting 就是用來儲存已經完成啟動的應用程式視窗(比如 Activity)的 AppWindowToken 的列表。
mResizingWindows ArrayList 用來儲存正在調整大小的視窗的列表
mAnimator WindowAnimator 用於管理視窗的動畫以及特效動畫
mH H 用於將任務加入到主執行緒的訊息佇列中,這樣的程式碼邏輯就會在主執行緒中執行。
mInputManager InputManagerService 它是輸入系統的管理者,它會對觸控事件進行處理,會尋找一個最合適的視窗來處理觸控反饋資訊。

這裡在補充一個知識 WindowToken (它是 mFinishedStarting 元素型別 AppWindowToken 的父類)

  • 可以理解為視窗令牌,當應用程式想要向 WMS 申請新建立一個視窗,則需要向 WMS 出示有效的 WindowToken 。AppWindowToken 作為 WindowToken 的子類,主要用來描述應用程式的 WindowToken 結構,應用程式中每個 Activity 都對應一個 AppWindowToken
  • WindowToken 會將通一個元件(比如同一個 Activity)的視窗 (WindowState)集合在一起,方便管理

Window 的新增過程(WMS 處理部分)

在上一篇文章中我們講到了對 Window 的操作是分為兩大部分一部分是 WindowManager ,另一部分是 WindowManagerService , 在上一篇文章中學習到了在 WindowManager 的處理過程,那麼該小節為大家介紹 WMS 中 addWindow 的處理過程,原始碼如下:

//WMS.java
    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        int[] appOp = new int[1];
        /**
         * 1. 根據 Window 的屬性,呼叫 WMP 的 checkAddPermission 方法來檢查許可權,具體在 PhoneWindowManager 的 checkAddPermission
         *    方法中實現,如果沒有許可權則不會執行後續的程式碼邏輯
         */
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }

        ...

        synchronized(mWindowMap) {
            if (!mDisplayReady) {
                throw new IllegalStateException("Display has not been initialialized");
            }

            /**
             * 2. 通過 displayID 來獲得視窗要新增到那個 DisplayContent 上,如果沒有找到 DisplayContent 則返回 WindowManagerGlobal.ADD_INVALID_DISPLAY 
             *    這一狀態,其中 DisplayContent 用來描述一塊螢幕
             */
            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
            if (displayContent == null) {
                Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
                        + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            if (!displayContent.hasAccess(session.mUid)
                    && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
                Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
                        + "does not have access: " + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            if (mWindowMap.containsKey(client.asBinder())) {
                Slog.w(TAG_WM, "Window " + client + " is already added");
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }

            /**
             * 3. type 代表一個視窗的型別,它的數值介於 FIRST_SUB_WINDOW 和 LAST_SUB_WINDOW 之間 (1000~1999)
             *    定義在 WindowManager 中,代表子類視窗定義
             */
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                /**
                 * 4. attrs.token 是 IBinder 型別的物件,windowForClientLocked 方法內部會根據 attrs.token 作為 key 值從 mWindowMap 中得到該子視窗的父視窗。接著對父視窗進行判斷,如果
                 *    父視窗為 null 或者 type 的取值範圍不正確這會返回錯誤的狀態。
                 */
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }
						...
            /** 
             * 5. 通過 displayContent 的 getWindowToken 得到 WindowToken 物件
             */
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
            
            /**
             * 6. 如果有父視窗就將父視窗的 type 值賦值給 rootType , 如果沒有將當前視窗的 type 值賦值給 rootType.接下來
             *    如果 WindowToken 為 null ,則根據 rootType 或者 type 的值進行區分判斷,如果 rootType 值等於 TYPE_INPUT_METHOD、TYPE_WALLPAPER 
             *    等值時,則返回狀態值 WindowManagerGlobal.ADD_BAD_APP_TOKEN ,說明 rootType 值等於 TYPE_INPUT_METHOD、TYPE_WALLPAPER  值時
             *    是不允許 WindowToken 為 null 的
             */
            final int rootType = hasParent ? parentWindow.mAttrs.type : type;

            boolean addToastWindowRequiresToken = false;

            if (token == null) {
                if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_INPUT_METHOD) {
                    Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_TOAST) {
                    // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                            parentWindow)) {
                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                }
                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                /**
                 * 7. 通過多次的條件判斷之後,會隱式建立 WindowToken,這說明當我們新增視窗時可以不向 WMS 提供 WindoToken ,
                 *    前提是 rootType 和 type 的值不為前面條件判斷篩選的值。WindowToken 隱式和顯示的建立肯定是要加以區分的,在該物件的第 4 個引數傳入
                 *    false 就代表是隱式建立的。
                 *    
                 */
                token = new WindowToken(this, binder, type, false, displayContent,
                        session.mCanAddInternalSystemWindow);

                /**
                 * 8. 判斷如果視窗為應用程式視窗
                 */
            } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                /**
                 * 9. 將 WindowToken 轉換為專門針對應用程式視窗的 AppWindowToken, 然後根據 AppWindowToken 的值進行後續的判斷
                 */
                atoken = token.asAppWindowToken();
                if (atoken == null) {
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (atoken.removed) {
                    return WindowManagerGlobal.ADD_APP_EXITING;
                }
            } else if (rootType == TYPE_INPUT_METHOD) {
                if (token.windowType != TYPE_INPUT_METHOD) {
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } 
          
          ...	
            }

            /**
             * 10. 建立 WindowState,它存有視窗的所有的狀態資訊,在 WMS 中它代表一個視窗
             *     this 指 IWindow ,IWindow 會將 WMS 中視窗管理的操作回撥給 ViewRootImpl,token 指的是 WindowToken
             */
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
            /**
             * 11. 判斷請求新增視窗的客戶端是否已經死亡
             */
            if (win.mDeathRecipient == null) {
                return WindowManagerGlobal.ADD_APP_EXITING;
            }

            /**
             * 12. 判斷視窗的 DisplayContent 是否為 null 
             */
            if (win.getDisplayContent() == null) {
                Slog.w(TAG_WM, "Adding window to Display that has been removed.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            /**
             * 13.呼叫 WMP 的 adjustWindowParamsLw 方法,該方法在 PhoneWindowManager 中實現,此方法會根據視窗的 type 對視窗的 LayoutParams 的值進行修改
             */
            mPolicy.adjustWindowParamsLw(win.mAttrs);
            win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));

            /**
             * 14. 呼叫 WMP 的 prepareAddWindowLw 方法,用於準備將視窗新增到系統中
             */
            res = mPolicy.prepareAddWindowLw(win, attrs);
            if (res != WindowManagerGlobal.ADD_OKAY) {
                return res;
            }

           ...
             
            win.attach();
            /**
             * 15. 將 WindowState 新增到 mWindowMap 中
             */
            mWindowMap.put(client.asBinder(), win);
            if (win.mAppOp != AppOpsManager.OP_NONE) {
                int startOpResult = mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(),
                        win.getOwningPackage());
                if ((startOpResult != AppOpsManager.MODE_ALLOWED) &&
                        (startOpResult != AppOpsManager.MODE_DEFAULT)) {
                    win.setAppOpVisibilityLw(false);
                }
            }

            final AppWindowToken aToken = token.asAppWindowToken();
            if (type == TYPE_APPLICATION_STARTING && aToken != null) {
                aToken.startingWindow = win;
                if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + aToken
                        + " startingWindow=" + win);
            }

            boolean imMayMove = true;

            /**
             * 16. 將 WindowState 新增到該 WindowState 對應的 WindowToken 中(實際是儲存在 WindowToken的父類 WindowContainer 中),這樣 WindowToken 就包含了同一個元件的 WindowState
             */
            win.mToken.addWindow(win);
            if (type == TYPE_INPUT_METHOD) {
                win.mGivenInsetsPending = true;
                setInputMethodWindowLocked(win);
                imMayMove = false;
            } else if (type == TYPE_INPUT_METHOD_DIALOG) {
                displayContent.computeImeTarget(true /* updateImeTarget */);
                imMayMove = false;
            } else {
                if (type == TYPE_WALLPAPER) {
                    displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                }
            }
					...
        return res;
    }
複製程式碼

WMS 的 addWindow 方法中程式碼比較多而且比較複雜,大概分為 16 步,這裡因為把重要程式碼都註釋的比較詳細,所以不在過多講解,我這裡直接對 addWindow 方法做一個總結:

  • 對所有要新增的視窗進行檢查,如果視窗不滿足一些條件,就不會再執行下面的程式碼邏輯。
  • WindowToken 相關的處理,比如有的視窗型別需要提供 WindowToken , 沒有提供的話就不會執行下面的程式碼邏輯,有的視窗型別則需要有 WMS 隱式建立 WindowToken.
  • WindowState 的建立和相關處理,將 WindowTokenWindowState 相關聯。
  • 建立和配置 DisplayContent, 完成視窗新增到系統前的準備工作。

Window 刪除過程

在上一篇文章我們講解了 Window 的建立和更新過程一樣,要刪除 Window 就需要先呼叫 WindowManagerImplremoveView 方法,在 removeView 方法中又會呼叫 WindowManagerGlobalremoveView 方法,我們就從這裡開始分析。程式碼如下:

//WindowManagerGlobal.java

    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            /**
             * 1. 通過要刪除的 view 找到在 mViews 列表中的位置
             */
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            /**
             * 2. 呼叫內部方法將索引位置傳遞進去
             */
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            ...
        }
    }
    private int findViewLocked(View view, boolean required) {
        final int index = mViews.indexOf(view);
        if (required && index < 0) {
            ...
        }
        return index;
    }
複製程式碼

接下來我們看註釋 2 的實現,程式碼如下:

//WindowManagerGlobal.java
    private void removeViewLocked(int index, boolean immediate) {
        /**
         * 1.根據傳入的索引拿到需要刪除 Window  的 ViewRootImpl
         */
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            /**
             * 2. 得到 InputMethodManager 例項
             */
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                /**
                 * 3. 結束 IMM 例項在被刪除 window 中的輸入法相關的邏輯
                 */
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        /**
         * 4. 呼叫 ViewRootImpl 的 die 方法
         */
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }
複製程式碼

上面程式碼分為 4 步,做一些刪除的準備工作,然後我們來看註釋 4 的實現,程式碼如下:

//ViewRootImpl.java
    //die 方法需要立即執行並且此時 ViewRootImpl 不再執行 performTraversals 方法
    boolean die(boolean immediate) {

        /**
         * 1. immediate 為 true ,mIsInTraversal 為 false 就立刻執行
         */
        if (immediate && !mIsInTraversal) {
            /**
             * 2. mIsInTraversal 在執行 ViewRootImpl 的  performTraversals 方法的時候會被設定為 true ,在 performTraversals 方法執行完成的時候會被設定為 false
             */
            doDie();
            return false;
        }

       ...
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
複製程式碼

上面我們知道在 performTraversals 執行完成之後就將 mIsInTraversal 設定了 false ,那麼這裡就必須立刻執行 doDie 方法,程式碼如下:

//ViewRootImpl.java

    void doDie() {
        /**
         * 1. 檢查執行執行緒是否是在建立 Window 的執行緒中,如果不屬於就拋一個異常
         */
        checkThread();
        synchronized (this) {
            /**
             * 2. 如果在刪除了就支援 return
             */
            if (mRemoved) {
                return;
            }
            /**
             * 3. 將刪除動作設定為 true 避免重複刪除操作
             */
            mRemoved = true;
            /**
             * 4. 判斷被刪除的 Window 是否有子 View
             */
            if (mAdded) {
                /**
                 * 5. 如果有就會呼叫 dispatchDetachedFromWindow 方法來銷燬 View
                 */
                dispatchDetachedFromWindow();
            }

            /**
             * 6. 如果被刪除的 Window 有子 View 並且不是第一次被刪除,就會執行後面邏輯
             */
            if (mAdded && !mFirst) {
                destroyHardwareRenderer();

                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        
                        try {
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(mWindow);
                            }
                        } catch (RemoteException e) {
                        }
                    }

                    mSurface.release();
                }
            }

            mAdded = false;
        }
        /**
         * 7. 處理 WindowManagerGlobal 的 doRemoveView 方法
         */
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }
複製程式碼

這裡總結一點首先判斷執行刪除 Window 的執行緒是否屬於建立該 Window 的執行緒,如果不屬於將會拋一個異常,這說明只允許建立和刪除在同一個執行緒中處理,然後做了一些刪除動作的邏輯判斷,比如是否有重複刪除 Window 的動作,還有被刪除的 Window 是否有子 View 存在如果有就分發下去刪除 View 的動作,最後呼叫註釋 7 ,程式碼如下:

//WindowManagerGlobal.java
    void doRemoveView(ViewRootImpl root) {
        synchronized (mLock) {
            /**
             * 1. 找到被刪除 Window 對應的 ViewRootImpl 在 mRoots 列表中的位置,
             */
            final int index = mRoots.indexOf(root);
            if (index >= 0) {
                mRoots.remove(index);
                mParams.remove(index);
                final View view = mViews.remove(index);
                mDyingViews.remove(view);
            }
        }
        if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
            doTrimForeground();
        }
    }
複製程式碼

上面程式碼首先找到被刪除 Window 對應的 ViewRootImpl 在 mRoots 的列表中的位置,然後拿到索引位置執行對佈局引數列表和 View 列表中刪除掉將要被刪除 Window 對應的元素,接著我們回到 ViewRootImpl 的doDie 方法中註釋 5 的程式碼實現,如下:

//ViewRootImpl.java

    void dispatchDetachedFromWindow() {
        ...
        try {
          //1. 
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }
			...
    }
複製程式碼

dispatchDetachedFromWindow 方法中,核心程式碼就是註釋 1 處的程式碼,呼叫 IWindowSessionremove 方法,IWindowSession 的實現類在 Session , 在上一篇文章中已經講到了,不理解的先去看一下該系列對 WindowManager 原始碼分析,接下來我們看 Sessionremove 方法,程式碼如下:

//Session.java
    public void remove(IWindow window) {
        mService.removeWindow(this, window);
    }
複製程式碼

這裡又通知 WMS 來刪除 Window ,程式碼如下:

//WMS.java
    void removeWindow(Session session, IWindow client) {
        synchronized(mWindowMap) {
            /**
             * 1. 獲取 Window 對應的 WindowState,WindowState 用於儲存視窗的資訊,此處用於描述一個窗							*			口
             */
            WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
                return;
            }
            /**
             * 2. 呼叫 WindowState 的 removeIfPossible 方法
             */
            win.removeIfPossible();
        }
    }
複製程式碼

我們看註釋 2 實現,程式碼實現:

//WindowState.java
    @Override
    void removeIfPossible() {
        super.removeIfPossible();
        /**
         * 呼叫內部的 removeIfPossible 方法
         */
        removeIfPossible(false /*keepVisibleDeadWindow*/);
    }
複製程式碼
//WindowState.java
    private void removeIfPossible(boolean keepVisibleDeadWindow) {
       ...//判斷邏輯程式碼省略

        /**
         * 1. 
         */
        removeImmediately();
        ...
    }
複製程式碼

在註釋 1 上面會對被刪除 Window 的操作進行條件判斷過濾,只要有一個不符合就延遲在刪除,比如該 Window 正在執行一個動畫,那麼就需要等待動畫執行完成之後在刪除,最後在執行註釋 1 的程式碼,如下:

//WindowState.java

    @Override
    void removeImmediately() {
        super.removeImmediately();

        /**
         * 1. 以為著正在執行刪除的動作
         */
        if (mRemoved) {
            ..
            return;
        }

        /**
         * 2. 用於防止重複刪除的操作
         */
        mRemoved = true;

      	...
        /**
         * 3. 如果當前要刪除的 Window 是 StatusBar 或者 NavigationBar 就會將這個 Window 從對應的控制器中刪除
         */
        mPolicy.removeWindowLw(this);

				...
        /**
         * 4.將被刪除 Window 對應的 Session 從 WMS 中刪除
         */
        mSession.windowRemovedLocked();
				...

        /**
         * 5.呼叫 WMS 的 postWindowRemoveCleanupLocked 方法用於對被刪除 Window 進行一些集中的清理工作。
         */
        mService.postWindowRemoveCleanupLocked(this);
    }
複製程式碼

下面我們分別來對註釋 3 ,4 來看下它們具體操作實現,程式碼如下

//PhoneWindowManager.java    
@Override
    public void removeWindowLw(WindowState win) {
        if (mStatusBar == win) {
            mStatusBar = null;
            mStatusBarController.setWindow(null);
        } else if (mNavigationBar == win) {
            mNavigationBar = null;
            mNavigationBarController.setWindow(null);
        }
    }
複製程式碼

上面程式碼就是將 StatusBarNavigationBar 在對應的控制器中刪除,下面看註釋 4 ,程式碼如下:

//Session.java
    void windowRemovedLocked() {
        mNumWindow--;
        killSessionLocked();
    }
    private void killSessionLocked() {
        if (mNumWindow > 0 || !mClientDead) {
            return;
        }

        /**
         * 1. 將被刪除 Window 身上的 Session 在 WMS 中刪除
         */
        mService.mSessions.remove(this);
        if (mSurfaceSession == null) {
            return;
        }
				....
        try {
            /**
             * 2. 釋放被刪除 Window  身上的 SurfaceSession 資源
             */
            mSurfaceSession.kill();
        } catch (Exception e) {
           ...
        }
 ...
    }
複製程式碼

上面程式碼首先將被刪除 Window 身上的 Session 在 WMS 中刪除,然後釋放被刪除 Window 身上的 SurfaceSession 資源 ,SurfaceSession 資源是 SufaceFlinger 的一個連線,通過這個連線可以建立 1 ~ 多個 Surface 並渲染到螢幕上。

Window 的刪除過程就分析到這裡了,雖然刪除過程過於複雜繁瑣,但是我們可以對它刪除的過程做一個簡單的總結,如下:

  1. 檢查刪除執行緒是否符合要求,如果不符合就丟擲一個異常。
  2. 從 ViewRootImpl 、佈局引數 、View 列表中刪除與被刪除 Window 對應的元素。
  3. 判斷是否可以執行刪除操作,如果不能就推遲操作。
  4. 執行刪除操作,清理和釋放與被刪除 Window 的相關資源。

總結

在該篇文章中我們學習到了 WMS 的職責以及 Window 的新增和刪除操作,從 WMS 的職責可以看出 WMS 是相當的複雜,與它關聯的就有 4 大模組 “視窗管理”、“視窗動畫”、“輸入系統”、“Surface” ,它們每一個都是比較重要且複雜的系統,該篇就介紹了與應用開發關聯比較密切的視窗管理,剩下的就得靠自己去查閱相關係統原始碼書籍或一些相關文章了。

感謝大家閱讀,希望對你有幫助!

參考

  • 《Android 進階解密》

相關文章