一、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通信过程如下
文章来源:https://www.toymoban.com/news/detail-439877.html
总结
以上便是笔者关于Window的相关理解文章来源地址https://www.toymoban.com/news/detail-439877.html
到了这里,关于Android:Window相关理解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!