view系列--Window,PhoneWindow,DecorView相關類

從這節(jié)起開始介紹view系列的文章,先從介紹Window,PhoneWindow相關類開始。

關于Window,PhoneWindow,DecorView,WindowManager這幾個類相關的文章確實很多,但是我感覺對于它們的講解都存在一些偏差。這幾個類本身也是具有“迷惑性”的,因此我希望通過這篇文章能把它們及它們之間的關系介紹清楚。

通過本節(jié)您可以了解PhoneWindow,Window,DecorView,WindowManager,WindowManagerImpl,WindowManagerGlobal這些類及它們之間的關系

1.Window

Window類不是真正的窗口

Window這個類是一個讓人很迷惑的類,在我剛接觸這個類的時候,看到它的名字我非常確信它就是一個窗口一個“名副其實的窗口”。還有與它有關系的WindowManager,WindowManagerImpl這兩個類,當看到這兩個類的時候立馬讓我想到了WindowManagerService這個類,WindowManagerService作為一個服務運行在系統(tǒng)進程中管理所有的窗口,我天真的認為WindowManager和WindowManagerImpl就是與WindowManagerService相對應的類是提供與WindowManagerService進行binder通信的類。

當看了源碼以后,我才發(fā)現(xiàn)上面的觀點完全有問題,首先Window類它是”名不符實“,為什么這樣說呢?Window類中沒有measure(測量)相關方法,也沒有l(wèi)ayout(布局)相關的方法,甚至沒有draw(繪制)相關的方法,甚至也沒有最基本的width和height屬性。我的理解是這樣的既然作為一個窗口,那它就應該有上面提到的這些方法和屬性。

別看WindowManagerService是管理所有的窗口,或者別看它的名字里面包含了Window這個詞,但是Window類和WindowManagerService沒有任何的關系(它倆誰也不認識誰),WindowManagerService中添加一個窗口的方法叫addWindow,別看方法名有Window這個詞,這個方法添加的不是Window這個類,它的簽名如下:

public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsState requestedVisibility,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls)

在調(diào)用WindowManager的addView方法的時候,也是根本和Window類沒關系的,addView方法只是需要View和LayoutParams這兩個參數(shù),addView方法的簽名如下:

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    }

Window類只是一個封裝類

為了能幫助大家更深入的理解,現(xiàn)有如下假設:

  • 沒有Window類
  • Activity中沒有setContentView方法
  • 框架層在第一次調(diào)用Activity的onResume方法之前把調(diào)用WindowManager的addView方法去掉(這樣就需要開發(fā)者自己調(diào)用WindowManager的addView方法)

在上面假設的情況下,那我們看下在Activity是如何創(chuàng)建和顯示View的,偽代碼如下:

public class MainActivity extends Activity{

    private View mRootView;
    private WindowManager.LayoutParams mLayoutParams;
    private boolean mIsFirstResume = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //把activity_main.xml解析出來
        View rootView = getLayoutInflater().inflate(R.layout.activity_main);
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();

        //設置flags
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        //設置窗口類型
        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;

        //設置layoutParams的各種屬性
        layoutParams.format = PixelFormat.TRANSPARENT;
        layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        layoutParams.x = 0;
        layoutParams.y = 0;
        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
        layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;

        mRootView = rootView;
        mLayoutParams = layoutParams;
    }

    @Override
    protected void onResume() {
        super.onResume();
        
        //只有在第一次進入onResume的時候才去執(zhí)行下面邏輯
        if(mIsFirstResume){
            mIsFirstResume = false;
            //調(diào)用WIndowManager的addView方法,開始mRootView的顯示
            getWindowManager().addView(mRootView, mLayoutParams);
        }
    }
}
    

在Activity的onCreate方法中做以下事情:

  • 調(diào)用LayoutInflater的inflate方法把activity_main.xml中的各種信息解析出來,獲取到rootView
  • 初始化WindowManager.LayoutParams的各種屬性

在Activity的onResume方法中做以下事情:

  • 在第一次進入onResume方法的時候,通過getWindowManager().addView(mRootView, mLayoutParams)這段代碼 mRootView最終會被顯示在屏幕上

其實在Android中View顯示出來最根本的做法就是:調(diào)用WindowManager的addView方法,在Activity調(diào)用setContentView方法就可以設置View,其實是框架層把這些邏輯都封裝了起來,從而感覺不到WindowManager的addView方法的存在。

上面的偽代碼中的activity_main.xml中分為“標題欄”和“內(nèi)容區(qū)域”,說實話上面的偽代碼為MainActivity設置View還可以接受。

那現(xiàn)在問題來了,假如這時候需要為n個Activity設置View,那如果每個Activity都重復MainActivity里面的操作肯定不可取,作為一個合格的程序員這時候肯定想到了封裝,把inflate rootView的操作和為WindowManager.LayoutParams設置各種屬性的操作統(tǒng)統(tǒng)都封裝到一個類中,這個類就叫做TempWindow吧,Activity直接使用這個封裝類即可,這樣使用起來方便多了。

