Window 和 WindowManager
Window 表示一个窗口的概念,它是一个抽象类,具体实现是 PhoneWindow,可通过 WindowManager 创建 Window,Window 具体实现位于 WindowManagerService 中,WindowManager 与 WindowManagerService 的交互是一个 IPC 过程。不管是 Activity,Dialog,Toast,它们的视图实际上都是附加在 Window 上,因此 Window 实际是 View 的直接管理者。
- 使用 WindowManager 添加一个 Window,注意在大于等于23版本下编译,悬浮窗权限默认是关闭没有权限,然在小于23版本下编译悬浮窗权限是开启有权限的,可以在设置 – 应用程序 – 选择你的 App 进行设置。
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Button button = new Button(WindowActivity.this);
button.setText("我是添加的悬浮窗");
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = 50;
params.y = 400;
wm.addView(button, params); //onCreate 中添加悬浮窗
protected void onDestroy() { //移除悬浮窗
super.onDestroy();
if (wm != null) {
wm.removeView(button);
}
}
FLAG_NOT_FOCUSABLE
表示 Window 不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用 FLAG_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的 Window。
FLAG_NOT_TOUCH_MODAL
系统会将当前 Window 区域以外的单击事件传递给底层的 Window,当前 Window 区域以内的单击事件则自己处理。一般开启此标记,否则其他 Window 将无法收到单击事件。
FLAG_SHOW_WHEN_LOCKED
开启此模式可以让 Window 显示在锁屏的界面上。
Type 参数表示 Window 的类型,有三种类型,并具有层级范围,层级大的会覆盖在层级小的 Window 的上面,如果想要位于所有 Window 最顶层,那么采用较大层级即可。一般采用 TYPE_SYSTEM_OVERLAY 和 TYPE_SYSTEM_ERROR,还要同时声明权限 “android.permission.SYSTEM_ALERT_WINDOW”
Window 类型 | 详解 | 层级范围 |
---|---|---|
应用 Window | 应用类 Window 对应着一个 Activity | 1~99 |
子 Window | 不能单独存在,需要附属在特定父 Window,比如 Dialog | 1000~1999 |
系统 Window | 需要声明权限在能创建的 Window,比如 Toast | 2000~2999 |
Window 的内部机制
Window 是一个抽象的概念,每一个 Window 都对应着一个 View 和 一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,因此 Window 并不是实际存在的,它是以 View 的形式存在。
Window 的添加过程源码分析
Window 的添加过程需要通过 WindowManager 的 addView 来实现,WindowManager 是一个接口,实现类是 WindowManagerImpl 类,部分实现如下:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), 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);
}
WindowManagerImpl 并没有实现 Window 的三大操作,而是全部交给 WindowManagerGlobal 来处理。mGlobal 的 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;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
2.创建 ViewRootImpl 并将 View 添加到列表中
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
//存储的是正在被删除的 View 对象
private final ArraySet<View> mDyingViews = new ArraySet<View>();
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view); //存储的是所有 Window 所对应的 View
mRoots.add(root); //存储的是所有 Window 所对应的 ViewRootImpl
mParams.add(wparams); //存储的是所有 Window 所对应的布局参数
3.通过ViewRootImpl 来更新页面并完成 Window 的添加过程
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
ViewRootImpl 中的 setView 方法会调用 requestLayout 方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
scheduleTraversals 实际是 View 绘制的入口,接着会通过 WindowSession 最终来完成 Window 的添加过程,mWindowSession 类型是 IWindowSession,是一个 Binder 对象,真正实现类是 Session,一次 IPC 调用。
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, 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 的添加。
Window 的删除过程
Window 的删除过程和添加过程一样,都是先通过 WindowManagerImpl 后,再进一步通过 WindowManagerGlobal 来实现。
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);
}
}
通过 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 中提供异步删除和同步删除两种接口,这里是异步情况,具体的删除操作由 ViewRootImpl 的 die 方法来完成。die 方法发送一个请求消息立刻返回,并没有完成删除操作,添加到待删除的 mDyingViews 列表中。
boolean die(boolean immediate) {
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "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 会调用 dispatchDetachedFromWindow,真正删除在这个方法中,主要做四件事。
- 垃圾回收相关的工作,比如清除数据和消息,移除回调。
- 通过 Session 的 remove 方法删除 Window,同样是一个 IPC 过程,Session 内部通过 WindowManagerService 的 removeWindow 方法。
- 调用 View 的 dispatchDetachedFromWindow 方法,内部调用 View 的 onDetachedFromWindow 以及 onDetachedFromWindowInternal,当 View 移除时,做一些资源回收的工作,比如终止动画,停止线程等。
- 调用 WindowManagerGloal 的 doRemoveView 方法刷新数据,包括 mRoots、mParams 以及 mDyingViews,需要将当前 Window 所关联的这三类对象从列表中删除。
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 的 LayoutParams 并替代掉老的 LayoutParams,接着在更新 ViewRootImpl 中的 LayoutParams,这一步通过 setLayoutParams 实现,同样通过requestLayout 中的 scheduleTraversals 方法对 View 重新布局,ViewRootImpl 还会通过 WindowSession 来更新 Window 的视图,最终由 WindowManagerService 的relayoutWindow 方法来具体实现,也是一个 IPC 过程。