这几天阅读了《Android开发艺术探索》的关于Window和WindowManager的章节,特此写一片博文来整理和总结一下学到的知识,做一个笔记方便自己查阅。
说到Window,大家都会想到所有的视图,包括Activity,Dialog,Toast,它们实际上都是附加在Window上的,Window是这些视图的管理者.今天我们就来具体将一下这些视图是如何附加在Window上的,Window有是如何管理这些视图的.
1.Window的属性和类别
当我们通过WindowManager添加Window时,可以通过WindowManger.LayoutParams来确定Window的属性和类别.其中Flags参数标示Window的属性,我们列出几个比较常见的属性:
- FLAG_NOT_FOCUSABLE 这个参数表示Window不需要获取焦点,也不需要接收任何输入事件
- FLAG_NOT_TOUCH_MODAL 这个参数表示当前Window区域之外的点击事件传递给底层Window,区域之内的点击事件自己处理,一般默认开启
- FLAG_SHOW_WHEN_LOCKED 这个属性可以让Window显示在锁屏界面上
Window不仅有属性,还有类型.Type参数表示Window的类型,分别为应用Window(activity对应的),子window(dialog对应的),和系统Window(Toast和系统通知栏).Window是分层的,每个window都有z-ordered,层级大的window会覆盖层级小的window,其大小关系为系统window>子window>应用window.所以系统window总会显示在最上边,但是使用系统window是需要声明相应的权限的.这一点需要注意.具体Type参数表示Window的类型如下
- 应用 Window:层级范围 1-99,优先级最低;一个应用 Window 对应一个 Activity
- 子 Window:1000-1999,优先级中等;例如对应一个 Dialog 。
- 系统 Window:2000-2999,一般为 TYPE_SYSTEM__OVERLAY 或 TYPE_SYSTEM_ERROR ,需要声明 SYSTEM_ALERT_WINDOW 权限
2.WindowManager接口
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
- 每一个 Window 都对应着一个 View 和一个 ViewRootImpl
- Window 和 View 通过 ViewRootImpl 建立联系
- 无法直接操作 Window,只能通过 WidowManager
2.1 Window的添加过程
Button btn = new Button(this);
btn.setText("Button");
LayoutParams params = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL |
LayoutParams.FLAG_NOT_FOCUSABLE |
LayoutParams.FLAG_SHOW_WHEN_LOCKED;
params.gravity = Gravity.LEFT | Gravity.TOP;
params.x = 100;
params.y = 300;
windowManager.addView(btn, params);
看看WindowManagerImpl#addView
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
} private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();<span style="font-family: monospace;font-size:14px; white-space: pre;"></span>
接着看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 {
// If there's no parent and we're running on L or above (or in the
// system context), assume we want hardware acceleration.
final Context context = view.getContext();
if (context != null
&& context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
顺便看下这个类里面比较重要的成员变量
// 存储所有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,但是没有完成的
private final ArraySet<View> mDyingViews = new ArraySet<View>();
WindowManagerGlobal#addView按照书中精简一下代码就是
// 创建ViewRootImpl,然后将下述对象添加到列表中
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);//设置Params
mViews.add(view);//window列表添加
mRoots.add(root);//ViewRootImpl列表添加
mParams.add(wparams);//布局参数列表添加
最后添加要加入的View
// 通过ViewRootImpl的setView来完成
root.setView(view, wparams, panelParentView);
在ViewRootImpl的setView函数中,会调用requestLayout来完成异步刷新,然后在requestLayout中调用scheduleTraversals来进行view绘制.
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals(); // 实际View绘制的入口
}
}
最后通过WindowSession来完成Window的添加过程,它是一个Binder对象,通过IPC调用来添加window,sheduleTraversals()会调用到下面的方法
mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel);
所以,Window的添加请求就交给WindowManagerService去处理,在其内部为每个应用保留一个单独的Session.
2.2 Window的删除过程
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的index
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);
}
}
removeView先通过findViewLocked来查找待删除的View的索引,然后用removeViewLocked来做进一步删除.
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index); //获得当前的view的viewRootImpl
View view = root.getView();
if (view != null) { //先让imm下降
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate); //die方法只是发送一个请求删除的消息之后就就返回
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);//加入dyingView
}
}
}
WindowManagerGlobal 通过 ViewRootImpl 来完成删除操作,在WindowManager中提供了两种删除接口removeVIew()和removeViewImmediate(),它们分别表示异步和同步删除(不常用).而异步操作中会调用die函数,来发送一个MSG_DIE消息来异步删除,ViewRootImpl的Handler会调用doDie(),而如果是同步删除,那么就直接调用doDie(),然后在removeView函数中把View添加到mDyingViews中.
boolean die (boolean immediate) {
if (immediate && !mIsInTraversal) {
doDia();
return flase;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(TAG, "尝试摧毁正常绘制中的 Window");
}
// ViewRootImpl 的 mHandler 将处理此消息并调用 doDie
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
当 immediate 为 false,使用异步删除,就发送一个 MSG_DIE 的消息,ViewRootImpl 的 mHandler 将处理此消息并调用 doDie 方法;如果是同步删除,就是直接调用 doDie 方法;doDie 方法会调用 dispatchDetachedFromWindow 方法,内部真正实现了 View 的删除逻辑。
dispatchDetachedFromWindow 方法做了四步工作:
- 垃圾回收,例如清除数据、消息、移除回调
-
通过 Session 的 remove 方法删除 Window
mWindow.remove(mWindow)
WindowManagerService.removeWindow()
-
调用 View 的 dispatchDetachedFromWindow 方法,内部调用 View 的 onDetachedFromWindow、onDetachedFromInternal()
- 调用 WindowManagerGlobal 的 doRemoveView 方法刷新数据,包括 mRoots、mParams、mDyingViews
2.3 Window 的更新过程
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
.....
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,更新 ViewRootImpl 的 LayoutParams,内部调用 scheduleTraversals 对 view 重绘,通过 WindowSession 更新 Window 的视图,最终调用 WindowMService 的 relayoutWindow() 具体实现。
3.Window的创建
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
...
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
// 为 Activity 关联运行过程中所依赖的一系列上下文环境变量
activity.attch(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config, r.voiceInteractor);
}
在 Activity 的 attach 方法内部,系统为 Activity 创建所属 Window 对象并设置回调,注意此时并未与 WindowManager 关联,最终 onResume 时才会完成关联:
mWindow = PolicyManager.makeNewWindow(this);
// 当 Window 接收到外界状态改变时回调 Activity 的接口实现
// onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
PolicyManager 实现了 IPolicy 定义的四个方法:
public interface IPolicy {
public Window makeNewWindow(Context context);
public LayoutInflater makeNewLayoutInflater(Context context);
public WindowManagerPolicy makewNewWindowManager();
public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}
PolicyManager 的实现 Policy,makeNewWindow
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
Window对象是通过PolicyManager的makeNewWindow方法实现的,由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的对应方法.而我们去追寻Window的具体实现类,会发现它就是PhoneWindow,而Activity中最常用的setContentView方法的具体操作都是在PhoneWindow的相应方法中实现的.至于setContentView的具体流程请参考setContentView源码解析。