TempWindow偽代碼如下

    class TempWindow{

        private View mRootView;
        private WindowManager.LayoutParams mLayoutParams;

        TempWindow(Context context, int layoutResId){
            View rootView = getLayoutInflater().inflate(layoutResId);
            WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();

            //設置flags
            layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            //設置窗口類型
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;

            //設置layoutParams的各種屬性
            layoutParams.format = PixelFormat.TRANSPARENT;
            layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
            layoutParams.x = 0;
            layoutParams.y = 0;
            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;

            mRootView = rootView;
            mLayoutParams = layoutParams;
        }
    }

TempWindow解決了重復代碼的問題,但是還有不方便的地方,每個Activity的布局文件都是分為:標題欄和內(nèi)容區(qū)域兩部分,Activity之間標題欄的差別其實并不大,但是每個Activity的布局文件都需要去寫標題欄和內(nèi)容區(qū)域,這確實太不方便了。

可以把標題欄拆到一個單獨的layout文件中,那每個Activity的布局文件中都去include標題欄布局文件,這種辦法確實可以解決問題,但是還是需要每個布局文件去include標題欄布局,這還不是最優(yōu)解。那有沒有更方便的方法呢?當然有了:可以寫一個布局文件,暫且稱它為root布局,它包含了標題欄和內(nèi)容區(qū)域的父View,root.xml偽代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/root"
    android:gravity="center">

    標題欄區(qū)域

    <LinearLayout
        android:id="@+id/content_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

結(jié)合上面的root.xml,再把TempWindow改造下:

    public class TempWindow{

        public View mRootView;
        public WindowManager.LayoutParams mLayoutParams;
        private View mContentView;

        TempWindow(Context context){
            //從root.xml中解析出rootView
            View rootView = getLayoutInflater().inflate(R.layout.root);

            //根據(jù)R.id.content_view從rootView中找出View并賦值給mContentView,
            mContentView = rootView.findViewById(R.id.content_view);
            WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();

            //設置flags
            layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            //設置窗口類型
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;

            //設置layoutParams的各種屬性
            layoutParams.format = PixelFormat.TRANSPARENT;
            layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
            layoutParams.x = 0;
            layoutParams.y = 0;
            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;

            mRootView = rootView;
            mLayoutParams = layoutParams;
        }

        //Activity調(diào)用這個方法把 自己的布局文件加入mContentView中
        public void setContentView(int layoutResId){
            View contentView = getLayoutInflater().inflate(layoutResId);
            //把解析出來的contentView作為mContentView的子View加入mContentView中
            mContentView.addView(contentView);
        }
    }

在Activity中使用新的TempWindow

    public class MainActivity extends Activity{

    private TempWindow mTempWindow;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mTempWindow = new TempWindow(this);
        mTempWindow.setContentView(R.layout.activity_main);
    }

    @Override
    protected void onResume() {
        super.onResume();
        
        //只有在第一次進入onResume的時候才去執(zhí)行下面邏輯
        if(mIsFirstResume){
            mIsFirstResume = false;
            //調(diào)用WIndowManager的addView方法,開始mRootView的顯示
            getWindowManager().addView(mTempWindow.mRootView, mTempWindow.mLayoutParams);
        }
    }
}

使用改造后的TempWindow是不是很方便,Activity中直接初始化一個TempWindow并且在onCreate方法里面直接調(diào)用TempWindow的setContentView方法就完事了。

TempWindow把公共的不易變化的內(nèi)容都封裝起來,比如初始化WindowManager.LayoutParams(它在一個app內(nèi)的Activity應該都是一樣的),標題欄(在Activity中也是一致的),界面的背景等。Activity只需要關注變化的部分也就是它們自己的內(nèi)容View即可,TempWindow的setContentView方法叫作ContentView而不是RootView可以看出該 方法就是添加內(nèi)容View的。Activity中的setContentView方法與TempWindow的setContentView方法意思是一樣的。

其實就想通過介紹TempWindow的封裝改造過程來間接的介紹Window類,當然Window類的功能肯定要比TempWindow更多,比如還包含了UI的background,點擊事件的分發(fā)等。

Window類重要屬性和方法介紹

FEATURE_開頭的屬性
凡是以”FEATURE_“開頭的靜態(tài)常量都是定義當前窗口具有什么特性(比如界面是帶有標題欄呢,還是節(jié)目帶有action bar)

  • FEATURE_OPTIONS_PANEL:代表創(chuàng)建action bar的menu
  • FEATURE_NO_TITLE:代表不顯示標題欄
  • FEATURE_PROGRESS:如果顯示標題欄,則標題欄中顯示進度條
  • FEATURE_LEFT_ICON:如果顯示標題欄,則標題欄顯示左icon
  • FEATURE_CUSTOM_TITLE:代表自定義標題欄
  • FEATURE_ACTION_BAR:代表顯示action bar
  • FEATURE_CONTENT_TRANSITIONS:代表內(nèi)容變化的時候的動畫

