Window 基本概念
Window 基本概念
- 抽象类,具体实现类PhoneWindow
- 通过 WindowManager 可以创建,具体实现是 WindowManagerService 中
- WindowManager和 WindowManagerService 的交互是 IPC 过程
- Android 所有的视图都是通过 Window 来呈现的,不管是 Activity , Dialog ,还是 Toast ,他们的视图都是附件在 Window 上的,
Window是 View 的直接管理者
- setContentView()的底层也是通过 Window 来完成的
Window和WindowManager
WindowManager.LayoutParams中的 flags 和type
Flags 参数
表示 window 的属性,可以控制 Window 的显示特性,主要常用的有一下
-
FLAG_NOT_FOCUSABLE
表示 Window 不需要获取焦点,也不需要接受各种输入事件,同时启动 FLAG_NOT_TOUCH_MODAL ,事件最终传递给下层的焦点的Window
-
FLAG_NOT_TOUCH_MODAL
此模式下,系统会将当前的 Window 区域以外的点击事件传递给底层的 Window ,当前 Window 区域以内的单击事件则自己处理,这个标记很重要,一般来说都需要开启此标记,否则其他 Window 将无法收到单击事件
-
FLAG_SHOW_WHEN_LOCKED
让 Window 显示在锁屏界面上
Type参数
Window 的类型,分为三种
-
应用Window
层级范围1~99 , 对应一个Activity
-
子Window
层级范围 1000~1999, 不能单独存在,需要附属特定的的 Window 上,常见的一些 Dialog 就是一个子Window
-
系统Window
层级范围 2000~2999,需要声明权限才能创建的 Window ,比如 Toast 和系统状态栏
Window 是分层的,层级大的会覆盖层级小的,层级的范围对应这WindowManager.LayoutParams的 type 参数
WindowManager
- 继承ViewManager
- 常用的方法 addView() , updateViewLayout() ,removeView()
- 真正实现是WindowManagerImpl
Window的内部机制
- 每一个 Window 对应一个 View 和ViewRootImpl
Window和 View 通过 ViewRootImpl 建立联系
- Window并不是实际存在的,是以 View 的形式存在的
- View才是 Window 存在的实体,所以对 Window 的添加,删除,更新都是针对 View 。
Window 的添加过程
其实就是通过 WindowManagerImpl 是添加一个 View , 那么我们就看看 WindowManagerImpl 的 addView() 方法吧。
// WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
android.util.SeempLog.record_vg_layout(383,params);
applyDefaultToken(params);
mGlobal.addView(view, params , mContext.getDisplay(), mParentWindow);
}
WindowManagerImpl 也没做啥操作,只是简单的调用了 mGlobal 的 addView() 方法。那么这个 mGlobal 是啥玩意呢
- mGlobal 是 WindowManagerGlobal 的对象,
- WindowManagerGlobal以工厂形式向外提供自己的实例, WindowManager 中有这样一段代码
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
这是典型的桥接模式,将所有的的操作委托 WindowManagerGlobal 来实现
那就看看 WindowManagerGlobal 中的 addView() 干啥了吧。
public void addView(View view, ViewGroup.LayoutParams params, Display display , Window parentWindow) {
// 1. 检查参数的合法性
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;
//如果有设置父窗口,会通过 adjustLayoutParamsForSubWindow 来调整 params 。
if (parentWindow != null) {
//调整布局参数,并设置token
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
...
}
...
//创建 ViewRootImpl ,并且将 view 与之绑定
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);//将当前 view 添加到 mViews 集合中
mRoots.add(root);//将当前 ViewRootImpl 添加到 mRoots 集合中
mParams.add(wparams);//将当前 window 的 params 添加到 mParams 集合中
//通过 ViewRootImpl 的 setView 方法,完成 view 的绘制流程,并添加到 window 上。
root.setView(view, wparams , panelParentView);
...
}
通过代码我们可知,主要做了以下四件事:
- 检查参数是否合法
- 如果是子 View ,调整布局参数
- 创建 ViewRootImpl ,并将 View 添加到列表中。从这里也能看出,一个 View ,对应一个 ViewRootImpl ,对应一个 LayoutParams 。
这里面有几个集合,需要关注一下
/* 存储所有 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 方法,但是删除操作还未完成的的 Window 对象*/ private final ArraySet<View> mDyingViews = new ArraySet<View>();
- 通过 ViewRootImpl 来更新并完成 Window 的添加过程
那么接下来我们看看 怎么通过 ViewRootImpl 的 setView() 完成 View 的添加工作
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();//View的绘制流程
...
res = mWindowSession.addToDisplay(mWindow, mSeq , mWindowAttributes , getHostVisibility()
, mDisplay.getDisplayId(), mAttachInfo.mContentInsets
, mAttachInfo.mStableInsets, mInputChannel);
...
}
主要工作
- 执行 View 的绘制流程 。
- 通过 WindowSession 来最终完成 Window 的添加过程
在之前文章 View 的绘制 - Draw 流程, invalidate 的流程 以及 requestLayout 流程 中讲过。
setView()-> requestLayout() ->scheduleTraversals()。
scheduleTraversals() 才是 View 绘制的入口。这里就不再细说
主要说说 WindowSession 的 addToDisplay()
我们先了解一下 mWindowSession 。
- mWindowSession 类型 IWindowSession ,是一个 Binder 对象,
- 在 ViewRootImpl 创建的时候,会创建这个 mWindowSession 对象。用来与 WMS 建立连接
// ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mWindowSession = WindowManagerGlobal.getWindowSession();
}
//WindowManagerGlobal.java
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
//创建 InputMethodManager 实例
InputMethodManager imm = InputMethodManager.getInstance();
//获取WMS
IWindowManager windowManager = getWindowManagerService();
//与 WMS 建立一个Session
sWindowSession = windowManager.openSession(new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
}, imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
// WindowManagerService.java
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client
,IInputContext inputContext) {
if (client == null) throw new IllegalArgumentException("null client");
if (inputContext == null) throw new IllegalArgumentException("null inputContext");
Session session = new Session(this, callback , client , inputContext);
return session;
}
//Session.java
public Session(WindowManagerService service, IWindowSessionCallback callback
, IInputMethodClient client , IInputContext inputContext) {
mService = service;
...
}
在 WMS 的 openSession() 中,创建一个 Session ,并且把 WMS 实例保存到 mService 中。并且返回这个 Session 对象。
也就是说 mWindowSession 正真的实现类是 Session 。
那么我们就看看 Session 中的 addToDisplay() 做了啥吧
//Session.java
@Override
public int addToDisplay(IWindow window, int seq , WindowManager.LayoutParams attrs,
int viewVisibility , int displayId , Rect outContentInsets , Rect outStableInsets ,
Rect outOutsets , InputChannel outInputChannel) {
//通过 WindowManagerService 来实现 Window 的添加
return mService.addWindow(this, window , seq , attrs , viewVisibility , displayId ,
outContentInsets , outStableInsets , outOutsets , outInputChannel);
}
这个 mService 前面说了,就是 WMS ,调用 WMS 的 addWindow() 来实现 Window 的添加。这也是一个 IPC 调用。至于 WMS 怎么 addWindow() 了,下次再分析吧。
整个流程图如下
Window 的删除过程
同 addView() 过程一样,桥接到 WindowManagerGlobal 中,执行 removeView() 方法实现的
在 WindowManager 提供两种删除接口:
- removeView() 异步删除
- removeViewImmdiate()同步删除,一般不需要此方法删除 Window ,以免发生意外
但是不管哪种,最后都执行到了 WindowManagerGlobal 的 removeView() 中,使用变量 immediate 表示是否为同步删除
//WindowManagerGlobal.java
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 的索引
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 的索引,然后调用 removeViewLockde() 删除。查找的逻辑就不说了。直接看删除吧
//WindowManagerGlobal.java
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());
}
}
//通过 ViewRootImpl 来完成删除操作
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
同样是通过 ViewRootImpl 来实现删除的。那么我们就看看 ViewRootImpl 的 die() 方法把
boolean die(boolean immediate) {
//如果是立即删除的话,直接执行doDie()
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() 方法只发送一个请求删除的消息后就返回了。这个时候 View 并没有删除所以最后把这个要删除的 View 添加到 mDyingView 中 最终执行 doDie 方法,
void doDie() {
...
if (mAdded) {
// 在 doDie 中,会通过 dispatchDetachedFromWindow ,通知 View 树,窗口已经移除了
dispatchDetachedFromWindow();
}
...
//刷新数据,包括 mRoots , mParams ,以及 mDyingViews ,需要将当前 Window 所关联的对象从列表中删除
WindowManagerGlobal.getInstance().doRemoveView(this);
}
在 doDie() 中真正删除的逻辑在 dispatchDetachedFromWindow() 方法的内部实现
删除成功后,刷新数据,包括 mRoots , mParams ,以及 mDyingViews ,需要将当前 Window 所关联的对象从列表中移除
那么我们就看看这个 dispatchDetachedFromWindow() 吧
void dispatchDetachedFromWindow() {
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
//内部会调用 onDetachedFromWindow 和 onDetachedFromWindowInternal ,
//当 View 从 Window 移除的时候,会执行 onDetachedFromWindow ,
//可以在内部做一些资源回收的工作,例如终止动画,线程停止等
mView.dispatchDetachedFromWindow();
}
//垃圾回收工作,比如清除数据和信息,移除毁掉
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mAccessibilityManager.removeHighTextContrastStateChangeListener(
mHighContrastTextManager);
removeSendWindowContentChangedCallback();
destroyHardwareRenderer();
setAccessibilityFocus(null, null);
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
mSurface.release();
if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
}
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
try {
//通过 Session 的 remove 方法删除 Window ,同样是一个 IPC 过程,最终调用 WMS 的 removeWindow 方法
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
...
}
看到没,也是一个 IPC 过程。通过 Session 调用 remove() ,最终执行 WMS 的 removeWindow() 来完成删除操作的。
整个流程图如下
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 的LayoutParams
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
//更新 ViewRootImpl 的LayoutParams
root.setLayoutParams(wparams, false);
}
}
- 检查参数合法性
- 更新 ViewRootImpl 的 LayoutParams ,通过 setLayoutParams() 实现,内部会执行 scheduleTraversals() 对 ViewRootImpl 重新布局,包括测量,布局,重绘,整个流程入下
setLayoutParams()->scheduleTraversals()->切换到主线程 mTraversalRunnable run() -> doTraversal()-> performTraversals()->relayoutWindow() ->mWindowSession.relayout()->Session.relayout->WMS.relayoutWindow()
通过 WindowSession 更新 Window 的视图,这个过程最终交个 WMS 的 relayoutWindow() 来实现
搬运地址:
Android 开发艺术探索
既已览卷至此,何不品评一二: