WMS(二)窗口属性

80 阅读8分钟

uTools_1694562714993.png

1. 窗口类型和层级

  1. application Window (应用程序窗口)
  2. System Window (系统窗口)
  3. Sub Window (子窗口)

每个大类型中又分很多小的类型,它们都定义在 WindowManager 的静态内部类 LayoutParams 中。

1.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;
        //这是TYPE_APPLICATION的变体,确保WindowManager在APP展示之前绘制完成此窗口
        public static final int TYPE_DRAWN_APPLICATION = 4;
        ///应用程序窗口的开始值
        public static final int LAST_APPLICATION_WINDOW = 99;
}
}

应用程序窗口的 Type 值的范围是 1-99

1.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

1.3 系统窗口

Toast、输入法窗口、系统音量条窗口、系统错误窗口都属于系统窗口。系统窗口的类型定义如下所示:

public interface WindowManager extends ViewManager {
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
        //系统窗口类型初始值
        public static final int FIRST_SYSTEM_WINDOW     = 2000;
        //系统状态栏,会显示在所有用户窗口中
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW; 
        //搜索条,会显示在所有用户窗口中
        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; 
        //系统Alert,只会显示在拥有者的窗口中
        @Deprecated //use TYPE_APPLICATION_OVERLAY instead
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3; 
       //锁屏界面,会显示在所有用户窗口中
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
        //Toast窗口,只会显示在拥有者的窗口中
        @Deprecated//use TYPE_APPLICATION_OVERLAY instead
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5; 
        //系统重载窗口,只会显示在拥有者的窗口中
        @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;
        //锁屏弹窗,会显示在所有用户窗口中
        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;
        //音量,会显示在所有用户窗口中
        public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;
        //Boot进度条,会显示在所有用户窗口中
        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_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
        //显示屏重载窗口,会显示在所有用户窗口中
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        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_SCREENSHOT = FIRST_SYSTEM_WINDOW + 36;

        public static final int TYPE_PRESENTATION = FIRST_SYSTEM_WINDOW + 37;
        //很多废弃的系统弹窗类型都可以使用这个进行替换,只会显示在拥有者的窗口中
        public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
        //会显示在所有用户窗口中
        public static final int TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 39;
        //会显示在所有用户窗口中
        public static final int TYPE_NOTIFICATION_SHADE = FIRST_SYSTEM_WINDOW + 40;
        //会显示在所有用户窗口中
        public static final int TYPE_STATUS_BAR_ADDITIONAL = FIRST_SYSTEM_WINDOW + 41;
        //系统窗口类型的结束值
        public static final int LAST_SYSTEM_WINDOW      = 2999;
}
}

系统窗口的类型值接近 40个,系统窗口的 Type 值范围为 2000-2999

1.4 窗口的显示次序

当进程向系统申请一个窗口时,系统会为窗口确定显示次序。 为了方便窗口显示次序的管理,手机屏幕可以虚拟地用X、Y、Z轴来表示,其中Z轴垂直于屏幕,从屏幕内指向屏幕外,这样窗口的显示次序其实就是窗口在Z轴上的次序,这个次序称为Z-Oder。

Type值是Z-Order排序的依据,我们知道应用程序窗口的Type值范围为1-99,子窗口为1000-1999,系统窗口为2000-2999,在一般情况下,Type值越大则Z~Order排序越靠前,窗口越靠近用户。

不过窗口显示次序的逻辑并不仅仅依靠窗口的Type,情况是比较多的;最常见的情况,当多个窗口的Type都是Type_APPLICATION,这时系统还需要结合具体情况来计算最终的Z-Oder。

二、WindowManager 的 addView 方法

  1. 关于 WindowManager 的 addView 方法,具体是在 WindowManagerImpl 中实现的。
// frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {

    @UnsupportedAppUsage
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyTokens(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
    
}
  1. WindowManagerImpl#addView 又进一步调用了 WindowManagerGlobal#addView
// frameworks/base/core/java/android/view/WindowManagerGlobal.java
public final class WindowManagerGlobal {

    @UnsupportedAppUsage
    private final ArrayList<View> mViews = new ArrayList<View>();//当前存在的View列表
    @UnsupportedAppUsage
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();//当前存在的ViewRootImpl列表
    @UnsupportedAppUsage
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();//当前存在的布局参数列表
            
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            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) {
            // 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);
                    }
                }
            }

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

}

这里的操作非常重要;

  1. WindowManagerGlobal 中维护了和 Window 操作相关的 3 个关键列表,在窗口的添加、更新和删除过程中都有涉及,分别是 View 列表、ViewRootImpl 列表、布局参数列表。

  2. addView 方法首先会对传入的参数 view, params, display 进行检查,另外如果parentWindow不为空,还需要父窗口根据WindowManager.LayoutParams类型的wparams参数对view进行相应调整。

  3. 一切准备就绪会创建ViewRootImpl对象实例,在将view、root、mparams保存到对应的数据列表中后,便会调用ViewRootImpl的setView方法将窗口和窗口参数设置到ViewRootImpl中。

  4. ViewRootImpl肩负了很多职责,主要有以下几点:

    • View树的根并管理View树
    • 触发View的测量、布局和绘制
    • 输入事件的中转站
    • 管理Surface
    • 负责与WindowManagerService进行进程间通信
  5. 继续看看 ViewRootImpl 中的 setView 方法

// frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
              ...代码省略...
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    adjustLayoutParamsForCompatibility(mWindowAttributes);
                    controlInsetsForCompatibility(mWindowAttributes);
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
                            mTempControls);
                    if (mTranslator != null) {
                        mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
                        mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
                    }
                } 
              ...代码省略...
    }
}

setView方法中有很多逻辑代码,这里只截取了最关键的一部分,调用了mWindowSession的addToDisplay方法,mWindowSession是IWindowSession类型的,它是一个Binder对象,用于进行进程间通信,IWindowSession是Client端的代理,它的Server端的实现为Session,此前的代码逻辑都是运行在本地进程的,而Session的addToDisplay方法则运行在WindowManagerService所在的进程(SystemServer)中。

image.png

从上图可以看出,本地进程的ViewRootImpl要想和WindowManagerService进行通信需要经过Session,那么Session为何包含在WindowManagerService中呢?

  1. 继续看 Session 的 addToDisplay 方法
//frameworks/base/services/core/java/com/android/server/wm/Session.java
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {

    final WindowManagerService mService;

    public Session(WindowManagerService service, IWindowSessionCallback callback) {
        mService = service;
        ...代码省略...
 	}
 	
    @Override
    public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
                requestedVisibilities, outInputChannel, outInsetsState, outActiveControls);
    }
}    

1)addToDisplay方法会进一步调用WindowManagerService的addWindow方法,并将自身作为参数传了进去,每个应用程序进程都会对应一个Session,WindowManagerService会用ArrayList来保存这些Session,这就是为什么WindowManagerService包含Session的原因。

2)之后的工作就全都交给了WindowManagerService处理,WindowManagerService会为这个添加的窗口分配Surface,并确定窗口显示次序,可见负责显示界面的是画布Surface,而不是窗口本身。WindowManagerService会将它所管理的Surface交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上。

image.png

参考连接

blog.csdn.net/abc6368765/…