上面只是列了一些常用的feature,其他的一些大家可以自行去看代碼,一切都在代碼里面

setFlags方法
它的方法定義如下:

    public void setFlags(int flags, int mask) {
        final WindowManager.LayoutParams attrs = getAttributes();
        attrs.flags = (attrs.flags&~mask) | (flags&mask);
        mForcedWindowFlags |= mask;
        dispatchWindowAttributesChanged(attrs);
    }

該方法的參數(shù)主要來自于WindowManager.LayoutParams.FLAG_開頭的常量,比如設置窗口為全屏,可以使用下面代碼設置:

 setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);

requestFeature方法
方法定義如下:

    public boolean requestFeature(int featureId) {
        final int flag = 1<<featureId;
        mFeatures |= flag;
        mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
        return (mFeatures&flag) != 0;
    }

該方法設置當前窗口的特性,比如設置窗口沒有標題欄可以這樣用:requestFeature(FEATURE_NO_TITLE)。它的參數(shù)就是上面的以FEATURE_開頭的屬性,該方法必須在setContentView方法調(diào)用之前才有效

findViewById方法
方法定義如下:

    public <T extends View> T findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }

該方法作用就是根據(jù)view的id來查找到對應的View,咱們在Activity經(jīng)常使用findViewById這個方法,其實最終會調(diào)用到Window的findViewById方法,而Window中又會調(diào)用到DecorView的findViewById方法。

getDecorView方法
方法定義如下:

    public abstract @NonNull View getDecorView();

該方法是一個抽象方法,獲取DecorView,子類去實現(xiàn)

小結(jié)

Window類它不是真正的窗口,和WindowManagerService沒有任何關系,它只是一個封裝類把公共的不易變化的內(nèi)容都封裝起來(比如把標題欄,UI background等封裝起來),讓Activity只關注content View即可,添加顯示content View更簡單(只需要調(diào)用Activity的setContentView方法即可,不需要關心關心WindowManager.LayoutParams這些屬性的設置)

Window類的使用者可不是單單只有Activity,還有Dialog等

2.DecorView

先從下面的關于DecorView兩幅圖開始


decorview.jpg
decorview-h.png

上圖展示的是一個Activity的界面繪制在屏幕上的效果圖和界面的每個部分所對應的View;下圖展示的上圖界面的View層級關系。

下面就結(jié)合上面的兩幅圖來詳細的介紹DecorView。

DecorView的子View

從上面兩幅圖可以看出DecorView是整個View層級最頂層View,它包含的直接子View有navigationBarBackground,statusBarBackground,LinearLayout

navigationBarBackground
看它的名字就知道,它與navigationBar的background有關系,它就是一個View,主要的作用就是用來設置navigationBar的背景色。它的高度是固定不變的(如上面DecorView的兩幅圖中的上圖),DecorView中會設置它的高度,它位于整個屏幕的底部,但是它的顏色是可以自定義的,比如您可以根據(jù)自己app的需求把它設置為黑色或者別的顏色。并且是可以隱藏navigation bar的,這樣就間接的隱藏了navigationBarBackground(navigation bar都已經(jīng)隱藏了,navigationBarBackground當然沒有存在的必要了)。

statusBarBackground
它的作用是設置status bar的背景色,和navigationBarBackground類似,它的高度在DecorView中會被設置,它位于整個屏幕的最頂部,它的顏色值是可以自定義的(如上面的上圖它的顏色設置為藍色),自定義它的顏色值主要是在themes.xml樣式文件中設置,如下代碼:

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.LifecycleDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <item name="colorPrimaryVariant">@color/purple_700</item>
    </style>
</resources>

status bar也是可以隱藏的,比如在全屏觀看視頻的時候,status bar會被隱藏,隨之statusBarBackground也會隱藏。

navigationBarBackground和statusBarBackground它們被設計出來的主要目的是:為了能讓開發(fā)者控制status bar和navigation bar的背景色,這樣就可以讓整個界面更加的協(xié)調(diào)一致了。

navigationBarBackground和statusBarBackground能改變status bar和navigation bar背景色的主要原理是:Activiity中DecorView它的大小與屏幕大小一致。status bar和navigation bar的層級想對于Activity中DecorView要高很多(在z軸上的值要大很多),并且它們的背景都是透明的,因此status bar和navigation bar是“漂浮于“DecorView上的。這樣在DecorView中修改navigationBarBackground和statusBarBackground它們的色值,從而以障眼法的方式看上去status bar和navigation bar的背景色被修改了,其實并沒有。

contentRoot
如上面DecorView的兩幅圖中的上圖,contentRoot它的高度是從DecorView的頂部到navigationBarBackground這段距離,如上面DecorView的兩幅圖中的下圖,contentRoot它對應的是LinearLayout。contentRoot對應的就是上面提到的Window類中的不同主題類型的布局文件中的root view。不同的主題類型:比如有帶標題欄的主題,帶action bar的主題,或者只有內(nèi)容的主題。

它包含View主要分為兩部分:標題欄/action bar和contentParent。

