前言
Window 是一个窗口的概念,是所有视图的载体,不管是 Activity,Dialog,还是 Toast,他们的视图都是附加在 Window 上面的。例如在桌面显示一个悬浮窗,就需要用到 Window 来实现。WindowManager 是访问 Window 的入口。
Window 是一个抽象类,他的实现类是 PhoneWidow,Activity 中的 DecorView ,Dialog 中的 View 都是在 PhoneWindow 中创建的。因此 Window 实际是 View 的直接管理者,例如:事件分发机制中,在 Activity 里面收到点击事件后,会首先通过 window 将事件传递到 DecorView,最后再分发到我们的 View 上。Activity 的 SetContentView 在底层也是通过 Window 来完成的。还有 findViewById 也是调用的 window。
在我的理解中,上面第一句话中的 window 和 第二句话中的 Window 不是一个东西。
第一句话中的 Window 是一个窗口,是一个抽象的概念,并不真实存在,他只是以 View 的形式存在。例如通过 WindowManager 添加一个 Window,这个 Window 就是以 View 的形式存在的。
第二句话中的 Window 指的是一个类,他的实现类是 PhoneWindow,他是用来创建我们页面中所需要的 View 的。所以这个 Window 可以称之为 View 的直接管理者。PhoneWindow 中的 DecorView 最终也是附加到 Window(窗口)上面的。
因为在最开始的时候经常把二者搞混,Window 即是 View 管理者,也是窗口,显然是不合理的。以上是我的个人理解,如果有感觉不对的,请指出,谢谢!
Window 和 WindowManager
如果要对 Window 进行添加和删除就需要通过 WindowManager 来操作,具体如下:
WindowManager 如何添加 Window?
val textView = TextView(this).apply { text = "window" textSize = 18f setTextColor(Color.BLACK) setBackgroundColor(Color.WHITE) } val parent = WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT ) parent.type = WindowManager.LayoutParams.TYPE_APPLICATION parent.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE parent.gravity = Gravity.END or Gravity.BOTTOM parent.y = 500 parent.x = 100 windowManager.addView(textView, parent)
上面这段代码可以添加一个 Window,位置在 (100,500),这里面比较重要的属性分别是 type 和 flags。
Type 窗口属性
Type 参数表示 Window 的类型,Window 分三种类型,对应着三种层级,如下:
子 Window 无法单独存在,必须依赖父级 Window,例如 Dialog 必须依赖 Activity 的存在
Window 分层,在显示时层级高的会覆盖层级低的窗口
Flags窗口的标志
Flags 表示 Window 的属性,它有多选项,通过这些可以通知 Window 显示的特性,例如:
WindowManager
WindowManager 所提供的功能很简单,常用的只有三个方法,即添加 View,更新View,和删除 View。
这三个方法定义在 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); }
public interface WindowManager extends ViewManager
由此看来 WindowManager 操作 Window 的过程更像是在操作 Window 中的 View,我们平常简单的那种可以拖动的 Window 效果其实是很好实现的,只需要修改 LayoutParams 中的 x,y 值就可以改变 Window 的位置。首先给 View 设置 onTouchListener,然后在 onTouch 方法中不断的更新 View 的位置即可。
Window 内部机制
Window 是一个抽象的概念,每一个 Window 都对应着一个 VIew 和一个 ViewRootImpl。
Window 和 View 通过 ViewRootImpl 来建立联系,因此 Window 并不是实际存在的,它是以 View 的形式存在。这个从 WindowManager 的定义就可以看出,提供的三个方法都是针对 View。这说明 View 才是 Window 的实体。
在实际开发中无法直接访问 Window,对 Window 访问必须通过 WindowManager
Window 的添加过程
Window 的添加需要通过 WindowManager 的 addView 来实现,WindowManager 是一个接口,他的真正实现是 WindowManageImpl。如下:
@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 来处理。
WindowManagerGlobal.addView
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); } else { //.... } ViewRootImpl root; View panelParentView = null; //创建 ViewRootImpl,并赋值给 root root = new ViewRootImpl(view.getContext(), display); //设置 View 的params view.setLayoutParams(wparams); //将 view,RootRootImpl,wparams 添加到列表中 mViews.add(view); mRoots.add(root); mParams.add(wparams); try { //调用 ViewRootImpl 来更新界面并完成 Window 的添加过程 root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; } }
上面代码中,创建了 ViewRootImpl,然后将 view,RootRootImpl,wparams 添加到列表中。最后通过 ViewRootImpl 来完成添加 Window 的过程。
这些列表的定义如下:
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>(); private final ArraySet<View> mDyingViews = new ArraySet<View>();
mViews 中是所有 Window 对应的 View
mRoots 中是所有 Window 对应的 ViewRootImpl
mParams 存储的是所有 Window 所对应的布局参数
而 mDyingViews 中是哪些真在被删除的 View,或者说是已经调用 RemoveView 但是删除操作没有完成的 Window 对象。
ViewRootImpl.setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; // 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(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, mTempInsets); setFrame(mTmpFrame); } //..... } } } @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
这个方法首先会调用 requestLayout 来进行一次刷新请求,其中 scheduleTraversals() 是 View 绘制的入口
requestLayout 调用之后,调用了 mWindowSession.addToDisplay 方法,来完成最终的 Window 的添加过程。
在上面代码中,mWindowSession 的类型是 IWindowSession,他是一个 Binder 对象,真正的实现是 Session,也就是 Window 的添加过程是一次 IPC 调用。
在 Session 内部会通过 WindoweManagerServer 来实现 Window 的添加,如下所示:
@Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,outInsetsState); }
如此一来,Window 的添加过程就交给了 WindowManagerServer 去处理。WMS 会为其分配 Surface,确定窗口显示的次序,最终通过 SurfaceFlinger 将这些 Surface 绘制到屏幕上。
梳理一下流程
首先调用的是 WindowManagerImpl.addView()
在 addView 中将实现委托给了 WindowManagerGlobal.addView()
WindowManagerGlobal.addView()
在 addView 中创建了 ViewRootImpl 赋值给了 root 。然后将 view,params,root 全部存入了各自的列表中。
最后调用了 ViewRootImpl.setView()
ViewRootImpl.setView()
在 setView 中通过调用 requestLayout 完成刷新的请求,接着会通过 IWindowSession 来完成最终的 Window 添加的过程,IWindowSession 是一个 Binder 对象,真正的实现类是 Session,也就是说 Window 的添加过程试一次 IPC 的调用。
在 Session 中会通过 WindowManageServer 来实现 Window 的添加。
Window 的更新过程
这里直接从 WindowManagerGlobal 开始:
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 中 view.setLayoutParams(wparams); synchronized (mLock) { //获取到 view 在列表中的索引 int index = findViewLocked(view, true); //拿到 view 对应的 ViewRootImpl ViewRootImpl root = mRoots.get(index); //从参数列表中移除旧的参数 mParams.remove(index); //将新的参数添加到指定的位置中 mParams.add(index, wparams); //调用 ViewRootImpl.setLayoutPrams 对参数进行更新 root.setLayoutParams(wparams, false); } }
ViewRootImpl.setLayoutPrams
在 setLayoutPrams 方法中,最终调用了 scheduleTraversals 方法来对 View 重新策略,布局,重绘。
除了 View 本身的重绘外,ViewRootImpl 还会通过 WindowSession 来更新 Window 视图,这个过程是由 WindowManagerServer 的 relayoutWindow来实现的,这同样也是一个 IPC 过程。
Window 的删除过程
Window 的删除过程和添加过程都一样,都是先通过 WindowManagerImpl 后,在进一步通过 WindowManagerGlobal 来实现的:
WindowManagerGlobal.removeView
@UnsupportedAppUsage 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); } }
上面代码中,找到在 views 列表中的索引,然后调用了 removeViewLocked 来做进一步的删除
private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); View view = root.getView(); if (view != null) { InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class); 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 和 removeViewImmedialte 分别是异步删除和同步删除。
一般不会使用 removeViewImmedialte 来删除 Window,以免发生意外错误。
所以这里使用的是 异步的删除情况,采用的是 die 方法。die 方法只是发送了一个请求删除的消息就立刻返回了,这个时候 View 并没有完成删除操作,所以最后会将其添加到 mDyingViews 列表中。
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(mTag, "Attempting to destroy the window while drawing!\n" + " window=" + this + ", title=" + mWindowAttributes.getTitle()); } mHandler.sendEmptyMessage(MSG_DIE); return true; }
这个方法里面做了判断,如果是异步删除就会发送一个 MSG_DIE 的消息,ViewRootImpl 中的 handler 会收到这个消息,并调用 doDie 方法,这就是这两种删除方式的区别。
void doDie() { checkThread(); if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface); synchronized (this) { if (mRemoved) { return; } mRemoved = true; if (mAdded) { //真正删除的逻辑是在此方法中 dispatchDetachedFromWindow(); } //.... mAdded = false; } WindowManagerGlobal.getInstance().doRemoveView(this); }
Window 的创建过程
通过上面的分析可以得出,View 不能单独存在,必须依附在 Window 上面,因此有视图的地方就有 Window。这些视图包括 :Activity,Dialog,Toast,PopupWindow 等等。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { //..... if (activity != null) { appContext.setOuterContext(activity); activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback, r.assistToken); //.... } return activity; }
在 Activity 的 attach 方法中,系统会创建 Activity 所属的 Window,并未其设置回调接口,由于 Activity 实现了 Window 的 Callback 接口,因此当 Window 接受到外接的状态改变时就会回调 Activity 中的方法。
Callback 中的方法有很多,但是有些我们是非常熟悉的,例如 dispatchTouchEvent,onAttachedToWdindow 等等。