Android8.0系統(tǒng)源碼WMS(一)解析之理解WindowManager

前言

AMS,PMS,WMS作為Android系統(tǒng)中最常見的三個Service,ActivityManagerService負責管理Activity,PackageManagerService負責管理Package,WindowManagerService負責管理Window。
本系列文章我們將會一起對WMS相關的系統(tǒng)源碼進行抽絲剝繭,以便我們更好的理解WMS。

一、PhoneWindow和WindowManager的關聯(lián)

1、WindowManager是一個接口類,繼承自接口ViewManager,ViewManager中定義了3個方法,分別用來添加、更新和刪除View,如下所示:

frameworks/base/core/java/android/view/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);
}

2、WindowManager也繼承了這些方法,這些方法傳入的參數(shù)都是View類型,說明Window也是以View的形勢存在的。WindowManager在繼承ViewManager的同時,還加入了很多功能,包括Window的類型和層級相關的常量、內部類以及一些方法。

frameworks/base/core/java/android/view/ViewManager.java

    public Display getDefaultDisplay();//獲得WindowManager所管理的屏幕
    public void removeViewImmediate(View view);//這個方法返回前會執(zhí)行View.onDetachedFromWindow()來完成View相關的銷毀工作

3、Window是一個抽象類,具體實現(xiàn)類為PhoneWindow,PhoneWindow是在Activity的attach方法中被創(chuàng)建的:

frameworks/base/core/java/android/app/Activity.java

  final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
        mWindow = new PhoneWindow(this, window, activityConfigCallback);//創(chuàng)建PhoneWindow
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);//設置回調

        ...代碼省略...
     mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//為PhoneWindow設置WindowManager實例。
        ...代碼省略...
    }

4、PhoneWindow的setWindowManager方法是在父類Window中實現(xiàn)的:

/frameworks/base/core/java/android/view/Window.java

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);//如果wm為null,那么就獲取已經存在的WindowManager對象實例。
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);//創(chuàng)建WindowManagerImpl的實例
    }

5、WindowManagerImpl的createLocalWindowManager方法如下所示:

frameworks/base/core/java/android/view/WindowManagerImpl.java

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();//獲取WindowManagerGlobal單例對象
    private final Context mContext;
   private final Window mParentWindow;//這個會指向PhoneWindow實例

    public WindowManagerImpl(Context context) {
        this(context, null);
    }

    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;//將PhoneWindow實例賦給mParentWindow
    }

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        //創(chuàng)建實例對象并傳入當前PhoneWindow的引用,使得WindowManagerImpl可以操作PhoneWindow
        return new WindowManagerImpl(mContext, parentWindow);
    }
  ...代碼省略...
}

6、WindowManager的關聯(lián)類圖如下所示:

WindowManager的關聯(lián)類圖

結合圖片和上面的代碼分析我們可以知道,PhoneWindow繼承自Window,Window通過setWindowManager方法與WindowManager發(fā)生關聯(lián)。WindowManager繼承自接口ViewManager,WindowManagerImpl是WindowManager接口的實現(xiàn)類,但是具體的功能最終都是委托給WindowManagerGlobal來實現(xiàn)的。

二、Window的屬性

了解Window的屬性能夠更好的理解WMS的內部原理,Window的屬性有很多種,與應用開發(fā)最密切的有3種,分別是Type(Window的類型),F(xiàn)lag(Window的標記)和SoftInputMode(軟鍵盤相關模式)。

1、Window的類型

Window的類型有很多種,比如應用程序窗口、系統(tǒng)錯誤窗口、輸入法窗口、PopupWindow、Toast、Dialog等。總的來說Window分為三大類型,分別是Application Window(應用程序窗口)、Sub Window(子窗口)、System Window(系統(tǒng)窗口),每個大類型中又分很多小類型,它們都定義在WindowManager的靜態(tài)內部類LayoutParams中,下面簡單介紹一下Window的三大類型。

1)應用程序窗口
Activity就是一個典型的應用程序窗口,應用程序窗口包含的類型如下所示:

frameworks/base/core/java/android/view/WindowManager.java

