目录
- 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 过程。(加粗部分需要继续研究)