在Android框架层意义上,每个应用界面都有一个应用级的window
。例如popupWindow、Toast、dialog、menu
都是需要通过创建window来实现。它是一个窗口的概念,也是一个抽象类。
view树是window机制的操作单位,在android机制中,每一个view**树**对应一个window
,view树是window的存在形式
。例如,我们平时看到的应用界面、dialog、popupWindow
以及悬浮窗,都是window的表现形式。
注意,我们看到的不是window,而是view树。不像windows系统有一个很明显的标志,Android中我们只能看到view树无法看到window,window是控制view树需要怎么显示的管理者。每个view树
背后都有一个window。
通过View的事件分发机制我们也可以知道,单击事件由Window
传递给DecorView
,然后再由DecorView传递给的View,就连Activity的设置视图的方法setContentView
在底层也是通过Window来完成的。
为什么view树是window机制的操作单位,而不是每个view呢?
什么是view树?
个view树就对应一个window。
不属于activity的view树
内,所以这个**dialog是一个独立的view树,所以他是一个window
。**如果我们需要让一个悬浮窗在所有界面显示,如果是在一个应用还好,直接在Activity里面设置即可,但在多个应用的情况下,如小米悬浮窗,怎么确定两个不同应用的view
的显示次序?又例如我们需要弹出一个dialog来提示用户,怎么样可以让dialog永远处于最顶层呢,包括显示dialog期间应用弹出的如popupWindow必须显示在dialog的低下,但toast又必须显示在dialog上面。
很明显,我们的屏幕可以允许多个应用同时显示非常多的view,他们的显示次序或者说显示高度是不一样的,如果没有一个统一的管理者,那么每一家应用都想要显示在最顶层,那么屏幕上的view会非常乱。
同时,当我们点击屏幕时,这个触摸事件应该传给哪个view?很明显我们都知道应该传给最上层的view,但是接受事件的是屏幕,是另一个系统服务,他怎么知道触摸位置的最上层是哪个view呢?即时知道,他又怎么把这个事件准确地传给他呢?
Activity、Dialog
还是Toast
,它们的视图实际上都是附加在Window上的,因此Window实际是View树的直接管理者。在了解window的操作流程之前,先补充一下window的相关属性。
Type参数对应着WindowManager.LayoutParams
的type
参数,表示Window的类型。
Window
是分层的,每个Window都有对应的z-ordered
,层级大的会覆盖在层级小的Window的上面,很显然系统Window
的层级是最大的。window
的**type
属性就是Z-Order的值**,我们可以给**type
属性赋值来决定window的高度**。这个属性就解决了前面我们讲到的window的显示次序问题。Z-Order越大,window越靠近用户,显示越高。
window根据显示高度范围不同有三种分类:
Z-Order
在1-99。应用类Window
对应着一个Activity
。window
:子**window
一般是显示在应用window
**之上,Z-Order
在1000-1999
。子Window不能单独存在,它需要附属在特定的父Window
之中,比如常见的一些Dialog就是一个子Window。2000-2999
。如果要弹出自定义系统级window需要动态申请权限。系统为我们三类window都预设了静态常量。
应用级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
// 系统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
的type
有很多值,一般我们选用TYPE_SYSTEM_OVERLAY
或者TYPE_SYSTEM_ERROR
,如果采用TYPE_SYSTEM_ERROR
,只需要为type参数指定这个层级即可:mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;
同时声明权限:<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
。否则会在创建时报错
Flags
参数表示Window
的属性,它有很多选项,各种情景下的显示逻辑(锁屏,游戏等)还有触控事件的处理逻辑。控制显示是他的很大部分功能,但并不是全部。下面看一下一些常用的flag,就知道flag的功能了
// 当 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
的处理逻辑,这在日常中也经常遇到,如:我们在微信聊天的时候,点击输入框,当软键盘弹起来的时候输入框也会被顶上去。如果你不想被顶上去,也可以设置为被软键盘覆盖。下面介绍一下常见的属性
// 没有指定状态,系统会选择一个合适的状态或者依赖于主题的配置
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;
window的其他属性
上面的三个属性是window比较重要也是比较复杂 的三个,除此之外还有几个日常经常使用的属性:
window
属性的常量值大部分存储在WindowManager.LayoutParams
类中,我们可以通过这个类来获得这些常量。当然还有Gravity
类和PixelFormat
类等。
一般情况下我们会通过以下方式来往屏幕中添加一个window:
// 在Activity中调用
WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
windParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
TextView view = new TextView(this);
getWindowManager.addview(view,windowParams);
我们可以直接给WindowManager.LayoutParams
对象设置属性。
第二种赋值方法是直接给window
赋值,如
getWindow().flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
除此之外,window的softInputMode
属性比较特殊,他可以直接在AndroidManifest中指定,如下:
<activity android:windowSoftInputMode="adjustNothing" />
window的重要属性有
type、flags、softInputMode、gravity
等,我们可以通过不同的方式给window属性赋值,等遇到需求再去寻找对应的常量即可。
window
都被添加到PhoneWindow
,window
的触摸事件等都会汇集到PhoneWindow
,然后再交给Activity
处理,那么Activity
就可以通过操作PhoneWindow
来操作Window
PhoneWindow
本身不是真正意义上的window
,他更多可以认为是辅助Activity
操作window
的工具类。windowManagerImpl
并不是管理window
的类,而是管理PhoneWindow
的类。真正管理window
的是WindowManagerService
。PhoneWindow
可以配合DecorView
可以给其中的window
按照一定的逻辑提供标准的UI策略PhoneWindow
限制了不同的组件添加window
的权限。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService
中,WindowManager
和Window-ManagerService
的交互是一个IPC过程。
WindowManager
是一个接口,它的真正实现是WindowManagerImpl
类。
WindowManager
所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View。从WindowManager的定义也可以看出,它提供的三个接口方法都是针对View的,这也说明View才是Window存在的实体。这三个方法定义在ViewManager中,而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);
}
由此来看,WindowManager
操作Window的过程更像是在操作Window中的View。我们时常见到那种可以拖动的Window效果,只需要根据手指的位置来设定LayoutParams中的x和y的值即可改变Window的位置。首先给View设置onTouchListener:mFloatingButton.setOnTouchListener(this)
。然后在onTouch方法中不断更新View的位置即可:
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: {
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);
break;
}
default:
break;
}
return false;
}
可以发现,WindowManagerImpl
并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal
来处理,WindowManagerGlobal
以工厂的形式向外提供自己的实例,在WindowManagerGlobal
中有如下一段代码:private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance()
。WindowManagerImpl
这种工作模式是典型的桥接模式,将所有的操作全部委托给WindowManagerGlobal
来实现。
window
的添加过程就是通过WindowManagerImpl
委托WindowManager-Global
的addView
方法来添加window的过程。
我们首先得有view
和WindowManager.LayoutParams
对象,才能去创建一个window。下面的代码演示了通过WindowManager
添加Window
的过程。
Button button = new Button(this);
WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
// 这里对windowParam进行初始化
windowParam.addFlags...
// 获得应用PhoneWindow的WindowManager对象进行添加window
getWindowManager.addView(button,windowParams);
WindowManager-Global
的addView
主要分为如下几步
1.检查参数是否合法,如果是子Window
那么还需要调整一些布局参数
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 首先判断参数是否合法
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,会对其做参数的调整,**这个好理解,子window要跟随父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;
}
}
synchronized (mLock) {
...
// 这里新建了一个viewRootImpl,并设置参数
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 添加到windowManagerGlobal的三个重要list中,后面会讲到
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// 最后通过viewRootImpl来添加window
try {
root.setView(view, wparams, panelParentView);
}
...
}
}
2.创建ViewRootImpI
并,并把view
、viewRootImpl
、params
三个对象添加到三个list中进行保存
在WindowManagerGlobal
内部有如下几个列表比较重要:
//mViews存储的是所有Window所对应的View
private final ArrayList<View> mViews = new ArrayList<View>();
//mRoots存储的是所有Window所对应的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList <ViewRootImpl>();
//mParams存储的是所有Window所对应的布局参数
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
//mDyingViews则存储了那些正在被删除的View对象,或者说是那些已经调用removeView方法但是删除操作还未完成的Window对象。
private final ArraySet<View> mDyingViews = new ArraySet<View>();
每一个window所对应的这三个对象都会保存在这里,之后对window的一些操作就可以直接来这里取对象了。当window被删除的时候,这些对象也会被从list中移除。
ViewRootImpl
是view
的最高层级,属于所有View的根,但它不是View,实现了viewParent接口,控件的测量、布局、绘制以及输入事件的派发处理都由ViewRootImpl触发,是view和windowManager
的通信桥梁。viewRootImpl可以处理两边的对象,然后联结起来
3.通过ViewRootImpI
来更新界面并完成Window
的添加过程。
这个步骤由ViewRootImpl的setView方法来完成
,View的绘制过程是由ViewRootImpl来完成的,这里当然也不例外。
synchronized (mLock) {
...
// 这里新建了一个viewRootImpl,并设置参数
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 添加到windowManagerGlobal的三个重要list中,后面会讲到
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// 最后通过viewRootImpl来添加window
try {
root.setView(view, wparams, panelParentView);
}
...
}
addView
方法小结
window
是不是子window
,如果是的话需要对window
进行调整,这个好理解,子window
要跟随父window
的特性。viewRootImpl
对象,并把view
、viewRootImpl
、params
三个对象添加到三个list中进行保存viewRootImpl
来进行添加public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
...
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// 这里调用了windowSession的方法,调用wms的方法,把**添加window的逻辑交给wms**
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
}
...
}
}
viewRootImpl的逻辑很多,重要的就是调用了
mWindowSession
的方法调用了WindowManagerService
的方法。mWindowSession
是一个IWindowSession
类型对象,IWindowSession
是一个IBinder
接口,他的具体实现类在WindowManagerService
,本地的mWindowSession
只是一个Binder
对象,通过这个mWindowSession
就可以直接调用WindowManagerService
的方法进行跨进程通信
。
那这个mWindowSession是从哪里来的呢?我们到viewRootImpl的构造器方法中看一下:public ViewRootImpl(Context context, Display display) { ... mWindowSession = WindowManagerGlobal.getWindowSession(); ... }
可以看到这个session对象是来自WindowManagerGlobal。再深入看一下:
public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { ... sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { ... }); } ... } return sWindowSession; } }
这熟悉的代码格式,可以看出来这个session是一个单例,也就是整个应用的所有
viewRootImpl
的windowSession
都是同一个,也就是一个应用只有一个windowSession
。WindowManagerService
服务于多个应用。WindowManagerService
的对象单位是应用,他在内部给每个**应用的session
**分配了一些数据结构如list
,用于保存每个应用的window
以及对应的viewRootImpl
。当需要操作view
的时候,通过session直
接找到viewRootImpl
就可以操作了。
后面的逻辑就交给WindowManagerService
去处理了,WindowManagerService
就会创建window
,然后结合参数计算window
的高度等等,最后使用viewRootImpl
进行绘制。这后面的代码逻辑就不讲了,这是深入到WindowManagerService
的内容。
最后做个总结:
window的添加过程是通过PhoneWindow
对应的WindowManagerImpl
来添加window
,内部会调用WindowManagerGlobal
来实现。WindowManagerGlobal
会使用viewRootImpl
来进行跨进程通信让WindowManagerService
执行创建window
的业务。每个应用都有一个windowSession
,用于负责和WindowManagerService
的通信,如ApplicationThread
与AMS的通信。
在**setView
内部会通过requestLayout
来完成异步刷新请求**。在下面的代码中,scheduleTraversals
实际是View绘制的入口:
public void requestLayout() {
if (! mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();//View绘制的入口
}
}
接着会通过WindowSession
最终来完成Window
的添加过程。在下面的代码中
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
}
在Session
内部会通过WindowManagerService
来实现Window
的添加,代码如下所示。
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams
attrs,
int viewVisibility, int displayId, Rect outContentInsets,
InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility,
displayId,
outContentInsets, outInputChannel);
}
如此一来,Window
的添加请求就交给WindowManagerService
去处理了,在**Window-ManagerService
内部会为每一个应用保留一个单独的Session**。
Window
的删除过程和添加过程一样,都是先通过WindowManagerImpl
,再通过WindowManagerGlobal
来实现的。下面是WindowManagerGlobal
的removeView
的实现:
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
removeView
的逻辑很清晰,首先通过findViewLocked
来查找待删除的View的索引,这个查找过程就是建立的数组遍历,然后再调用removeViewLocked
来做进一步的删除,如下所示。
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view ! = null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm ! = null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view ! = null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
removeViewLocked
是通过ViewRootImpl
来完成删除操作的。在WindowManager
中提供了两种删除接口removeView
和removeViewImmediate
,它们分别表示异步删除和同步删除,其中一般来说不需要使用removeViewImmediate来删除Window以免发生意外的错误。
异步删除由**ViewRoot-Impl
的die
方法来执行。在异步删除的情况下,die
方法只是发送了一个请求删除的消息后就立刻返回了,这个时候View并没有完成删除操作,所以最后会将待删除的View添加到**mDyingViews
中。
ViewRootImpl
的die方法:
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal
or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && ! mIsInTraversal) {
doDie();
return false;
}
if (! mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(TAG, "Attempting to destroy the window while drawing! \n" +
" window=" + this + ", title=" + mWindowAttributes.
getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
在die方法内部只是做了简单的判断,如果是异步删除就发送一个MSG_DIE
的消息,ViewRootImpl
中的Handler
会处理此消息并调用doDie
方法,如果是同步删除(立即删除),就不发消息直接调用doDie
方法。在doDie
内部会调用此ViewRootImpl
的dispatchDetachedFromWindow
方法,真正删除View
的逻辑在dispatchDetachedFromWindow
方法的内部实现。
dispatchDetachedFromWindow
方法主要做四件事:
Session
的remove
方法删除Window
。mWindowSession.remove(mWindow)
,这同样是一个IPC过程,最终会调用WindowManagerService
的removeWindow
方法。dispatchDetachedFromWindow
方法,在内部会调用View的onDetached-FromWindow()
以及onDetachedFromWindowInternal()
。当View
从Window
中移除时onDetachedFromWindow()
也会被调用,可以在这个方法内部做一些资源回收的工作,比如终止动画、停止线程等。WindowManagerGlobal
的doRemoveView
方法刷新数据,包括mRoots、mParams
以及mDyingViews
,需要将当前Window
所关联的这三类对象从列表中删除。看WindowManagerGlobal
的updateViewLayout
方法,如下所示。
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (! (params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.
LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.Layout-
Params)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
updateViewLayout
方法中,首先它需要更新View
的LayoutParams
并替换掉老的LayoutParams
,接着再更新ViewRootImpl
中的LayoutParams
,这一步是通过ViewRootImpl
的setLayoutParams
方法来实现的。在ViewRootImpl
中会通过scheduleTraversals
方法来对View重新布局,包括测量、布局、重绘这三个过程。除了View本身的重绘以外,ViewRootImpl
还会通过WindowSession
来更新Window的视图,这个过程最终是由WindowManagerService
的relayoutWindow()
来具体实现的,它同样是一个IPC
过程。
Android开发艺术探索
https://blog.csdn.net/weixin_43766753/article/details/108350589