前言
从windows窗口的概念开始,通过对比去理解Android窗口体系,本文没有深入源码,重在理解概念
代码都是抄来抄去,概念也是互相借鉴 🐶,先看看Windows窗口的一些概念。
概念如下:
是我们使用软件时看到的界面,包含各种各样的控件,与用户交互
窗口有三种类型: 系统窗口类,应用程序全局窗口,应用程序局部窗口
窗口具有Z轴层级
可以设置窗口的大小与位置
平时打开文件夹,微信,网易云,AndroidStudio都是打开一个窗口。
了解Android窗口的同志们可能会感觉到Windows窗口的概念好熟悉呀。
窗口这个概念在Android中并不清晰,手机移动终端屏幕太小。很少有机会和Windows一样一块屏幕同时展示多个窗口。
操作也不一样,在Windows中可以随意对 不同窗口调整,最大化最小化,拖拽,改变尺寸,切换窗口巴拉巴拉的一顿操作。
所以Windos天生就更容易理解窗口,毕竟能够直接操作。打开微信和网易云一眼就能看出 这是两个窗口。
Windows先放一哈,视线会到Android,看一个情景,包含:Activity,Dialog,Toast。 问:图里有几个窗口
答:三个,Activity应用窗口,Dialog子窗口,Toast系统窗口。
如果没看过源码的同志肯定会犹豫,说到底还是移动端手机屏幕太小了,没办法给人直观感性的认识窗口。
通过对比还是看出窗口的一些特性:
具有层级概念,在上述场景中Activity层级最低,Dialog次之,Toast最高
窗口可以设置位置大小,屏幕中的一块区域展示内容。只不过Activity是全屏的,不像Windows可以最大化最小化, 用户手动改变窗口大小,弱化了窗口的概念。
对于Window的认识阶段
第一阶段
刚刚学习Android的时候,都听说过一个概念,Activity代表一个界面。实际开发起来也是如此,在Activity中加载xml文件,绑定数据与view。
Activity == UI
第二阶段
看了几篇文章,接触了Window,WMS 概念 。发现Activity并不是UI界面,Activity内部持有Window对象,Window的实现类PhoneWindow内部持有DecorView作为根布局,开发人员编写的xml 会添加到DecorView中。 哦!结合对WMS粗浅的理解,WMS是窗口管理服务,window不就是窗口么,可能window在创建完成后最终传递给WMS管理。 Window == UI
第三阶段
之后深入到源码中发现Window虽然翻译过来是窗口,但实际上并不是真正的窗口。
理由有二:Window并没有与WMS交互,Window没有view管理之类的功能。
首先可以确定的是wms是系统窗口服务,所有窗口都要与wms打交道。如果window代表的窗口,那么它或者它的唯一子类PhoneWindow,必然存在Binder机制与wms交互,然而并没有。
既然Window没有与wms交互,那它做了什么工作呢?
在面向对象中,设计一个类的意义可以从它的属性以及暴露的方法来推测。
如下是从:PhoneWindow中摘取的一些通过名字可以大概推测出作用的属性
大部分都是关于资源的设置:状态栏,导航栏,是否透明,转场动画,应用主题等等
private DecorView mDecor;
private TextView mTitleView;
int mStatusBarColor = 0;
int mNavigationBarColor = 0;
private int mTitleColor = 0;
private CharSequence mTitle = null;
boolean mIsFloating;
private boolean mIsTranslucent;
private LayoutInflater mLayoutInflater;
private Transition mEnterTransition = null;
private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
private int mTheme = -1;
private boolean mIsStartingWindow;
Window类注释 — 百度翻译
Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.
顶级窗口外观和行为策略的抽象基类。此类的实例应用作添加到窗口管理器的顶级视图。它提供标准的UI策略,例如背景、标题区域、默认键处理等。
结合window类注释可以做出结论,Window也是一层封装,提供通用页面模板,并不是真正的window。
寻找真正的Window
上面讨论了Window类 并不是真正的Window,只是一层封装。系统提供了WindowManager
允许开发人员添加Window。
如下代码是在Activity获取windowManager 添加Window。为什么api是addView
。不应该是addWindow
才对么? 难道view才是window?(下面代码会报错 添加系统window需要权限)
val wm:WindowManager =windowManager
val layoutParams = WindowManager.LayoutParams()
layoutParams.run{
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
format = PixelFormat.TRANSLUCENT
gravity = Gravity.STARTor Gravity.TOP
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
type =
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
else WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
}
val view :View = LayoutInflater.from(this).inflate(R.layout.xxx,null)
wm.addView(view, layoutParams)
跟踪源码
WindowManager
是个接口,实现类为 WindowManagerImpl
WindowManagerImpl
内部把逻辑转发给WindowManagerGlobal
WindowManagerGlobal
调用 ViewRootImpl
ViewRootImpl
通过WindowSession 与 wms 完成进程间通信
具体方法调用流程
WindowManager.addView()
—WindowManagerImpl.addView()
—WindowManagerGlobal.addView()
— ViewRootImpl.setView()
— WindowSession.addToDisplayAsUser()
ViewRootImpl类核心逻辑如下:
WindowManager.addView()
在应用层最终调用 ViewRootImpl.setView()
添加的View通过 WindowSession 进入 wms,方法 IWindowSession.addToDisplay
第一个参数 mWindow
代表真正的window。
mWindow的实现类W,类型是 IWindow.Stub ,Binder对象 对其他进程暴露方法。
W类 持有ViewRootImpl ,公开的接口方法内部调用ViewRootImpl 类。
所以 IWindowSession
是把一个Binder对象传递给WMS,WMS通过进程间通信操作ViewRootImpl ,ViewRootImpl 操作View
ViewRootImpl 操作的View
对应到当前场景是 windowManager.addView() 添加的View
对应到Activity则是PhoneWindow中DecorView
经过这一顿分析 好像没有确定Window的实体对象 难以捉摸。它不像一个User类,Person类那样明晃晃的放在开发者面前。原本以为 传递给WMS肯定会是Window了,结果是Binder,WMS进程间通信最终操控的是View。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
final W mWindow;
View mView;
final IWindowSession mWindowSession;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
mView = view;
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);
}
static class W extends IWindow.Stub {
private final WeakReference<ViewRootImpl> mViewAncestor;
private final IWindowSession mWindowSession;
W(ViewRootImpl viewAncestor) {
mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
mWindowSession = viewAncestor.mWindowSession;
}
....
@Override
public void hideInsets(@InsetsType int types, boolean fromIme) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.hideInsets(types, fromIme);
}
}
@Override
public void moved(int newX, int newY) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchMoved(newX, newY);
}
}
....省略其他方法
}
}
Window到底是什么
window是一个抽象的概念,对应手机屏幕的一块区域,实际是view。
View成了Window??? 什么场景下可以把View叫做Window呢?
想象一个场景:一个Activity内有DialogA,DialogB
这个场景会创建三个Window,Activity一个,Dialog两个,对应三个xml布局。是三个抽象的Window,对应三个具体的View,应该叫做View树
它们彼此之间互不影响,为DialogA添加View,不会影响到Activity和DialogB。因为它们属于不同的Window。
这也应该是添加Window的Api 叫做 addView()
而不是 addWidnow()
的原因。
根本就没有具体的Window,只有具体的View,Window是抽象的。
理解了什么是Window之后,在简单说一下添加window的Api。
View表示需要在屏幕展示的内容
layoutParams 则是对内容进行约束,基本的宽高,位置。
layoutParams.type 设置window类型,其实是弹窗的显示层级。
应用window:1 ~ 99
子window:1000 ~ 1999
系统window:2000~ 2999
数值越大层级越高,层级高覆盖层级低的,一般通过常量设置,系统window需要申请权限
layoutParams.flags 设置Window不同场景下的逻辑,比如:
// 全屏显示,隐藏所有的 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 装饰,比如在游戏、播放器中的全屏显示 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;
val wm:WindowManager =windowManager
val layoutParams = WindowManager.LayoutParams()
layoutParams.run{
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
format = PixelFormat.TRANSLUCENT
gravity = Gravity.STARTor Gravity.TOP
x = 0
y = 0
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
type =
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
else WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
}
val view :View = LayoutInflater.from(this).inflate(R.layout.xxx,null)
wm.addView(view, layoutParams)
Activity,Window,View的关系
Activity是一层封装,屏蔽复杂的系统实现细节,抽象出UI生命周期,方便开发人员工作,专注于界面样式的编写
Window,指PhoneWindow,页面通用模板,所有的Window都需要主题,状态栏,导航栏,背景等等设置。PhoneWindow是对上述内容的一个模板实现。
软件设计中很重要的一点就是找到业务当中的 “变与不变”。 在Window体系中,一个页面通用不变的部分交给PhoneWindow实现。变化的部分就是View,让开发人员能够自由定制。
PhoneWindow的存在也是帮Activity减轻负担,指责单一是一个好理解并且非常有效的原则。Activity已经非常复杂了, 设计出PhoneWindow把UI相关的代码从Activity中剥离出去。
由了上述层层抽象封装才有了最初学习Android时的概念,Activity == 页面。