Android:Window相关理解


一、Window概述

Window概念

  • window是一个抽象类,主要用来处理窗口的展示与行为策略(比如触摸,点击等)。
  • window是View的直接管理者
  • window类的实例应该是作为顶级view,被添加到windowManager的顶级视图。(PhoneWindow)
  • window提供了标准的UI策略,如背景,标题区域,默认密钥处理等。
  • widnow唯一的实现类是android.view.PhoneWindow,如果要使用window就必须通过android.view.PhoneWindow。

Window和DecorView

  • 一个Activity对应一个PhoneWindow,PhoneWidnow会处理这个activity中的ui展示和 用户的行为(如触摸,点击等)。
  • PhoneWidnow不是一个View对象,通过将PhoneWindow添加到windowManager中,PhoneWindow能够将要处理的行为事件传递给DecorView。
  • DecorView继承自FrameLayout,是除了Window之外最顶级的视图。
  • ContentView就是我们通常使用activity.setContentView()中设置的View。它所对应的id是R.id.content。
    在这里插入图片描述

二、Window属性和类型

添加窗口是通过WindowManagerGlobal的addView方法操作的,这里有三个必要参数。view,params,display。 display : 表示要输出的显示设备。 view : 表示要显示的View,一般是对该view的上下文进行操作。(view.getContext()) params : 类型为WindowManager.LayoutParams,即表示该View要展示在窗口上的布局参数。其中有一个重要的参数type,用来表示窗口的类型。

Window的类型

应用窗口

Activity 对应的窗口类型是应用窗口, 所有 Activity 默认的窗口类型是 TYPE_BASE_APPLICATION。 WindowManager 的 LayoutParams 的默认类型是 TYPE_APPLICATION。 Dialog 并没有设置type,所以也是默认的窗口类型即 TYPE_APPLICATION

子窗口

子窗口不能单独存在,它需要附属在特定的父Window之中,常见的存在便是PopWindow,之所以称其为子窗口,是因为其的存在或出现依附于父窗口,父窗口显现子窗口才能出现,反之亦然

系统窗口

系统窗口跟应用窗口不同,不需要对应 Activity。跟子窗口不同,不需要有父窗口。一般来讲,系统窗口应该由系统来创建的,常见的系统窗口有音量调节栏、Toast弹窗和系统状态栏,屏保等。

Window的属性

这里介绍日常开发中我们或多或少会用到的Window属性

type参数
  • type参数,表示Window是什么类型,同时起到Z-order参数的作用,表示Window的层级,一般从0~9999,数值越大,越容易覆盖在屏幕上方

应用窗口的type属性值
应用程序Window的type值范围是[1-99],什么是应用程序Window,比如Activity所展示的页面,在WindowManager#LayoutParams中定义了如下应用程序的type值

// 应用程序 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;

子窗口的type属性值
表示子Window,它的范围是[1000,1999],这些Window会按照Z-Order顺序依附于父Window上,而且他们的坐标是相当于父Window的,例如PopupWindow和一些Dialo

/**
 * 子Window的开始值,该Window的token必须设置在他们依附的父Window
 */
public static final int FIRST_SUB_WINDOW = 1000;

/**
 * 应用程序Window上面的面板
 */
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;

/**
 * 用于显示多媒体(比如视频)的Window,这些Windows会显示在他们依附的Window后面
 */
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;

/**
 * 应用程序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上显示覆盖物
 * @hide
 */
@UnsupportedAppUsage
public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;

/**
 * 依附在应用Window上和它的子面板Window上的子面板
 * @hide
 */
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;

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

系统窗口的属性值
系统Window的范围是[2000,2999],常见的系统的Window有Toast、输入法窗口、系统音量条窗口、系统错误窗口等,对应type的值如下

// 系统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;\
\
@Deprecated\
// API 已经过时,用 TYPE_APPLICATION_OVERLAY 代替\
public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;\
\
@Deprecated\
// API 已经过时,用 TYPE_APPLICATION_OVERLAY 代替\
public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;\
\
// 已经从系统中被移除,可以使用 TYPE_KEYGUARD_DIALOG 代替\
public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;\
\
@Deprecated\
// API 已经过时,用 TYPE_APPLICATION_OVERLAY 代替\
public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;\
\
@Deprecated\
// API 已经过时,用 TYPE_APPLICATION_OVERLAY 代替\
public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;\
\
@Deprecated\
// API 已经过时,用 TYPE_APPLICATION_OVERLAY 代替\
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\
// API 已经过时,用 TYPE_APPLICATION_OVERLAY 代替\
public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;\
\
// 输入法窗口,位于普通 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;

