1. 程式人生 > >Android視窗系統第三篇---WindowManagerService中視窗的組織方式

Android視窗系統第三篇---WindowManagerService中視窗的組織方式

上面文章梳理了一個視窗的新增過程,系統中有很多應用,每個應用有多個Activity,一個Activity上有一個Window,WindowManagerService是怎麼管理的?先adb shell dumpsys activity檢視一下Activity.
image.png

Display #0 (activities from top to bottom):
Display對應視窗系統中的DisplayContent類,可以理解成一個螢幕,手機上一般就是一塊螢幕,當然也可以有虛擬螢幕,最近中興推出了一款雙螢幕的手機,那麼這款手機上DisplayContent的size就為2,#0代表當前螢幕的裝置ID。

Stack #0:

Stack對應WindwoManagerService中的TaskStack類,如果是在ActivityManagerService就對應ActivityStack類,為什麼要引入TaskStack和ActivityStack類呢?因為他們的作用是管理TASK,一個Stack中包含了多個Task。應用程式也可以在AndroidManifest.xml檔案中通過android:launchMode
指定當前Activity執行在哪一個Task中。#0代表Stack的id,android 中規定了id為0的Stack是存放home桌面的視窗的。在Android N以前,是不支援多視窗的,所以只有兩個Stack,一個是HOME Stack,也就是存在桌面的Stack,另外一個就是存在其他應用程式的Stack。在android N 以後,總共有5個Stack。這些Stack都是在ActivityManager.java中定義的。


/** First static stack ID. */
public static final int FIRST_STATIC_STACK_ID = 0;

/** Home activity stack ID. */
public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;

/** ID of stack where fullscreen activities are normally launched into. */
public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1
; /** ID of stack where freeform/resized activities are normally launched into. */ public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1; /** ID of stack that occupies a dedicated region of the screen. */ public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1; /** ID of stack that always on top (always visible) when it exist. */ public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;