標題欄/action bar開發(fā)者基本上不用,開發(fā)者一般都是自己來封裝自己的標題欄。contentParent:在調(diào)用Activity的setContentView方法的時候設置的content view最終會被添加到contentParent中,并且它的id是:com.android.internal.R.id.content。

DecorView會根據(jù)各種情況比如窗口設置了全屏,隱藏了navigation bar或者隱藏了status bar等 來設置contentRoot的margin top/left/bottom/right的值,相關設置代碼如下:

    WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
        WindowManager.LayoutParams attrs = mWindow.getAttributes();
        int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();


        省略代碼......

        final WindowInsetsController controller = getWindowInsetsController();

        // IME is an exceptional floating window that requires color view.
        final boolean isImeWindow =
                mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD;
        if (!mWindow.mIsFloating || isImeWindow) {
            
        //consumingNavBar代表是否占據(jù)navigation bar的位置
        boolean consumingNavBar =
                ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
                        && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
                        && decorFitsSystemWindows
                        && !hideNavigation)
                || forceConsumingNavBar;

        // If we didn't request fullscreen layout, but we still got it because of the
        // mForceWindowDrawsBarBackgrounds flag, also consume top inset.
        // If we should always consume system bars, only consume that if the app wanted to go to
        // fullscreen, as othrewise we can expect the app to handle it.
        
        //是否是全屏顯示
        boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
                || (attrs.flags & FLAG_FULLSCREEN) != 0
                || !(controller == null || controller.isRequestedVisible(ITYPE_STATUS_BAR));

        //consumingStatusBar代表是否占據(jù)status bar的位置
        boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
                && decorFitsSystemWindows
                && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
                && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
                && mForceWindowDrawsBarBackgrounds
                && mLastTopInset != 0
                || (mLastShouldAlwaysConsumeSystemBars && fullscreen);

        //下面計算 consumed top/right/bottom/left 值

        //如果占據(jù)status bar的位置則consumedTop的值為mLastTopInset,mLastTopInset的值為status bar的高度
        int consumedTop = consumingStatusBar ? mLastTopInset : 0;
        int consumedRight = consumingNavBar ? mLastRightInset : 0;
        //如果占據(jù)navigation bar位置,則consumedBottom設置為mLastBottomInset,mLastBottomInset代表navigation bar的高度值
        int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
        int consumedLeft = consumingNavBar ? mLastLeftInset : 0;

        //開始對mContentRoot的layoutparmas設置
        if (mContentRoot != null
                && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
            MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
            if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight
                    || lp.bottomMargin != consumedBottom || lp.leftMargin != consumedLeft) {

                //對mContentRoot的top/right/bottom/left的margin分別設置
                lp.topMargin = consumedTop;
                lp.rightMargin = consumedRight;
                lp.bottomMargin = consumedBottom;
                lp.leftMargin = consumedLeft;
                mContentRoot.setLayoutParams(lp);

                if (insets == null) {
                    // The insets have changed, but we're not currently in the process
                    // of dispatching them.
                    requestApplyInsets();
                }
            }
            if (insets != null) {
                insets = insets.inset(consumedLeft, consumedTop, consumedRight, consumedBottom);
            }
        }

        省略代碼......

        return insets;
    }

DecorView是啥?

DecorView繼承了FrameLayout類,DecorView是一個窗口中整個View層級最頂層View。正如其名”裝飾View“它也確實有如此的功能,DecorView通過navigationBarBackgroundstatusBarBackground這兩個View搭建了”裝飾“了navigation bar和status bar的功能。使用者就可以根據(jù)自己的需求來”裝飾“navigation bar和status bar的樣子了。如果想隱藏它們也是可以的。DecorView同樣有”裝飾“contentRoot的功能,可以對contentRoot的上下左右margin值進行設置,來修改contentRoot的顯示邊界。

DecorView既然是一個窗口中整個View層級最頂層View,那很多非常重要的事情肯定是先通知到它,它作為頂級View然后在把相應的通知分發(fā)給它的子View,孫子View等等。ViewRootImpl類作為一個具有非常豐富功能的超級頂級類,它的其中最重要的mView屬性它的值指向的就是DecorView。

ViewRootImpl中比如有點擊事件發(fā)生的時候,就會調(diào)用DecorView的dispatchPointerEvent方法,DecorView最終會把點擊事件分發(fā)到它的對應子View,看誰能處理這個事件。比如繪制事件發(fā)生的時候,會調(diào)用到DecorView的updateDisplayListIfDirty方法,DecorView開始遞歸調(diào)用它的子View,孫子View去進行繪制。

DecorView就介紹到此,下面介紹PhoneWindow。

3.PhoneWindow

PhoneWindow類是Window類的唯一子類,它實現(xiàn)了Window類中的一些方法比如實現(xiàn)了getDecorView()方法,會返回一個DecorView。很多的功能已經(jīng)在Window類介紹過了,在此介紹下PhoneWindow是如何實現(xiàn)不同的特性窗口的。

不同特性的窗口

PhoneWindow類中實現(xiàn)不同特性的窗口的代碼主要在generateLayout方法中,那就來看下這方法

    protected ViewGroup generateLayout(DecorView decor) {
        //獲取各種window相關的屬性,放入TypedArray中
        TypedArray a = getWindowStyle();

        省略代碼......

        //如果設置了Window_windowIsFloating,則代表是一個float類型的窗口,Dialog就是這種類型的窗口
        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        //mIsFloating為true(Dialog的時候該值為true),則調(diào)用setLayout方法設置WindowManager.LayoutParams attrs的width和height屬性為WRAP_CONTENT
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
            getAttributes().setFitInsetsSides(0);
            getAttributes().setFitInsetsTypes(0);
        }

        //如果R.styleable.Window_windowNoTitle為true,則代表是不需要標題欄的窗口,則調(diào)用requestFeature(FEATURE_NO_TITLE)
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            //不需要action bar的窗口
            requestFeature(FEATURE_ACTION_BAR);
        }

        省略其他的feature設置代碼......

        //下面代碼設置各種flag值,比如全屏了,status bar/navigation bar為透明了等
        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }

        if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
                false)) {
            setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                    & (~getForcedWindowFlags()));
        }

        if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
                false)) {
            setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
                    & (~getForcedWindowFlags()));
        }


        省略代碼.....

        //設置Dialog的窗口的dim屬性
        if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
                mIsFloating)) {
            /* All dialogs should have the window dimmed */
            if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            }
            if (!haveDimAmount()) {
                params.dimAmount = a.getFloat(
                        android.R.styleable.Window_backgroundDimAmount, 0.5f);
            }
        }

        省略代碼......


        //下面的代碼就很有意思了,根據(jù)features去獲取布局文件(layoutResource)
         int layoutResource;
        int features = getLocalFeatures();

        //如果當前的窗口特性的標題欄是包含left icon或者right icon的,則進入下面的邏輯
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            //懸浮類型的窗口,則進入下面邏輯(Dialog會走這)
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.

            //如果是FEATURE_PROGRESS或者FEATURE_INDETERMINATE_PROGRESS特性的并且沒有FEATURE_ACTION_BAR的窗口,則使用R.layout.screen_progress的布局文件
            layoutResource = R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {

            //如果是自定義的特性的窗口,則進入這
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                //使用R.layout.screen_custom_title布局文件
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {

            //如果是沒有標題欄的特性的窗口,則進入這的邏輯

            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                //如果是action bar特性的窗口,則使用R.layout.screen_action_bar布局文件
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            //默認使用R.layout.screen_simple布局文件
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
        }


        //mDecor它的類型是DecorView,調(diào)用onResourcesLoaded方法會把layoutResource解析出來,并且add view到mDecor中,這樣DecorView中就有子View了
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        //contentParent View它的id值是ID_ANDROID_CONTENT,因為上面已經(jīng)把layoutResource對應的View加入到了DecorView中,因此調(diào)用findViewById可以找到contentParent View
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

        省略代碼......
    }

PhoneWindow主要根據(jù)下面的步驟來生成不同特性的窗口:
1.獲取樣式信息并設置feature
會從themes.xml樣式文件中獲取到對應的信息,比如根據(jù)R.styleable.Window_windowIsFloating獲取的值為true,則代表當前的窗口是float類型的(Dialog就是這種類型的),比如根據(jù)R.styleable.Window_windowNoTitle獲取的值為true,則代表當前的窗口是不需要標題欄的。獲取到對應的樣式信息后,調(diào)用requestFeature方法來設置窗口特性。
因此這也是在themes.xml樣式文件中能控制生成什么樣特性窗口的原因

2.獲取樣式信息并設置flag

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.LifecycleDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <item name="android:windowFullscreen">true</item>
    </style>
</resources>

比如上面的樣式文件中,設置了”android:windowFullscreen“的值為true,則PhoneWindow類中會解析到R.styleable.Window_windowFullscreen的值,并且調(diào)用 setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags())) 方法設置窗口為全屏顯示。

其他的樣式文件中的設置也如此類似,因此這也是在themes.xml樣式文件中能控制窗口的flag值的原因。

3.獲取布局文件
上面的各種信息都設置完畢后,就可以根據(jù)最后的features信息來斷定是使用哪個布局文件了。

比如如果是FEATURE_PROGRESS或者FEATURE_INDETERMINATE_PROGRESS特性的并且沒有FEATURE_ACTION_BAR的窗口,則使用R.layout.screen_progress布局文件
screen_progress.xml的代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:fitsSystemWindows="true"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
>
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />

    //帶有progess功能的標題欄
    <RelativeLayout android:id="@android:id/title_container" 
        style="?android:attr/windowTitleBackgroundStyle"
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
    >
        //圓形progress
        <ProgressBar android:id="@+android:id/progress_circular"
            style="?android:attr/progressBarStyleSmallTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="5dip"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:visibility="gone"
            android:max="10000"
        />

        //橫向的progess
        <ProgressBar android:id="@+android:id/progress_horizontal"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="2dip"
            android:layout_alignParentStart="true"
            android:layout_toStartOf="@android:id/progress_circular"
            android:layout_centerVertical="true"
            android:visibility="gone"
            android:max="10000" 
        />

        //title
        <TextView android:id="@android:id/title"
            style="?android:attr/windowTitleStyle"
            android:layout_width="match_parent" 
            android:layout_height="match_parent"
            android:layout_alignParentStart="true"
            android:layout_toStartOf="@android:id/progress_circular"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:scrollHorizontally="true" 
        />
    </RelativeLayout>

    //內(nèi)容View
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay"
    />
</LinearLayout>

在比如默認特性的窗口,它的布局文件是R.layout.screen_simple,它的內(nèi)容是特別簡單的,它的代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />

    //只有content view部分
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

PhoneWindow根據(jù)不同的features去加載使用不同的布局文件。

4.contentRoot添加到DecorView
這里的contentRoot對應的就是上一步 根據(jù)不同的features去加載使用不同的布局文件 的根View(調(diào)用LayoutInflater的inflate方法是可以把布局文件的所有View都解析出來的),調(diào)用DecorView的addView方法就可以把contentRoot添加到DecorView中。

到此關于PhoneWindow生成不同特性的窗口就介紹完了。

4.WindowManager

WindowManager它是一個接口,從它的名字來看以為它和WindowManagerService(簡稱WMS)能有那么一點點的關系,但是你錯了,它和WMS沒有辦毛錢關系。它做了一件”偷梁換柱”的事情,那這個事情是啥事情呢,下面就來聊下。

WindowManager和WindowManagerService沒有任何關系

大家都知道我們是可以通過Context的getSystemServiceName方法獲取到各種服務的,比如下面代碼

    //獲取ActivityManager
    ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

如上代碼,通過調(diào)用getSystemService(Context.ACTIVITY_SERVICE)方法是可以獲取到ActivityManager的實例,這個ActivityManager就是ActivityManagerService的binder代理類,調(diào)用ActivityManager的方法最后通過binder調(diào)用是可以到達ActivityManagerService的對應方法的。

同理我們也可以使用如下代碼獲取到WindowManager

    WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

但是獲取的這個WindowManager卻和WindowManagerService沒有任何關系,更談不上與WMS通信了。

那我們就來看下為啥它們之間沒有關系,從Context的getSystemService方法開始說起,Context是一個抽象類,ContextImpl類繼承了Context,它也實現(xiàn)了getSystemService方法,如下代碼

    @Override
    public Object getSystemService(String name) {
        if (vmIncorrectContextUseEnabled()) {
            // Check incorrect Context usage.
            if (WINDOW_SERVICE.equals(name) && !isUiContext()) {
                final String errorMessage = "Tried to access visual service "
                        + SystemServiceRegistry.getSystemServiceClassName(name)
                        + " from a non-visual Context:" + getOuterContext();
                final String message = "WindowManager should be accessed from Activity or other "
                        + "visual Context. Use an Activity or a Context created with "
                        + "Context#createWindowContext(int, Bundle), which are adjusted to "
                        + "the configuration and visual bounds of an area on screen.";
                final Exception exception = new IllegalAccessException(errorMessage);
                StrictMode.onIncorrectContextUsed(message, exception);
                Log.e(TAG, errorMessage + " " + message, exception);
            }
        }
        //從SystemServiceRegistry獲取服務
        return SystemServiceRegistry.getSystemService(this, name);
    }

上面方法最終調(diào)用了SystemServiceRegistry的getSystemService方法

public static Object getSystemService(ContextImpl ctx, String name) {
        if (name == null) {
            return null;
        }
        //從SYSTEM_SERVICE_FETCHERS中獲取
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        
        省略代碼......

        return ret;
    }

SYSTEM_SERVICE_FETCHERS是一個Map類型,在當前方法中是直接從SYSTEM_SERVICE_FETCHERS中獲取,那肯定就有往SYSTEM_SERVICE_FETCHERS中注冊的地方

    static{
        
        省略其他的注冊代碼......

        //看到?jīng)]有,這個地方注冊了Context.WINDOW_SERVICE,它的最終會返回一個WindowManagerImpl實例
        registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx);
            }});

         registerService(Context.USER_SERVICE, UserManager.class,
                new CachedServiceFetcher<UserManager>() {
            @Override
            public UserManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                IBinder b = ServiceManager.getServiceOrThrow(Context.USER_SERVICE);
                IUserManager service = IUserManager.Stub.asInterface(b);
                return new UserManager(ctx, service);
            }});

        省略其他的注冊代碼......
    }

各種服務的注冊是在SystemServiceRegistry類的靜態(tài)塊中進行的,在注冊window服務的時候Context.WINDOW_SERVICE,最終會返回一個WindowManagerImpl實例。看下別的服務的注冊比如Context.USER_SERVICE,它最終會返回UserManagerService的binder代理類IUserManager,并且封裝到Usermanager中。

