8. windowsManager
Window是 一个抽象类,它的具体实现是PhoneWindow。创建一个Window是很简单的事,只需要通过WindowManager即可完成
。WindowManager是外界访问Window的入口,Window的具体实现位于 WindowManagerService中,WindowManager和Window-ManagerService的交互是一个IPC过程。Android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在 Window上的,因此Window实际是View的直接管理者
。
mLayoutParams = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0,
PixelFormat.TRANSPARENT);
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR ;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
//显示位置
mLayoutParams.x = 100;
mLayoutParams.y = 300;
//支持半透明
mLayoutParams.format = PixelFormat.TRANSLUCENT
//使用系统动画
mLayoutParams.windowAnimations = android.R.style.Animation_Toast
mFloatingButton.setOnTouchListener(this);
mWindowManager.addView(mFloatingButton, mLayoutParams);
8.1 WindowManager.LayoutParams参数
- Flags参数表示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对应着一个Activity。
- 子Window不能单独存在,它需要附属在特定的父Window之中,比如常见的一些Dialog就是一个子Window。
- 系统Window是需要声明权限在能创建的Window,比如Toast和系统状态栏这些都是系统Window。
Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window的上面。
在三类Window中,
应用Window的层级范围是1~99,
子 Window的层级范围是1000~1999,
系统Window的层级范围是2000~2999,
这些层级范围对应着WindowManager.LayoutParams的type参数一般我们可以选用TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR,
//Start of system-specific window types. These are not normally created by applications.
public static final int FIRST_SYSTEM_WINDOW = 2000;
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
8.1.1 权限
如果采用TYPE_SYSTEM_ERROR,只需要为type参数指定 这个层级即可:mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;同时声明权限:<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
。因为系统类型的Window是需要 检查权限的,如果不在AndroidManifest中使用相应的权限,那么创建Window的时候就会报错。
Caused by: android.view.WindowManager$BadToken- Exception: Unable to add window android.view.ViewRootImpl$W@42882fe8 -- permission denied for this window type
WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,
public interface ViewManager {
public void addView(View view,ViewGroup.LayoutParams params);
public void updateViewLayout(View view,ViewGroup.LayoutParams params);
public void removeView(View view);
}
8.1.2 拖动的Window效果
拖动的Window效果,其实是很好实现的,只需要根据手 指的位置来设定LayoutParams中的x和y的值即可改变Window的位置。首先给View设置onTouchListener:mFloatingButton.setOnTouchListener(this)。
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()) {
...
case MotionEvent.ACTION_MOVE: {
int x = (int) event.getX();
int y = (int) event.getY();
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);
break;
}
...
return false;
}
8.2 Window的的内部机制
WindowManager继承ViewManager,提供的三个接口方法addView、updateViewLayout以及removeView都是针对View的,这说明View才是Window存在的实体。在实际使用中无法直接访问Window,对 Window的访问必须通过WindowManager。
8.2.1 Window的的添加过程
Window的添加过程需要通过WindowManager的addView来实现,WindowManager是一个接口,它的真正实现是WindowManagerImpl类。在WindowManagerImpl中Window的三大操作
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
@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);
}
Window的添加过程需要通过WindowManager的addView来实现,WindowManager是一个接口,它的真正实现是WindowManagerImpl,WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal来处理。
- 检查参数是否合法
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添加到列表里
@UnsupportedAppUsage
private final ArrayList<View> mViews = new ArrayList<View>();
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
@UnsupportedAppUsage
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对象。
root = new ViewRootImpl(view.getContext(),display); view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
- 通过ViewRootImpl来更新界面并完成window添加过程
View的绘制过程是由ViewRootImpl来完成的, 在setView内部会通过requestLayout来完成异步刷新请求。在下面的代码中,scheduleTraversals实际是View绘制的入口:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
接着会通过WindowSession最终来完成Window的添加过程。在下面的代码中,mWindowSession的类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,也就是Window的添加过程是一次IPC调用
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow,mSeq,mWindowAttributes,
getHostVisibility(),mDisplay.getDisplayId(),mAttachInfo.mContentInsets,mInputChannel);
在Session内部会通过WindowManagerService来实现Window的添加
public int addToDisplay(IWindow window,int seq,WindowManager.LayoutParams
attrs,int viewVisibility,int displayId,Rect outContentInsets,InputChannel outInputChannel) {
return mService.addWindow(this,window,seq,attrs,viewVisibility,displayId,
outContentInsets,outInputChannel);
}
Window的添加请求就交给WindowManagerService去处理了,在WindowManagerService内部会为每一个应用保留一个单独的Session
8.2 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中提供了两种删除接口removeView和removeViewImmediate,它们分别表示异步删除和同步删除,其中removeViewImmediate使用起来需要特别注意,一般来说不需要使用此方法来删除Window以免发生意外的错误。这里主要说异步删除的情况,具体的删除操作由ViewRoot-Impl的die方法来完成。在异步删除的情况下,die方法只是发送了一个请求删除的消息后就立刻返回了,这个时候View并没有完成删除操作,所以最后会将其添加到mDyingViews中,mDyingViews表示待删除的View列表
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(TAG,"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方法,这就是这两种删除方式的区别。在doDie内部会调用dispatchDetachedFromWindow方法,真正删除View的逻辑在dispatchDetachedFromWindow方法的内部实现。dispatchDetachedFromWindow
方法主要做四件事:
(1)垃圾回收相关的工作,比如清除数据和消息、移除回调。
(2)通过Session的remove方法删除Window:mWindowSession.remove(mWindow),这同样是一个IPC过程,最终会调用WindowManagerService的removeWindow方法。
(3)调用View的dispatchDetachedFromWindow方法,在内部会调用View的onDetached-FromWindow()以及onDetachedFromWindowInternal()。对于onDetachedFromWindow()大家一定不陌生,当View从Window
中移除时,这个方法就会被调用,可以在这个方法内部做一些资源回收的工作,比如终止动画、停止线程等。
(4)调用WindowManagerGlobal的doRemoveView方法刷新数据,包括mRoots、mParams以及mDyingViews,需要将当前Window所关联的这三类对象从列表中删除。
8.3 Window的更新过程
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.Layout-Params)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);
}
}