美文网首页
Android关于Window相关的一些总结

Android关于Window相关的一些总结

作者: 星晴_371a | 来源:发表于2021-04-22 17:12 被阅读0次
关于Window你的了解?

说到window很多同学会说so easy,window是一个抽象类,PhoneWindow是他的实现类,PhoneWindow中又包含decorView,这个decorVIew实际上是一个FrameLayout,decorView中又包含两个部分,TitleBar和Content
如下图所示:


image.png

Window实际上是不存在的,他是一个抽象的概念,用于View进行依赖的。
我们setContentView来对一个activity设置xml布局文件时:
1.在setContentView里面,通过getWindow获得了当前activity的phoneWindow窗口;
2.调用的PhoneWindow中的setContentView,并在里面调用了mLayoutInflater.inflate(layoutResID, mContentParent)方法
3.将xml的布局填充到DecorView上面
我们来分析一下哈,

设置xml布局文件的目的
View view = getLayoutInflater().inflate(R.layout.layout_activity, null);
得到这样的一个View
然后通过getWindow,获取的Window
然后通过WindowManger 来对这个Window管理,比如这个Window上面依赖的View,显示位置,透明度,层级

从上面的分析看的出咱们这个Window既然会被管理,那么应该会有一些属性进行设置吧,是的没错
咱们这个Window其实啊还是分层级的,用一个字段Z-Order表示:

1.系统Window

常见的系统Window有哪些呢?比如在手机电量低的时候,会有一个提示电量低的Window,我们输入文字的时候,会弹出输入法Window,还有搜索条Window,来电显示Window,Toast对应的Window,可以总结出来,系统Window是独立与我们的应用程序的,对于应用程序而言,我们理论上是无法创建系统Window,因为没有权限,这个权限只有系统进程有。Z-Order在2000-2999

2.应用程序Window

所谓应用窗口指的就是该窗口对应一个Activity,因此,要创建应用窗口就必须在Activity中完成了。本节后面会分析Activity对应的Window的创建过程。Z-Order在1-99

3.子Window

所谓的子Window,是说这个Window必须要有一个父窗体,比如PopWindow,Dialog因为有自己的WindowToken,属于应用程序Window,这个比较特殊。Z-Order在1000-1999
用一张图来显示就很明显:


image.png

这里要说明一下,如果我们要把当前这个window设置成系统window 那么我们就需要设置对应的权限
要在manifest文件设置,Android6.0以上还要动态获取能在其他应用 层上面显示的权限

以下是常见的一些window层级属性:

应用级

// 应用程序 Window 的开始值
public static final int FIRST_APPLICATION_WINDOW = 1;

// 应用程序 Window 的基础值
public static final int TYPE_BASE_APPLICATION = 1;

// 普通的应用程序
public static final int TYPE_APPLICATION = 2;

// 特殊的应用程序窗口,当程序可以显示 Window 之前使用这个 Window 来显示一些东西
public static final int TYPE_APPLICATION_STARTING = 3;

// TYPE_APPLICATION 的变体,在应用程序显示之前,WindowManager 会等待这个 Window 绘制完毕
public static final int TYPE_DRAWN_APPLICATION = 4;

// 应用程序 Window 的结束值
public static final int LAST_APPLICATION_WINDOW = 99;

子window级

// 子 Window 类型的开始值
public static final int FIRST_SUB_WINDOW = 1000;

// 应用程序 Window 顶部的面板。这些 Window 出现在其附加 Window 的顶部。
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;

// 用于显示媒体(如视频)的 Window。这些 Window 出现在其附加 Window 的后面。
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;

// 应用程序 Window 顶部的子面板。这些 Window 出现在其附加 Window 和任何Window的顶部
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;

// 当前Window的布局和顶级Window布局相同时,不能作为子代的容器
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;

// 用显示媒体 Window 覆盖顶部的 Window, 这是系统隐藏的 API
public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;

// 子面板在应用程序Window的顶部,这些Window显示在其附加Window的顶部, 这是系统隐藏的 API
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;

// 子 Window 类型的结束值
public static final int LAST_SUB_WINDOW = 1999;

系统级

// 系统Window类型的开始值
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;

// 已经从系统中被移除,可以使用 TYPE_KEYGUARD_DIALOG 代替
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;

// 系统对话框窗口
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;

// 锁屏时显示的对话框
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;

// 输入法窗口,位于普通 UI 之上,应用程序可重新布局以免被此窗口覆盖
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_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;

// 系统Window类型的结束值
public static final int LAST_SYSTEM_WINDOW = 2999;
还有window的flags参数
// 当 Window 可见时允许锁屏
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;

// Window 后面的内容都变暗
public static final int FLAG_DIM_BEHIND = 0x00000002;

// Window 不能获得输入焦点,即不接受任何按键或按钮事件,例如该 Window 上 有 EditView,点击 EditView 是 不会弹出软键盘的
// Window 范围外的事件依旧为原窗口处理;例如点击该窗口外的view,依然会有响应。另外只要设置了此Flag,都将会启用FLAG_NOT_TOUCH_MODAL
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;

// 设置了该 Flag,将 Window 之外的按键事件发送给后面的 Window 处理, 而自己只会处理 Window 区域内的触摸事件
// Window 之外的 view 也是可以响应 touch 事件。
public static final int FLAG_NOT_TOUCH_MODAL  = 0x00000020;

// 设置了该Flag,表示该 Window 将不会接受任何 touch 事件,例如点击该 Window 不会有响应,只会传给下面有聚焦的窗口。
public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;

// 只要 Window 可见时屏幕就会一直亮着
public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;

// 允许 Window 占满整个屏幕
public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;

// 允许 Window 超过屏幕之外
public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;

// 全屏显示,隐藏所有的 Window 装饰,比如在游戏、播放器中的全屏显示
public static final int FLAG_FULLSCREEN      = 0x00000400;

// 表示比FLAG_FULLSCREEN低一级,会显示状态栏
public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;

// 当用户的脸贴近屏幕时(比如打电话),不会去响应此事件
public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;

// 则当按键动作发生在 Window 之外时,将接收到一个MotionEvent.ACTION_OUTSIDE事件。
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;

@Deprecated
// 窗口可以在锁屏的 Window 之上显示, 使用 Activity#setShowWhenLocked(boolean) 方法代替
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;

// 表示负责绘制系统栏背景。如果设置,系统栏将以透明背景绘制,
// 此 Window 中的相应区域将填充 Window#getStatusBarColor()和 Window#getNavigationBarColor()中指定的颜色。
public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;

// 表示要求系统壁纸显示在该 Window 后面,Window 表面必须是半透明的,才能真正看到它背后的壁纸
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;

window的solfInputMode属性

这个属性很多人都很面熟的吧,没错,就是跟键盘有关系的那个仔

// 没有指定状态,系统会选择一个合适的状态或者依赖于主题的配置
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;

// 当用户进入该窗口时,隐藏软键盘
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;

// window会调整大小以适应软键盘窗口
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;

// 没有指定状态,系统会选择一个合适的状态或依赖于主题的设置
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;

// 当软键盘弹出时,窗口会调整大小,例如点击一个EditView,整个layout都将平移可见且处于软件盘的上方
// 同样的该模式不能与SOFT_INPUT_ADJUST_PAN结合使用;
// 如果窗口的布局参数标志包含FLAG_FULLSCREEN,则将忽略这个值,窗口不会调整大小,但会保持全屏。
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;

// 当软键盘弹出时,窗口不需要调整大小, 要确保输入焦点是可见的,
// 例如有两个EditView的输入框,一个为Ev1,一个为Ev2,当你点击Ev1想要输入数据时,当前的Ev1的输入框会移到软键盘上方
// 该模式不能与SOFT_INPUT_ADJUST_RESIZE结合使用
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;

// 将不会调整大小,直接覆盖在window上
public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
x与y属性:指定window的位置
alpha:window的透明度
gravity:window在屏幕中的位置,使用的是Gravity类的常量
format:window的像素点格式,值定义在PixelFormat中,比如支持透明
考官再问问你WindowManager呢?

额,依然可以对答入流,WindowManger嘛,很明显是一个对window进行管理的类,他是个接口类,继承自ViewManager,那么具体的实现类呢?,慢慢开始有的同学可能就答不上来了,WindowManagerImpl,对,没错,那个很帅的同学,回答对了,如果我们想要对Window进行添加和删除就可以使用WindowManager,具体的工作都是由WMS来处理的,WindowManager和WMS通过Binder来进行跨进程通信,WMS作为系统服务有很多API是不会暴露给WindowManager的。如下:


image.png

我们可以看到Window包含了View,对View进行管理,然后WindowManger是对Window进行管理,上面我们说过WindowManager其实是一个接口类,那么管理的具体实现又是WMS去处理的。这样一来,感觉对整体有个大的把握了,接着往下看。
刚刚说了WindowManager是继承自ViewManager的,我们来看看ViewManager的源码:

package android.view;

/** Interface to let you add and remove child views to an Activity. To get an instance
  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
  */
public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

很清晰,ViewManger提供了三个方法,addView(添加View)、updateViewLayout(更新布局)、removeView(删除View).根据WindowManager跟ViewManger的继承关系,WindowManger中也有这些方法,而这些方法传入的参数都是View,说明WindowManager具体管理的是以View形式存在的Window,当然通过源码中可以看到Windowmanger作为一个子类,其中还有很多自己的常量跟方法,这里就不一一贴出来了,后面会说到额。
那么实际应用的过程中,我们是怎么来使用的呢?
比如我们现在通过在window添加view的方式,来在界面上面显示一个按钮,而不是通过Activity中的setContentView方法去实现。

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Button floatingButton = new Button(this);
        floatingButton.setText("button");
      //通过windowmanager.layoutparams来设置window的相关属性
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                0, 0,
                PixelFormat.TRANSPARENT
        );
        // flag 设置 Window 属性
        layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        // type 设置 Window 类别(层级),这里需要应用上层显示权限(6.0及以上)
        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        //设置window显示的位置
        layoutParams.gravity = Gravity.CENTER;
        WindowManager windowManager = getWindowManager();
       //通过windowmanger来添加view
        windowManager.addView(floatingButton, layoutParams);
    }

//这里给出的一个应用Window 相当于Activity的界面显示,咱们不用setCOntentView这个方法
       WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,TYPE_APPLICATION,0, PixelFormat.TRANSPARENT);
       layoutParams.gravity = Gravity.CENTER;
       layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        layoutParams.type = TYPE_APPLICATION;
        WindowManager windowManager = getWindowManager();
        View view = LayoutInflater.from(this).inflate(R.layout.activity_demo6, null);
        windowManager.addView(view,layoutParams);

很清晰的可以看到,我们是通过windowManger来设置了window的相关属性,然后把view添加到window上去的,虽然全程并没有看到关于window的对象.当然这里提高一点,我们如果是自定义的一些View,比如继承自Dialog的时候也可以这么实现:

         private void initView() {
        View view = getLayoutInflater().inflate(R.layout.test, null);
          //findView过程
        initFindView(view);
       // 此处是直接调用的Dialog的setContentView方法
        setContentView(view);
        initAttribute();
        Window window = getWindow();
        window.setGravity(Gravity.BOTTOM);
        // 设置dialog占满父容器
        WindowManager.LayoutParams lp = window.getAttributes();
        lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
        window.setAttributes(lp);
        // 设置点击外围消散
        setCanceledOnTouchOutside(true);
    }

      //Window中的方法
    public void setAttributes(WindowManager.LayoutParams a) {
        mWindowAttributes.copyFrom(a);
        dispatchWindowAttributesChanged(mWindowAttributes);
    }

实际处理过程如下图:


image.png

继续来深入分析一波:
看似是通过WindowManager来addView,但是上面已经说过了WindowManger是个接口类,具体的add到底是哪里呢?它的真正实现是WindowManagerImpl类。

        public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

接着往下看WindowManagerGlobal中的addView方法做了什么

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);
        } 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);

            view.setLayoutParams(wparams);

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

            try {
                //将View显示到手机窗口
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

ViewRootImpl 是一个视图层次结构的顶部,ViewRootImpl 实现了 View 与 WindowManager 之间所需要的协议,作为 WindowManagerGlobal 中大部分的内部实现。

参考文献:https://blog.csdn.net/weixin_43766753/article/details/108350589
https://www.jianshu.com/p/1973b529a414
https://www.jianshu.com/p/c5c3ef2b1b03

相关文章

网友评论

      本文标题:Android关于Window相关的一些总结

      本文链接:https://www.haomeiwen.com/subject/strxrltx.html