從上面分析可知道,最終通過調(diào)用getSystemService(Context.WINDOW_SERVICE)獲取的WindowManager,它是WindowManagerImpl類型的實例,WindowManagerImpl實現(xiàn)了WindowManager接口,因此WindowManagerImpl和WindowManagerService沒有任何關系,也更不會與WMS進行binder通信了。

LayoutParams

LayoutParams類定義了很多與布局相關的很多屬性比如width/height等,還定義了很多的常量,現(xiàn)在就介紹幾個關鍵的屬性。

窗口類型
為了對窗口有一個統(tǒng)一的管理,每種窗口都有它自己的type值,可以根據(jù)type值的大小來決定窗口的顯示層級(在Z軸上的值)type值越大窗口顯示的層級越大(離用戶更近),這樣如Toast就會顯示在Activity的界面之上。
同時也可以根據(jù)type值的范圍對窗口進行分類,大致可以劃分為三類:應用程序級別的窗口,子窗口級別的窗口,系統(tǒng)級別的窗口。它們的type值是越來越大的。

  • 應用程序級別的窗口:它的type值的范圍是從1--99
    TYPE_BASE_APPLICATION:1
    TYPE_APPLICATION :2 Activity對應的窗口
    TYPE_APPLICATION_STARTING:3 應用程序第一次啟動的時候,白色背景的窗口
    TYPE_DRAWN_APPLICATION:4
    LAST_APPLICATION_WINDOW:99

  • 子窗口級別的窗口:它的type值的范圍是從1000--1999
    TYPE_APPLICATION_PANEL:1000
    TYPE_APPLICATION_MEDIA:1001
    TYPE_APPLICATION_SUB_PANEL:1002
    LAST_SUB_WINDOW:1999

  • 系統(tǒng)級別的窗口:它的type值范圍是從2000--2999
    TYPE_STATUS_BAR:2000 status bar
    TYPE_SEARCH_BAR:2001 search bar
    TYPE_SYSTEM_ALERT:2003 比如電量不足界面
    TYPE_TOAST:2005 toast
    LAST_SYSTEM_WINDOW:2999

上面的這些type值都定義在LayoutParams類中,只是選取了其中一部分。LayoutParams有一個type屬性,它的值就取自上面的這些值,type屬性的值并不是想用那個就用哪個是有要求的,比如當前窗口是應用程序級別,則它的type值就不能是2000以上的,因為2000以上是系統(tǒng)就級別的窗口,并且使用2000以上的窗口是需要權(quán)限的。

各種FLAG
LayoutParams中定義了各種類型的FLAG常量,它們用來控制窗口的一些特性

  • FLAG_KEEP_SCREEN_ON:設置了這個flag,則不會鎖屏
  • FLAG_FULLSCREEN:代表窗口是全屏

上面列舉了常用的兩個,當然還有很多,LayoutParams的flags屬性會持有這些FLAG值

關于LayoutParams類的介紹到此為止。

WindowManager的關鍵方法

下面三個關鍵方法都是從ViewManager繼承來的
addView
它的聲明如下:

    public void addView(View view, ViewGroup.LayoutParams params);

這是個抽象方法,WindowManagerImpl實現(xiàn)了這個方法,它的主要作用是添加一個View和它的LayoutParams

updateViewLayout
它的聲明如下:

    public void updateViewLayout(View view, ViewGroup.LayoutParams params);

同樣也是抽象方法,WindowManagerImpl實現(xiàn)了這個方法,它的作用是更新某個View和它的LayoutParams

removeView
它的聲明如下:

    public void removeView(View view);

同樣也是抽象方法,WindowManagerImpl實現(xiàn)了這個方法,它的作用是移除View

上面的三個方法都很View有關系,比如addView方法主要是把一個View(它肯定是根View)和它對應的LayoutParams(View的layout相關的屬性都在這個類中,比如width和height,還比如上面介紹的type和flags)進行添加,到底add到什么地方,后面會詳細介紹到。

小結(jié)

WindowManager從名字上來看是不是管理Window的,應該和Window類有關系,最起碼應該有addWindow,removeWindow這類的方法,但是其實不然,WindowManager和Window類沒有關系,上面提到的三個方法也都和Window類沒有關系,甚至它也沒有addWindow,removeWindow這些方法。哈哈是不是世界觀被侮辱了。還是上面提到的Window類的存在就是為了給開發(fā)者帶來方便,android中添加和顯示View的最終方法是WindowManager的addView方法,它的參數(shù)是View和LayoutParams,和Window類無關。

5.WindowManagerGlobal

WindowManagerGlobal這個類從它的名字中的Global可以看出在一個進程中肯定只存在一個實例。下面是它的生成單例的方法

    public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }

還記得上面提到的WindowManagerImpl類嗎?通過調(diào)用getSystemService(Context.WINDOW_SERVICE)返回的就是WindowManagerImpl實例,這時候調(diào)用它的addView方法最終會調(diào)用到WindowManagerGlobal類的addView方法,addView方法的代碼如下:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        
        省略代碼......

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            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
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            //把view,root,params存儲到各自的列表中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                //調(diào)用ViewRootImpl的setView方法,設置View,params等參數(shù),這個方法被調(diào)用后開始了View的繪制等流程
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