public interface WindowManager extends ViewManager {
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
        public static final int FIRST_APPLICATION_WINDOW = 1;
        public static final int TYPE_BASE_APPLICATION = 1;//應用程序窗口的基礎值,其他窗口的值都要大于這個值
        public static final int TYPE_APPLICATION = 2;//普通的應用程序窗口類型
        public static final int TYPE_APPLICATION_STARTING = 3;//應用程序啟動窗口類型,用戶系統(tǒng)在應用程序窗口啟動前顯示的窗口
        public static final int TYPE_DRAWN_APPLICATION = 4;
        public static final int LAST_APPLICATION_WINDOW = 99;//應用程序窗口的結束值
}
}

應用程序窗口的Type值的范圍為1~99。

2)子窗口
子窗口不能單獨存在,需要依附于其他窗口才行,PopupWindow就屬于子窗口。子窗口的類型定義如下所示:

frameworks/base/core/java/android/view/WindowManager.java

public interface WindowManager extends ViewManager {
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
       public static final int FIRST_SUB_WINDOW = 1000;//子窗口類型初始值
        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
        public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
        public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
        public static final int LAST_SUB_WINDOW = 1999;//子窗口類型結束值
}
}

子窗口的Type值的范圍為1000~1999。
3)系統(tǒng)窗口
Toast、輸入法窗口、系統(tǒng)音量條窗口、系統(tǒng)錯誤窗口都屬于系統(tǒng)窗口。系統(tǒng)窗口的類型定義如下所示:

public interface WindowManager extends ViewManager {
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
        public static final int FIRST_SYSTEM_WINDOW     = 2000; //系統(tǒng)窗口類型初始值
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW; //系統(tǒng)狀態(tài)欄窗口
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1; //搜索條窗口
        @Deprecated //use TYPE_APPLICATION_OVERLAY instead
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2; //通話窗口
        @Deprecated //use TYPE_APPLICATION_OVERLAY instead
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3; //系統(tǒng)Alert窗口
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4; //鎖屏窗口
        @Deprecated//use TYPE_APPLICATION_OVERLAY instead
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5; //Toast窗口
        @Deprecated //use TYPE_APPLICATION_OVERLAY instead
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
        @Deprecated //use TYPE_APPLICATION_OVERLAY instead
        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;//系統(tǒng)彈窗
        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;//鎖屏彈窗
        @Deprecated//use TYPE_APPLICATION_OVERLAY instead
        public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;
        public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;
        public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
        public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;
        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
        public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
        public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+16;
        public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
        public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
   public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;//系統(tǒng)導航欄
        public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;//音量
        public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
        public static final int TYPE_INPUT_CONSUMER = FIRST_SYSTEM_WINDOW+22;
        public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;
        public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
        public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
        public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
        public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
        public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
        public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
        public static final int TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33;
        public static final int TYPE_DOCK_DIVIDER = FIRST_SYSTEM_WINDOW+34;
        public static final int TYPE_QS_DIALOG = FIRST_SYSTEM_WINDOW+35;
        public static final int TYPE_PRESENTATION = FIRST_SYSTEM_WINDOW + 37;
        public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;//很多廢棄的系統(tǒng)彈窗類型都可以使用這個進行替換
        public static final int LAST_SYSTEM_WINDOW      = 2999; //系統(tǒng)窗口類型的結束值
}
}

系統(tǒng)窗口的類型值有接近40個,系統(tǒng)窗口的Type值范圍為2000~2999。

4)窗口的顯示次序
當一個進程向WMS申請一個窗口的時候,WMS會為窗口確定顯示次序。為了方便窗口顯示次序的管理,手機屏幕可以虛擬地用X、Y、Z軸來表示,其中Z軸垂直于屏幕,從屏幕內指向屏幕外,這樣窗口的顯示次序其實就是窗口在Z軸上的次序,這個次序稱為Z-Oder。Type值是Z-Order排序的依據(jù),我們知道應用程序窗口的Type值范圍為1-99,子窗口為1000-1999,系統(tǒng)窗口為2000-2999,在一般情況下,Type值越大則Z~Order排序越靠前,窗口越靠近用戶。
不過窗口顯示次序的邏輯并不僅僅依靠窗口的Type,情況是比較多的;最常見的情況,當多個窗口的Type都是Type_APPLICATION,這時WMS還需要結合各種情況來計算最終的Z-Oder。