Id等於0:Home Stack,就是Launcher所在的Stack。但是還有一些系統介面也執行在這個Stack上,比如近期任務的介面。
Id等於1:FullScren Stack,全屏的Activity所在的Stack。 但其實在分屏模式下,Id為1的Stack只佔了半個螢幕。
Id等於2:Freeform模式的Activity所在Stack
Id等於3:Docked Stack 在分屏模式下,螢幕有一半運行了一個固定的應用,這就是Docked Stack
Id等於4:Pinned Stack 這是畫中畫Activity所在的Stack

  Stack #0:
    Task id #142
      TaskRecord{a11ed77 #142 A=com.miui.home U=0 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10800000 cmp=com.miui.home/.launcher.Launcher }
        Hist #0: ActivityRecord{2661ad9 u0 com.miui.home/.launcher.Launcher t142}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10800000 cmp=com.miui.home/.launcher.Launcher }
          ProcessRecord{7da5d1c 2606:com.miui.home/u0a16}

所以上面的dum就可以這樣解釋,在id等於0這個stack管理一個id等於142的TASK,TASK中存放一個ActivityRecord,這個ActivityRecord就是HOME。瞭解了上面,就可以看下面這張圖。
image.png

WindwoManagerService中需要管理多個顯示螢幕,這些顯示屏物件被儲存在mDisplayContents列表裡面,每一個顯示屏物件中有一個mStackBoxs列表,這個列表包含多個StackBox,StackBox被組織成二叉樹的形式,mFirst指向第一個節點的TaskStack物件,mSecond指向第二個節點的TaskStack物件,mStack指向當前TaskStack物件,一個TaskStack物件內部有mTask列表,這個列表中儲存的物件是TASK,一個TASK中有很多Activity,一個Activity對應一個AppWindowToken。一個AppWindowToken中有一個windows列表,所以一個AppWindowToken對應多個WindowState。

DisPlayContent、TaskStack都解釋過,現在看看什麼是AppWindowToken?

說AppWindowToken需要先說WindowToken,WindowToken的子類AppWindowToken,你可以把WindowToken理解成是一個顯示令牌,無論是系統視窗還是應用視窗,新增新的視窗的時候必須使用這個令牌向WMS表明自己的身份,新增視窗的時候會建立WindowToken,銷燬視窗的時候移除WindowToken(removeWindowToken方法)。

WMS使用WindowToken將同一個應用元件(Activity,InputMethod,Wallpaper,Dream)的視窗組織在一起,換句話說,每一個視窗都會對應一個WindowToken,並且這個視窗中的所有子視窗將會對應同一個WindowToken,就如下面這個圖的關係,假設視窗A是播放器中的一個視窗,除了主視窗外,還有三個子視窗,這些視窗的WindowToken都是一樣的。

WindowToken和WindowState是1對多的關係.png

所以說WindowToken就像一個“戶口本”,表示一家人都要在一個本本上。

AppWindowToken:每個App的Activity對應一個AppWindowToken。其中的appToken為IApplicationToken型別,連線著對應的AMS中的ActivityRecord::Token物件,有了它就可以順著AppWindowToken找到AMS中相應的ActivityRecord。

這裡為什麼要針對Activity弄出一下AppWindowToken呢?我覺得有以下幾個原因:
1、AppWindowToken是AMS中ActivityRecord::Token的產物,也就是ActivityRecord::Token可以看作ActivityRecord的一個遠端控制代碼,在WMS中以AppWindowToken形式存在,ActivityRecord::Token實現了IApplicationToken。當WMS要通知AMS視窗變化時,就是用的這個介面。

2、一次連續性的操作(Task)可以開啟多個Activity,每個Activity可以包含多個視窗(對應WindowState),WMS中TaskStack維護了Task的列表mTask,Task維護了AppWindowToken列表mAppTokens,AppWindowToken維護了相關WindowState的列表windows。即AppWindowToken被TaskStack管理,TaskStack中的mTasks是按歷史順序存放的,最老的Task在最底下,這個也是與AMS中ActivityStack的mTaskHistory順序保持一致。如果沒有AppWindowToken,直接用WindowToken,那麼AMS的負擔被增加,因為這些非Activity視窗,AMS是不care的。

AppWindowToken是什麼時候被建立的呢?

在Activity視窗新增之前,AMS會呼叫addConfigOverride向WMS登記。

    void addConfigOverride(ActivityRecord r, TaskRecord task) {
        final Rect bounds = task.updateOverrideConfigurationFromLaunchBounds();
        // TODO: VI deal with activity
        mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
                r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
                (r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges,
                task.voiceSession != null, r.mLaunchTaskBehind, bounds, task.mOverrideConfig,
                task.mResizeMode, r.isAlwaysFocusable(), task.isHomeTask(),
                r.appInfo.targetSdkVersion, r.mRotationAnimationHint);
        r.taskConfigOverride = task.mOverrideConfig;
    }

引數r.appToken,是ActivityRecord:Token物件。r.task.taskId是這個Activity執行的Task的ID, mStackId是這個Actiivty的Task所在的Stack的ID。

    @Override
    public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
            int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int userId,
            int configChanges, boolean voiceInteraction, boolean launchTaskBehind,
            Rect taskBounds, Configuration config, int taskResizeMode, boolean alwaysFocusable,
            boolean homeTask, int targetSdkVersion, int rotationAnimationHint) {
  
        ....
        synchronized(mWindowMap) {
            AppWindowToken atoken = findAppWindowToken(token.asBinder());
            if (atoken != null) {
                Slog.w(TAG_WM, "Attempted to add existing app token: " + token);
                return;
            }
           //建立AppWindowToken
            atoken = new AppWindowToken(this, token, voiceInteraction);
            atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
            atoken.appFullscreen = fullscreen;
            atoken.showForAllUsers = showForAllUsers;
            atoken.targetSdk = targetSdkVersion;
            atoken.requestedOrientation = requestedOrientation;
            atoken.layoutConfigChanges = (configChanges &
                    (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
            atoken.mLaunchTaskBehind = launchTaskBehind;
            atoken.mAlwaysFocusable = alwaysFocusable;
            if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
                    + " to stack=" + stackId + " task=" + taskId + " at " + addPos);
            atoken.mRotationAnimationHint = rotationAnimationHint;
            //mTaskIdToTask是一個SparseArray物件,先嚐試取出Task,沒有就建立一個
            Task task = mTaskIdToTask.get(taskId);
            if (task == null) {
                task = createTaskLocked(taskId, stackId, userId, atoken, taskBounds, config);
            }
            //將atoken放到task中的addPos位置
            task.addAppToken(addPos, atoken, taskResizeMode, homeTask);
            //atoken放到mTokenMap中
            mTokenMap.put(token.asBinder(), atoken);

            // Application tokens start out hidden.
            atoken.hidden = true;
            atoken.hiddenRequested = true;
        }
    }
    private Task createTaskLocked(int taskId, int stackId, int userId, AppWindowToken atoken,
            Rect bounds, Configuration config) {
        if (DEBUG_STACK) Slog.i(TAG_WM, "createTaskLocked: taskId=" + taskId + " stackId=" + stackId
                + " atoken=" + atoken + " bounds=" + bounds);
        //這裡是能夠取到的,因為AMS在呼叫addAppWindowToken之前,會呼叫WMS的attachStack方法,提前將TaskStack創建出來。
        final TaskStack stack = mStackIdToStack.get(stackId);
        if (stack == null) {
            throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId);
        }
        EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId);
        Task task = new Task(taskId, stack, userId, this, bounds, config);
        //把上面建立的Task放到mTaskIdToTask中
        mTaskIdToTask.put(taskId, task);
        //把task放到stack中,返回task
        stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */, atoken.showForAllUsers);
        return task;
    }

現在從Activity的角度來看Activiy視窗的組織方式應該如下圖。
image.png

TaskRecord和ActivityRecord:Token是AMS包下面的類,Task和DisplayContent是WMS包下面的類,其中TaskRecord和Task對應,TaskRecord中有mActivities列表,這個列表中放了若干ActivityRecord,每一個ActivityRecord都有一個Token,這個Token要通過WMS的addAppToken給加入到WMS中的Task中去,AMS和WMS都是執行在system_server程序下面,所有addAppToken並不是一個IPC過程。每一個AppWindwToken可以對應一個WindowState或者對應多個WindowState,圖中的WindowState-Z是主視窗,WindowState-Z-1是子視窗,每一個WindowState是屬於一個螢幕(DisplayContent)中。另外不僅App的視窗新增的時候需要呼叫addAppToken註冊Token,其他型別的視窗新增也需要向WMS註冊token,比如牆紙型別的視窗在新增的時候,也需要提前呼叫addWindowToken向WMS中註冊token,這樣後面執行addWindow的過程才不會失敗。