需要注意的是,使用系统窗口需要申请相应权限Manifest.permission.SYSTEM_ALERT_WINDOW

Flag属性

除了Type属性以外,Flag属性用以控制Window的一些行为特征,如出现时Window后方是否会变暗

// 当 Window 可见时允许锁屏\
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;\
\
// Window 后面的内容都变暗\
public static final int FLAG_DIM_BEHIND        = 0x00000002;\
\
@Deprecated\
// API 已经过时,Window 后面的内容都变模糊\
public static final int FLAG_BLUR_BEHIND        = 0x00000004;\
\
// 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软键盘输入区域的显示模式,比如我们在微信聊天时,我们希望点击输入框软键盘弹起来的时候,能把输入框也顶上去,这样就可以看见自己输入的内容了。

// 没有指定状态,系统会选择一个合适的状态或者依赖于主题的配置
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.addView()

下面我们了解Window的添加过程,也就是WindowManager的addView()过程

我们先来看Window的内部机制图
在这里插入图片描述
对相关机制涉及到的类进行一番解读

  • ViewManager是个接口,用来规定View的一些控制行为
public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
  • WindowManager继承自ViewManager,并通过注解向外暴露可以获取的方式
public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
  • WindowManagerImpl是WindowManager的代理类,可以通过getSystemService获取,来看WindowManagerImpl中的addView()
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
	applyDefaultToken(params);
	mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
  • 这里调用mGlobal.addView()去进行View的添加控制,需要注意的是,WindowManagerGlobal是一个全局单例类,实际上就是使用桥接模式将所有View的控制委托给WindowManagerGlobal进行控制
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;
    //如果是子Window,需要对参数做额外调整
    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实例
    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        // 省略
        //创建ViewRootImpl实例,并且设置参数
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        //分别记录View树 ViewRootImpl和Window参数,见分析1
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        
        try {
            //最后通过ViewRootImpl来添加Window,见分析2
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
           ...
        }
    }
}
  • 由于WindowManagerGlobal是单例,它是真正WindowManager的逻辑实现类,所以需要把要处理的Window等都记录起来,这里使用了几个数据结构进行控制,注意到ViewRootImpl和Window其实是对应的关系,最后通过ViewRootImpl.setView()方法控制View的添加
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
  • 调用ViewRootImpl的setView()方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    synchronized (this) {
            ...
            //分析1,在被添加到WindowManager之前调用一次
            requestLayout();
            ...
            //通过WindowSession来完成IPC调用,完成创建Window
            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);
                }
            } catch (RemoteException e) {
                mAdded = false;
                mView = null;
                mAttachInfo.mRootView = null;
                mFallbackEventHandler.setView(null);
                unscheduleTraversals();
                setAccessibilityFocus(null, null);
                throw new RuntimeException("Adding window failed", e);
            } finally {
                if (restore) {
                    attrs.restore();
                }
            }

          ...
    }
}
  • 上面代码是ViewRootImpl的setView方法部分逻辑,它主要干俩件事,第一件事就是更新界面,在注释分析1的地方,通过调用requestLayout来完成异步刷新请求,方法实现如下
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
  • 其中scheduleTraversal方法就是View绘制的入口,接着会通过mWindowSession的addToDisplay方法来完成Window的添加过程,那这个mWindowSession是什么类的实例呢,通过查看源码可知,mWindowSession是一个IWindowSession对象,而IWindowSession是一个IBinder接口,所以mWindowSession只是一个Binder对象,而实现类在WindowManagerService中,这里通过mWindowSession完成了IPC通信。
    然后真正添加Window的逻辑就交由WindowManagerService(简称WMS)了,由于WMS比较复杂,这里就不过多深入了。

ViewRootImpl在其中起到的作用就是View和WindowManagerService的桥梁,在该类中对View进行了绘制,同时又通过IPC通信让WMS创建了Window

对于其中几个重要的类,进行梳理如下

  • ViewRootImpl,在调用addView时会创建实例,这也就说明一个View树对应一个ViewRootImpl,同时它是Window和View之间的桥梁,一边负责View的绘制,一边负责IPC通信到WMS创建Window。

  • IWindowSession实例,它是APP范围内单例,是一个Binder,负责和WMS通信。这里为什么一个一个应用就一个实例呢,这是因为WMS是系统服务,它要服务很多个APP,而一个APP又有多个Window,所以每个Window都要WMS来管理,则太多了,这样WMS只需要和APP的IWindowSession进行通信即可。

  • WindowManagerGlobal实例,前面我们调用WindowManager的addView方法时,会调用该类的单例,它可以看成是WindowManager的实现单例。

关于IWindowSession通信过程如下
在这里插入图片描述

总结

以上便是笔者关于Window的相关理解

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值