2、Window的標記

Window的標記也就是Flag,用于控制Window的顯示,同樣被定義在WindowManager的靜態(tài)內部類LayoutParams 中,一共有20多個,這里介紹幾個比較常用的。

public interface WindowManager extends ViewManager {
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
        public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;//只要窗口可見,就允許在開啟狀態(tài)的屏幕上鎖屏
        public static final int FLAG_DIM_BEHIND        = 0x00000002;
        @Deprecated
        public static final int FLAG_BLUR_BEHIND        = 0x00000004;
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;//窗口不能獲得輸入焦點,設置該標記的同時,F(xiàn)LAG_NOT_TOUCH_MODAL也會被設置
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;//窗口不接受任何觸摸事件
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;//將該窗口區(qū)域外的觸摸事件傳遞給其他的Window,而自己只會處理窗口區(qū)域內的觸摸事件
        @Deprecated
        public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;//只要窗口可見,屏幕就會一直亮著
        public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
        public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;//允許窗口超過屏幕之外
        public static final int FLAG_FULLSCREEN      = 0x00000400;//隱藏所有的屏幕裝飾窗口,比如在游戲、播放器中的全屏顯示
        public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
        @Deprecated
        public static final int FLAG_DITHER             = 0x00001000;
        public static final int FLAG_SECURE             = 0x00002000;
        public static final int FLAG_SCALED             = 0x00004000;
        public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;//當用戶的臉貼近屏幕時(比如打電話),不會去響應此事件
        public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
        public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
        public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
        public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;//窗口可以在鎖屏的窗口之上顯示
        public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
        public static final int FLAG_TURN_SCREEN_ON = 0x00200000;//窗口顯示時將屏幕點亮
        @Deprecated
        public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
        public static final int FLAG_SPLIT_TOUCH = 0x00800000;
        public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
        public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
        public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
        public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
        public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000;
        public static final int FLAG_SLIPPERY = 0x20000000;
        public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000;
        public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
}
}

設置Window的Flag有3中方法。
1)通過Window的setFlags方法。

Window mWindow = getWindow();
mWindow.setFlag(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);

2)通過Window的addFlags方法。

Window mWindow = getWindow();
mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

其實addFlags方法內部也是調用setFlags方法來實現(xiàn)的,因此和第1種方法區(qū)別不大。
3)通過WindowManager的addView方法進行添加。

WindowManager.LayoutParams mWindowLayoutParams = new WindowManager.LayoutParams();
mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
WindowManager mWindowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
TextView mTextView = new TextView(this);
mWindowManager.addView(mTextView,mWindowLayoutParams);

3、軟鍵盤相關模式

窗口和窗口的疊加是十分常見的場景,但如果其中的窗口是軟鍵盤窗口,可能就會出現(xiàn)一些問題,比如典型的用戶登錄界面,默認情況彈出的軟鍵盤窗口可能會蓋住輸入框下方的按鈕,這樣用戶體驗會非常糟糕。為了使得軟鍵盤窗口能夠按照期望來顯示,WindowManager的靜態(tài)內部類LayoutParams種定義了軟鍵盤的相關模式。

        public static final int SOFT_INPUT_MASK_STATE = 0x0f;
        public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;//沒有指定狀態(tài),系統(tǒng)會選擇一個合適的狀態(tài)或依賴于主題的設置
        public static final int SOFT_INPUT_STATE_UNCHANGED = 1;//不會改變軟鍵盤的狀態(tài)
        public static final int SOFT_INPUT_STATE_HIDDEN = 2;//當用戶進入該窗口時,軟鍵盤默認隱藏
        public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;//當窗口獲取焦點時,軟鍵盤總是被隱藏
        public static final int SOFT_INPUT_STATE_VISIBLE = 4;
        public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
        public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
        public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
        public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;//當軟鍵盤彈出時,窗口會調整大小
        public static final int SOFT_INPUT_ADJUST_PAN = 0x20;//當軟鍵盤彈出時,窗口會調整大小
        public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
        public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;
  • SoftInputMode與AndroidManifest種Activity的屬性android:windowSoftInputMode是對應的。
  • 還可以在Java代碼中為Window設置SoftInputMode,如下所示:
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

