目录
- 1 Window 和 WindowManager
- 1.1 说一说对于 Window 的理解。
- 1.2 WindowManager 的常用方法有哪些?
- 1.3 WindowManager 中有一个静态内部类 LayoutParams,绘制一下它的类图。
- 1.4 Window 的类型分类
- 2 Window 的内部机制
- 2.1 Window 和 View 的关系
- 2.2 Window 的添加过程
- 2.3 Window 的删除过程
- 2.4 Window 的更新过程
1 Window 和 WindowManager
1.1 说一说对于 Window 的理解。
Window 表示一个窗口的概念。Window 是一个抽象类,它的具体实现是 PhoneWindow,同时也是唯一的实现。通过 WindowManager 可以创建一个 Window。外界访问 Window 要通过 WindowManager,Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 过程。
Android 中所有的视图都是通过 Window 来呈现的,不管是 Activity,Dialog 还是 Toast,它们的视图实际上都是附加在 Window 之上的,因此 Window 实际是 View 的直接管理者。
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.
1.2 WindowManager 的常用方法有哪些?
WindowManager 是一个接口,常用的方法有三个,即添加 View,更新 View 和删除 View,这三个方法是定义在 ViewManager 接口中,WindowManager 是继承于 ViewManager 的。
可以看到,addView,updateViewLayout 和 removeView 这三个接口方法都是针对 View 来操作的。
1.3 WindowManager 中有一个静态内部类 LayoutParams,绘制一下它的类图。
ViewGroup_LayoutParams +int width +int height LayoutParams +int x +int y +float horizontalWeight +float verticalWeight +int type +int flags +int softInputMode +int gravity +float horizontalMargin +float verticalMargin +int format +int windowAnimations +float alpha +float dimAmount +IBinder token +String packageName +int screenOrientation +float preferredRefreshRate +int systemUiVisibility -CharSequence mTitle +setTitle(CharSequence title) : void +getTitle() : CharSequence Parcelable extends implements参考链接:·WindowManager.LayoutParams详解总结和对应实例
1.4 Window 的类型分类
WindowManager.LayoutParams 的 type参数表示 Window 的类型。
| 类型 | 层级范围 | 说明 |
|---|---|---|
| 应用 Window | 1~99 | 应用类 Window 对应着一个 Activity |
| 子 Window | 1000~1999 | 子 Window 不能单独存在,需要附属在特定的父 Window 里,比如常见的 Dialog 就是一个子 Window |
| 系统 Window | 2000~2999 | 系统 Window 是需要声明权限 android.permission.SYSTEM_ALERT_WINDOW 才能创建的 Window,如 Toast 和系统状态栏都是系统 Window |
Window 是分层的,每个 Window 都有对应的 z-ordered,层级大的会覆盖在层级小的 Window 的上面。
应该注意的是,在有些手机上,需要手动打开 android.permission.SYSTEM_ALERT_WINDOW 才可以。
2 Window 的内部机制
2.1 Window 和 View 的关系
Window 是一个抽象的概念,每一个 Window 都对应着一个 View 和 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系。所以,Window 并不是实际存在的,它是以 View 的形式存在的。
2.2 Window 的添加过程
public class WindowActivity extends Activity implements View.OnTouchListener {
private WindowManager mWindowManager;
private Button mFloatingButton;
private WindowManager.LayoutParams mLayoutParams;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_window);
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
}
public void button(View view) {
mFloatingButton = new Button(this);
mFloatingButton.setText("click me");
mLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
// 如果不写下面这行,那么锁屏后竟然无法解锁了。
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL // 设置了这个标记,即便当前 Window 具有焦点,
// 也允许将当前 Window 区域以外的单击事件传递给底层的 Window;否则,当前窗口将消费所有的点击事件,不管点击事件是否在窗口之内。
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE // 表示 Window 不需要获取焦点,也不需要接收各种输入事件,
// 此标记会同时启用 FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的 Window
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; // 让 Window 显示在锁屏的界面上。
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mFloatingButton.setOnTouchListener(this);
// 添加
mWindowManager.addView(mFloatingButton, mLayoutParams);
}
}
从上面的代码中,可以看到:Window 的添加过程需要通过 WindowManager 的 addView 来实现。但是,WindowManager 是一个接口,那么它的实现类是谁呢?
其实在 ContextImpl 里有一个静态代码块,包含了初始化 WindowManager 的代码:
static {
registerService(WINDOW_SERVICE, new ServiceFetcher() {
Display mDefaultDisplay;
public Object getService(ContextImpl ctx) {
Display display = ctx.mDisplay;
if (display == null) {
if (mDefaultDisplay == null) {
DisplayManager dm = (DisplayManager)ctx.getOuterContext().
getSystemService(Context.DISPLAY_SERVICE);
mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
}
display = mDefaultDisplay;
}
// 构造了 WindowManagerImpl 的实例
return new WindowManagerImpl(display);
}});
}
private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
new HashMap<String, ServiceFetcher>();
private static void registerService(String serviceName, ServiceFetcher fetcher) {
if (!(fetcher instanceof StaticServiceFetcher)) {
fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
}
SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}
再看一下 ContextImpl 的 getSystemService 方法:
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
这里获取的正是 WindowManagerImpl 的实例。所以,WindowManager 接口的真正实现是 WindowManagerImpl 类。
我们看一下 WindowManagerImpl 类如何实现添加 Window 的:
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Display mDisplay;
private final Window mParentWindow;
public WindowManagerImpl(Display display) {
this(display, null);
}
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
}
可以看到,WindowManagerImpl 并没有直接实现 Window 的添加,更新以及删除,而是全部交给了 WindowManagerGlobal 来处理。WindowManagerGlobal 是单例类。
WindowManagerImpl 与 WindowManagerGlobal 的关系用到了桥接模式,如下:
先介绍 WindowManagerGlobal 里几个重要的列表:
// 存储的是所有 Window 对应的 View 对象
private final ArrayList<View> mViews = new ArrayList<View>();
// 存储的是所有 Window 对应的 ViewRootImpl 对象
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
// 存储的是所有 Window 对应的布局参数
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
// 存储的是那些正在被删除的 View 对象,或者说是那些已经调用 removeView 方法
// 但是删除操作还未完成的 View 对象
private final ArraySet<View> mDyingViews = new ArraySet<View>();
在 WindowManagerGlobal 的 addView 方法的执行流程:
-
检查参数合法性,如果是子
Window(存在父Window的就是子Window)就调整一些布局参数;如果不存在父Window并且版本大于等于 21,会默认设置窗口的硬件加速属性。 -
检查
View是否在mViews集合中,是否在mDyingViews集合中。如果已经在mView里,是不可以添加的,会抛出异常;如果已经在mDyingViews集合里,需要立马销毁掉对应的View。int index = findViewLocked(view, false); if (index >= 0) { if (mDyingViews.contains(view)) { // Don't wait for MSG_DIE to make it's way through root's queue. mRoots.get(index).doDie(); } else { throw new IllegalStateException("View " + view + " has already been added to the window manager."); } // The previous removeView() had not completed executing. Now it has. } -
创建
ViewRootImpl对象,并将VIew添加到列表中相关的代码如下:
// 创建 ViewRootImpl 对象 root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); // 添加到 mViews.add(view); mRoots.add(root); mParams.add(wparams); -
通过
VIewRootImpl来更新界面完成Window的添加过程root.setView(view, wparams, panelParentView);4.1 在
setView方法内部,先通过requestLayout()来完成异步刷新请求public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { // 检查是否是 UI 线程 checkThread(); mLayoutRequested = true; // 开启异步刷新请求(这里面用到了同步屏障消息和异步消息) scheduleTraversals(); } }4.2 接着通过
WindowSession最终来完成Window的添加过程。// 这个返回值,就是平常开发中遇到的 `BadTokenException`,`InvalidDisplayException` 的来源。 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel);mWindowSession的类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,这说明Window的添加过程需要一次 IPC 调用。
4.3 在Session内部通过WindowManagerService来实现Window的添加。public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outInputChannel); }
2.3 Window 的删除过程
mWindowManager.removeView(mFloatingButton);
- 调用
WindowManager的removeView方法,是调用它的实现类WindowManagerImpl的removeView方法;再通过桥接调用WindowManagerGolbal对象的removeView(View view, boolean immediate)方法(第二个参数取值为false)如下:public void removeView(View view, boolean immediate) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } synchronized (mLock) { // 先在 mViews 集合里查找索引,找不到会抛出异常 int index = findViewLocked(view, true); // 从 mRoots 里根据索引找到 ViewRootImpl 对象,获取对应的 `View` 对象 View curView = mRoots.get(index).getView(); // 最后移除 View 对象 removeViewLocked(index, immediate); if (curView == view) { return; } throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView); } } 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); } } }ViewRootImpl的die方法。这里传给die的实参是false,表示异步删除,由removeView方法调用而来;传true时,表示同步删除,由removeViewImmediate方法调用而来。一般不要使用同步删除方式来删除Window以免发生意外的错误。ViewRootImpl的die方法:
如果boolean die(boolean immediate) { // Make sure we do execute immediately if we are in the middle of a traversal o // 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;immediate取值为true,即同步删除,那么会直接调用doDie()方法,die方法返回false,表示已经完成删除;如果immediate取值为false,即异步删除,那么发送一个请求删除的消息(MSG_DIE),die方法返回true,表示请求在排队中,再回到removeViewLocked方法中,会把view添加到mDyingViews集合(表示存那些正在被删除的 View 对象集合)。ViewRootImpl的doDie()方法:void doDie() { checkThread(); if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface); synchronized (this) { if (mRemoved) { return; } mRemoved = true; if (mAdded) { dispatchDetachedFromWindow(); } mAdded = false; } // 刷新几个列表的数据 WindowManagerGlobal.getInstance().doRemoveView(this); }ViewRootImpl的dispatchDetachedFromWindow()方法:void dispatchDetachedFromWindow() { if (mView != null && mView.mAttachInfo != null) { mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false); // 回调 View 的 onDetachedFromWindow 方法 mView.dispatchDetachedFromWindow(); } mView.assignParent(null); mView = null; try { // Session 的 remove 方法移除 Window mWindowSession.remove(mWindow); } catch (RemoteException e) { } // Dispose the input channel after removing the window so the Window Manager // doesn't interpret the input channel being closed as an abnormal termination. if (mInputChannel != null) { mInputChannel.dispose(); mInputChannel = null; } mDisplayManager.unregisterDisplayListener(mDisplayListener); unscheduleTraversals(); }
2.4 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.LayoutParams)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);
}
}
首先是对参数的一些检查,接着把布局参数设置给 view;int index = findViewLocked(view, true); 检查 view 是否还在 mViews 集合里,不在的话会抛出异常;最后调用 ViewRootImpl 对象的 setLayoutParams 方法更新 Window。在 ViewRootImpl 里会通过 scheduleTraversals 方法来对 View 重新布局,包括测量,布局,重绘三个过程。除了 View 本身的重绘外,ViewRootImpl 还会通过 WindowSession 来更新 Window 的视图,这个过程最终是 WMS 的 relayoutWindow() 来实现的,它同样是一个 IPC 过程。(加粗部分需要继续研究)




