这篇文章主要介绍“Android窗口怎么实现”,在日常操作中,相信很多人在Android窗口怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Android窗口怎么实现”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
对于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 == 页面。