三、Window的操作

1、窗口的添加過程

WindowManager對Window進行管理,說到管理那就離不開對Window的添加、更新和刪除操作,在這里我們把它們稱為Window的操作。對于Window的操作,最終都是交由WMS來進行處理的。窗口的操作分為兩大部分,一部分是WindowManager處理,另一部分是WMS處理。上面我們介紹過Window的三個大類,應用程序窗口、子窗口、系統(tǒng)窗口。對于不同類型的窗口WindowManager的添加過程會有所不同,但是對于WMS處理的部分,添加過程基本上是一樣的。

image.png

2、系統(tǒng)窗口StatusBar的添加過程

1)上面我們介紹過Window的三大類型,也說過不同類型的Window的添加過程也不盡相同,接下來我們以系統(tǒng)窗口StatusBar為例,講述一下系統(tǒng)窗口的添加過程。
StatusBar是SystemUI的重要組成部分,具體就是指系統(tǒng)狀態(tài)欄。
StatusBar的addStatusBarWindow方法源碼如下所示:

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java

    private void addStatusBarWindow() {
        makeStatusBarView();//構建視圖
        mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
        mRemoteInputController = new RemoteInputController(mHeadsUpManager);
        mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
    }

2)addStatusBarWindow方法首先調用makeStatusBarView構建狀態(tài)欄視圖,然后再調用mStatusBarWindowManager的add方法,將狀態(tài)欄視圖和狀態(tài)欄高度傳進去,StatusBarWindowManager的add方法如下所示:

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java

    public void add(View statusBarView, int barHeight) {
        mLp = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                barHeight,
                WindowManager.LayoutParams.TYPE_STATUS_BAR,//窗口類型
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                        | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                PixelFormat.TRANSLUCENT);
        mLp.token = new Binder();
        mLp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
        mLp.gravity = Gravity.TOP;
        mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
        mLp.setTitle("StatusBar");
        mLp.packageName = mContext.getPackageName();
        mStatusBarView = statusBarView;
        mBarHeight = barHeight;
        mWindowManager.addView(mStatusBarView, mLp);//將狀態(tài)欄視圖添加到WindowManager中
        mLpChanged = new WindowManager.LayoutParams();
        mLpChanged.copyFrom(mLp);
    }

3)StatusBarWindowManager的add方法首先創(chuàng)建LayoutParams,
并配置了包括Width、Height、Type、Flag、Gravity、SoftInputMode等StatusBar視圖的屬性。特別是設置了窗口類型為TYPE_STATUS_BAR,明確了StatusBar視圖的窗口類型是狀態(tài)欄。然后調用WindowManager的addView方法將狀態(tài)欄視圖添加到WindowManager中,關于mWindowManager的addView方法,具體是在WindowManagerImpl中實現(xiàn)的。

frameworks/base/core/java/android/view/WindowManagerImpl.java

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

4)WindowManagerImpl的addView又進一步調用WindowManagerGlobal的addView方法。

frameworks/base/core/java/android/view/WindowManagerGlobal.java

public final class WindowManagerGlobal {
    private final ArrayList<View> mViews = new ArrayList<View>();//當前存在的View列表
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();//當前存在的ViewRootImpl列表
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();//當前存在的布局參數(shù)列表
 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
      ...代碼省略...
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);//如果當前窗口要作為子窗口顯示,需要父窗口根據(jù)WindowManager.LayoutParams類型的wparams參數(shù)對子窗口進行相應調整
        } else {
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...代碼省略...
            root = new ViewRootImpl(view.getContext(), display);//創(chuàng)建ViewRootImpl實例對象
            view.setLayoutParams(wparams);
            mViews.add(view);//將要添加的view保存到View列表中
            mRoots.add(root);//將新創(chuàng)建的root保存到ViewRootImpl列表中
            mParams.add(wparams);//將窗口的參數(shù)保存到布局參數(shù)列表中
            try {
                root.setView(view, wparams, panelParentView);//將窗口和窗口參數(shù)設置到ViewRootImpl中
            } catch (RuntimeException e) {
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
}

5)WindowManagerGlobal中維護了和Window操作相關的3個關鍵列表,在窗口的添加、更新和刪除過程中都會涉及這3個列表,他們分別是View列表,ViewRootImpl列表,布局參數(shù)列表。addView方法首先會對傳入的參數(shù)view、params、display進行檢查,另外如果parentWindow不為空,還需要父窗口根據(jù)WindowManager.LayoutParams類型的wparams參數(shù)對view進行相應調整。
一切準備就緒會創(chuàng)建ViewRootImpl對象實例,在將view、root、mparams保存到對應的數(shù)據(jù)列表中后,便會調用ViewRootImpl的setView方法將窗口和窗口參數(shù)設置到ViewRootImpl中。ViewRootImpl肩負了很多職責,主要有以下幾點:

  • View樹的根并管理View樹
  • 觸發(fā)View的測量、布局和繪制
  • 輸入事件的中轉站
  • 管理Surface
  • 負責與WMS進行進程間通信
    了解了ViewRootImpl的職責以后,繼續(xù)來著看ViewRootImpl的setView的方法:

frameworks/base/core/java/android/view/ViewRootImpl.java

  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
              ...代碼省略...
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } 
              ...代碼省略...
    }

5)setView方法中有很多邏輯,這里只截取了最關鍵的一小部分,主要就是調用了mWindowSession的addToDisplay方法,mWindowSession是IWindowSession類型的,它是一個Binder對象,用于進行進程間通信,IWindowSession是Client端的代理,它的Server端的實現(xiàn)為Session,此前的代碼邏輯都是運行在本地進程的,而Session的addToDisplay方法則運行在WMS所在的進程(SystemServer)中。


ViewRootImpl與WMS通信

6)從上圖可以看出,本地進程的ViewRootImpl要想和WMS進行通信需要經過Session,那么Session為何包含在WMS中呢?繼續(xù)看Session的addToDisplay方法。

frameworks/base/services/core/java/com/android/server/wm/Session.java

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

7)addToDisplay方法會進一步調用WMS的addWindow方法,并將自身作為參數(shù)傳了進去,每個應用程序進程都會對應一個Session,WMS會用ArrayList來保存這些Session,這就是為什么WMS包含Session的原因。這樣剩下的工作就交給WMS來處理,在WMS中會為這個添加的窗口分配Surface,并確定窗口顯示次序,可見負責顯示界面的是畫布Surface,而不是窗口本身。WMS會將它所管理的Surface交由SurfaceFlinger處理,SurfaceFlinger會將這些Surface混合并繪制到屏幕上。

系統(tǒng)窗口StatusBar的添加過程的時序圖

3、Activity的添加過程

1)無論是StatusBar還是Activity,又或者其他類型從窗口,它的添加過程在WMS處理部分中基本是類似的,只不過會在權限和窗口顯示次序等方面會有些不同。但是在WindowManager處理部分會有所不同,這里以最典型的應用程序窗口Activity為例,Activity在啟動過程中,如果Activity所在的進程不存在則會創(chuàng)建新的進程,創(chuàng)建新的進程之后就會運行代表主線程的實例ActivityThread,ActivityThread管理著當前應用程序進程的線程,這在Activity的啟動過程中運用的很明顯,當界面要與用戶進行交互的時候,會調用ActivityThread的handleResumeActivity方法,如下所示:

frameworks/base/core/java/android/app/ActivityThread.java

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ...代碼省略...
        r = performResumeActivity(token, clearHide, reason);//方法內部最終會調用Activity的onResume方法
        ...代碼省略...
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();//獲取WindowManager實例
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);//調用WindowManager的addView,該方法會進一步調用WindowManagerImpl方法的addView方法
                    } else {
                        a.onWindowAttributesChanged(l);
                    }
                }
        ...代碼省略...
    }

