##1、背景##
Window表示一个窗口的概念,我们平时直接接触Window的机会并不多,但是在某些特殊需求中比如在桌面上显示一个悬浮的图标时,就需要Window来实现。
Window是一个抽象类,其具体实现类是PhoneWindow。创建一个Window是很简单的事情,只需通过WindowManager即可完成,WindowManager是外界访问Window的入口,而Window的具体实现是在WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。
Android中所有的视图都是通过Window来呈现的,无论是Activity,Dialog还是Toast,它们的视图实际都是附加在Window上的,因此Window实际是View的直接管理者。需要具体了解Android是如何将视图添加到界面上的,可以参考一下另外一篇文章SetContentView与LayoutInflater源码分析。
##2、Window简介##
为了分析Window的工作机制,我们先来了解一下WindowManager如何添加一个Window,下面我们给个示例。
mButton = new Button(this);
mButton.setText("button");
wManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
mParams = new WindowManager.LayoutParams();
mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;// 系统提示window
mParams.format = PixelFormat.TRANSLUCENT;// 支持透明
mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;// 焦点
mParams.width = 490;//窗口的宽和高
mParams.x = 100;//窗口位置的偏移量
mParams.y = 200;
wManager.addView(mButton,mParams);
以上就是将一个Button添加到屏幕坐标(100,200)的位置,而其中的flag和type是两个比较重要的参数。
Flags表示Window的属性值,有很多选项具体可以参考官方文档。
Type表示Window的类型,具体有三种应用Window、子Window、系统Window。其中应用Window对应Activity,子Window不能单独存在需要依附在特殊父Window中(比如常见的Dialog),而系统Window需要声明权限才能创建Window(比如Toast和系统状态栏)。
Window是分层的,层级大的会覆盖在层级小的上面。在三类Window中应用Window的层级范围是1-99,子Window的层级范围是1000-1999,而系统Window的层级范围是2000-2999,这些层级对应着WindowManager.LayoutParams的type属性值。
WindowManager所提供的功能很简单,常用的就三个方法,即addView、removeView、updateView,如果大家对此不是很熟悉的请先于阅读一下我的另外一篇文章WindowManagerService和应用程序的IPC过程。
从这三个方法来看,WindowManager操作Window的过程更像是在操作Window中的View,其实我们常见的通过手指拖动Window的效果也很好实现,只需根据手指的位置来设定LayoutParams中的x和y的值即可改变Window的位置,当然需要给View设定OnTouchListener监听。
public boolean onTouch(View v,MotionEvent event){
int rawX = (int)event.getRawX();
int rawY = (int)event.getRawY();
switch(event.getAction()){
case MotionEvent.ACTION_MOVE:{
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mButton,mLayoutParams);
break;
}
}
return false;
}
##3、Window的操作方法##
之前也提到过操作Window的三个方法即addView,removeView,updateView,为了分析Window的内部机制,我们就从这三个方法入手。
1、Window的addView过程
Window的添加过程需要通过WindowManager的addView来实现,而WindowManager是一个接口,其真正实现者是WindowManagerImpl类,我们进入该类查看这三个方法。
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
public void removeView(View view) {
mGlobal.removeView(view, false);
}
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
可以发现这三个方法都将操作转交给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");
}
//创建ViewRootImpl并将其添加到列表中
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//这里紧接着就调用ViewRootImpl的setView方法来更新Window的添加过程
root.setView(view, wparams, panelParentView);
}
这里我们来看一下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对象
private final ArraySet<View> mDyingViews = new ArraySet<View>();
我们紧接着addView方法的最后通过调用setView方法来完成界面刷新,进入该方法。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
//......
// 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();
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//通过WindowSession完成Window的添加过程,mWindowSession的类型是IWindowSession,是一个Binder对象,真正的实现着是Session
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
}
在setView方法中通过requestLayout来完成异步刷新请求,并最终调用Session的addToDisplay方法,我们进入该方法。
final class Session extends IWindowSession.Stub
implements IBinder.DeathRecipient {
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,...) {
//这里将操作转给WMS的addWindow方法
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,outContentInsets, outStableInsets, outInputChannel);
}
}
好啦,到此Window的添加请求就交给了WMS进行处理了,具体WMS内部是如何添加的我们不去深究。
2、Window的删除过程
Window的删除过程和添加一样,最终都是通过WindowManagerGlobal来实现的,我们来看一下其源码。
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
//找到需要删除View的index
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
//调用removeViewLocked进行删除
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) {
//通过索引index获得需要删除View的ViewRootImpl对象
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对象的die方法,immediate表示同步删除还是异步删除
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
//由于die并没有立即删除View,所以需要将其添加到mDyingViews列表中,表示待删除View
mDyingViews.add(view);
}
}
}
通过以上分析我们知道,Window的删除操作具体由ViewRootImpl的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.
//如果是同步删除将直接调用doDie()方法
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
//如果是异步删除将通过Handler发送消息然后在调用doDie()方法
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
无论是同步还是异步删除,其最终都是调用doDie方法,我们直接分析该方法。
void doDie() {
dispatchDetachedFromWindow();
//.....
mWindowSession.finishDrawing(mWindow);
//.....
WindowManagerGlobal.getInstance().doRemoveView(this);
}
在doDie内部会调用dispatchDetachedFromWindow方法,真正的删除操作都在dispatchDetachedFromWindow方法内部实现,并在最后调用WindowManagerGlobal的doRemoveView方法来刷新数据,包括mRoots、mParams、mDyingViews,将当前Window从这三个列表中删除。
dispatchDetachedFromWindow()方法我们就不分析源码,其主要做了以下几件事:
- 垃圾回收,比如清除数据移除回调等;
- 通过Session的remove删除Window:mWindowSession.remove(mWindow)这是一个IPC过程,其最终调用WMS的removeWindow方法;
- 调用View的dispatchDetachedFromWindow()方法,该方法在View内部调用onDetachedFromWindow和onDetachedFromWindowInternal方法,对于onDetachedFromWindow相信大家都已有所了解,当View从Window中移除时,这个方法就会被调用,可以在该方法内部做一些回收的工作;
3、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的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);
}
}
从以上代码我们不难发现,Window的更新操作还是比较简单的,首先更新View的LayoutParams,然后在更新ViewRootImpl的LayoutParams,具体当中的细节我就不一一分析啦。
好啦,到此对Window的添加、删除、更新等操作就分析完毕啦,下一篇我们将分析几种类型的Window的创建过程(Activity,Dialog,Toast)理解Window和WindowManager(二)。