addView方法主要做了以下幾件事情:

  • 各種異常判斷
  • 生成ViewRootImpl的實例
  • 把root,view(這個view通常是DecorView類型),params等存儲到各自的列表中
  • 調(diào)用ViewRootImpl的setView方法,這個方法被調(diào)用后就開啟了View的繪制流程,ViewRootImpl會和WMS建立交互,ViewRootImpl依據(jù)vsync機制進行View的繪制工作,并且把繪制的buffer通過binder調(diào)用傳遞給SurfaceFlinger進程,進行渲染等操作

小結(jié)
WindowManagerGlobal在一個進程中存在一個實例,它的addView方法中會把的View(View一般是DecorView類型),LayoutParams,ViewRootImpl這些值保存起來,并且最終會調(diào)用ViewRootImpl的setView方法開啟View的繪制等流程。它的removeView方法中會把保持的View,LayoutParams,ViewRootImpl這些值移除。

因為通過調(diào)用getSystemService(Context.WINDOW_SERVICE)返回的就是WindowManagerImpl實例,那如果想與WMS(WindowManagerService)交互的話該怎么進行呢?
WindowManagerGlobal類中給出了結(jié)果,如果需要與WMS通信,需要從WMS獲取一個Session的binder代理實例,這個代理實例在一個進程中只存在一個,如下代碼:

    @UnsupportedAppUsage
    private static IWindowManager sWindowManagerService;
    @UnsupportedAppUsage
    private static IWindowSession sWindowSession;

    @UnsupportedAppUsage
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            //sWindowSession為null,則去獲取
            if (sWindowSession == null) {
                try {
                    // Emulate the legacy behavior.  The global instance of InputMethodManager
                    // was instantiated here.
                    // TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    
                    //先獲取WindowManagerService的binder代理對象,這個對象是可以通過binder與WMS進行通信的
                    IWindowManager windowManager = getWindowManagerService();

                    //調(diào)用WMS的openSession方法,獲取Session的binder代理對象
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

    @UnsupportedAppUsage
    public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                //獲取WMS的binder代理對象
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                try {
                    if (sWindowManagerService != null) {
                        ValueAnimator.setDurationScale(
                                sWindowManagerService.getCurrentAnimatorScale());
                        sUseBLASTAdapter = sWindowManagerService.useBLAST();
                    }
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }

如上代碼,WindowManagerGlobal的getWindowSession方法返回Session的binder代理對象,這樣就可以調(diào)用對應的方法與Session進行通信,最終與WMS進行通信。

6.總結(jié)

DecorView類:繼承了FrameLayout類,DecorView是一個窗口中整個View層級最頂層View。正如其名”裝飾View“它也確實有如此的功能,DecorView通過navigationBarBackground和statusBarBackground這兩個View搭建了”裝飾“了navigation bar和status bar的功能。使用者就可以根據(jù)自己的需求來”裝飾“navigation bar和status bar的樣子了。如果想隱藏它們也是可以的。DecorView同樣有”裝飾“contentRoot的功能,可以對contentRoot的上下左右margin值進行設置,來修改contentRoot的顯示邊界。DecorView也是為Activity服務的,正是因為有了Window和PhoneWindow類,DecorView才有意義。

Window類,PhoneWindow類:它們就是封裝類,主要作用是幫助開發(fā)者在Activity/Dialog中更方便快捷的添加顯示View,并且提供了不同特性的窗口(比如有帶標題欄的窗口,有帶action bar的窗口,有帶progress和title功能的窗口),Window類和WindowManagerService類沒有任何的關系,雖然WindowManagerService中有addWindow的方法,但是這個方法也和Window類沒有關系。DecorView的顯示最終要調(diào)用WindowManager的addView方法,把DecorView和PhoneWindow中獲取的LayoutParams作為addView的參數(shù)。

WindowManager接口,WindowManagerImpl類,WindowManagerGlobal類:它們是與addView,updateView,removeView功能有關系的類。調(diào)用getSystemService(Context.WINDOW_SERVICE)方法獲取的是WindowManagerImpl實例,WindowManagerImpl實現(xiàn)了WindowManager接口,但是真正干活的不是WindowManagerImpl,而是WindowManagerGlobal,WindowManagerGlobal在一個進程中只存在一個實例,它會把View(View一般是DecorView類型),LayoutParams,ViewRootImpl這些值保存起來,它會與ViewRootImpl進行交互最終開啟View的顯示繪制流程。

這三個類與Window類沒有關系,它們的名字中都有管理Window的功能,按道理來說它們應該有類似于addWindow/removeWindow/updateWindow相關的方法來進行與Window相關的操作,但是它們中卻沒有與Window類相關的方法,添加/更新/移除相關的方式是addView,updateView,removeView。

WindowManger接口和WindowManagerImpl類和WindowManagerService服務也沒有任何的關系。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容