ActivityThread的handleResumeActivity方法主要做了以下幾點:

  • 調用performResumeActivity方法,該方法最終會調用Activity的onResume方法
  • 獲取WindowManager的實例對象
  • 將Activity的視圖內容mDecor添加到WindowManager中
  • WindowManager的addView方法進一步調用WindowManagerImpl方法的addView方法,進一步調用WindowManagerGlobal的setView方法,進一步調用ViewRootImpl的setView方法,進一步調用Session的addToDisplay方法,進一步調用WMS的addWindow方法。Activity窗口的添加過程和StatusBar窗口的添加過程非常相似。

4、Window的更新過程

1)Window的更新過程和Window的添加過程是相似的,需要調用ViewManager的updateViewLayout方法,updateViewLayout方法在WindowManagerImpl中實現(xiàn)。

frameworks/base/core/java/android/view/WindowManagerImpl.java

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

2)WindowManagerImpl進一步調用WindowManagerGlobal的updateViewLayout方法。

frameworks/base/core/java/android/view/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.setLayoutParams(wparams);//將更新的參數(shù)設置到View中
        synchronized (mLock) {
            int index = findViewLocked(view, true);//從已經存在的View列表中獲取窗口的索引
            ViewRootImpl root = mRoots.get(index);//根據(jù)索引得到窗口的ViewRootImpl
            mParams.remove(index);
            mParams.add(index, wparams);//更新存在的窗口的布局參數(shù)
            root.setLayoutParams(wparams, false);//調用ViewRootImpl的setLayoutParams方法將更新的參數(shù)設置到ViewRootImpl中
        }
    }

frameworks/base/core/java/android/view/ViewRootImpl.java

void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
        synchronized (this) {
            ...代碼省略...
            scheduleTraversals();
        }
    }

3)ViewRootImpl的setLayoutParams的最后會調用scheduleTraversals方法。

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//編舞者
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

scheduleTraversals方法中最關鍵的就是名為“編舞者”的Choreographer,該對象用戶接受顯示系統(tǒng)的VSync信號(垂直同步信號),在下一幀渲染的時候控制執(zhí)行一些操作。Choreographer的postCallback方法用戶發(fā)起添加回調,這個添加的回調mTraversalRunnable將在下一幀被渲染的時候執(zhí)行。

4)繼續(xù)來看下ViewRootImpl類中和mTraversalRunnable相關的代碼。

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

5)TraversalRunnable的run方法進一步調用 ViewRootImpl的doTraversal方法。

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            performTraversals();
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

6)doTraversal方法中又調用了performTraversals方法,performTraversals方法使得ViewTree開始View的工作流程,如下所示。

private void performTraversals() {

    ...代碼省略...

    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

    ...代碼省略...

    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    ...代碼省略...


    performLayout(lp, mWidth, mHeight);

    ...代碼省略...

    if (!cancelDraw && !newSurface) {
        if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).startChangingAnimations();
            }
            mPendingTransitions.clear();
        }

        performDraw();
    } else {
        if (isViewVisible) {
            // Try again
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).endChangingAnimations();
            }
            mPendingTransitions.clear();
        }
    }

    mIsInTraversal = false;
}

以上代碼主要做了以下幾個關鍵點:

  • relayoutWindow方法內部調用IWindowSession的relayout方法來更新Window視圖,和上面StatusBar窗口、Activity窗口原理一樣,最終也是調用WMS的relayoutWindow方法。
  • performMeasure方法調用View的messure方法進行View的測量;performLayout方法調用View的layout方法進行View的布局;performDraw方法調用View的draw方法進行View的繪制;這樣就完成了View的工作流程。

performTraversals方法既更新了Window的視圖,又執(zhí)行了Window中View的工作流程,這樣就完成了Window的更新。

四、小結

本篇文章我們學習了WindowManager的關聯(lián)類、Window的屬性和Window的操作,了解了Window、WindowManager和WMS之間的關系,也知道了Window的操作分為兩大部分并且最終都是由WMS處理的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373