关于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
网友评论