每一个Window都对应着一个View和一个ViewRootImpl。Window和View通过ViewRootImpl来建立联系(ViewRootImpl是Window的实际展现形式,它是视图层级结构的最顶部。)
通过WindowManager的addView来实现在屏幕上添加一个View,但它只是一个接口,真正的实现是WindowManagerImpl
WindowManagerImpl这种工作模式是典型的桥接模式,将所有的操作全部委托给WindowManagerGlobal这个单例类来实现,而其内部则是通过ViewRootImpl来更新界面并完成Window的添加过程。
WindowManagerGlobal.java/addView
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
root.setView(view, wparams, panelParentView);
这里的这个root,是新创建出的ViewRootImpl(view.getContext(), display);
前面说了,它其实是Window的实际展现形式,暂且理解为window本身,这样我们将view添加到window本身上了。
真正的调用顺序:windowManager -> WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl
WindowManagerGlobal.java
removeView(View view, boolean immediate)
它有一个immediate参数表示是否是立即移除,其中removeView是异步方法removeViewImmediate为同步方法。方法内部拿到了view的index之后去ArrayList<ViewRootImpl> mRoots
中寻找对应的Index。然后调用removeViewLocked(index, immediate);
此时将View添加到了ArraySet<View> mDyingViews
中去,并没有立即执行删除,而是发送一个消息
boolean die(boolean immediate) {
...
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
mHandler.sendEmptyMessage(MSG_DIE);
}
如果是同步删除,会直接移除,并刷新mRoots等属性列表
此时我们就会收到onDetachedFromWindow()
这个方法的调用。
void doDie() {
...
if (mAdded) {
dispatchDetachedFromWindow();
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
Activity的启动过程很复杂,最终会由ActivityThread中的performLaunchActivity()
来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用其attach
方法为其关联运行过程中所依赖的一系列上下文环境变量
在Activityattach()
中,新建一个Window
实例作为自己的成员变量,它的类型为PhoneWindow
,这是抽象类Window
的一个子类。然后设置mWindow
的WindowManager
。
Activity中的window就是phoneWindow,而Activity中的setContentView就是调用的phoneWindow的setContentView。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow.java/setContentView
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
PhoneWindow中 包含一个DecorView,
如果为null就新建一个,通过installDecor()
,内部调用generateDecor(-1);
创建mDecor。
如果mDecor不为null那么直接关联上phoneWindowmDecor.setWindow(this);
拿到了mDecor后去生成布局mContentParent = generateLayout(mDecor);
在generateLayout
中,将Activity的布局addView到decorView上mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
onResourcesLoaded
内部实现最终的addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
在ActivityThread的handleResumeActivity方法中,首先会调用Activity的onResume方法,接着会调用Activity的makeVisible()
只有在Activity的makeVisible()
被调用的时候,DecorView才会被addview进Windowmanager
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
- 不同的
WindowManager
对象,相同的WindowManagerGlobal
WindowManager
只是一个interface
,它真正的对象是WindowManagerImpl
,而WindowManagerImpl
中的addView,removeView等方法,又是直接调用了WindowManagerGlobal
的相应方法。WindowManagerImpl
中依赖的WindowManagerGlobal
也是单例模式创建的,所以app范围内,WindowManagerGlobal
实例也只有一个
Dialog的弹窗需要Context,而这个Context必须是Activity,其内的Window包含一个token。如果没有这个Token就会报错。
“Caused by: android.view.WindowManager$BadToken-Exception: Unable to add window --token null is not for an application”
而系统级的Window就不需要token。因此我们可以
dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_OVERLAY)
来实现一个系统级别的Dialog。But运行时权限如果不给的话,就不会显示出来,显然这是不被接受的。
我们知道View在window上的显示是有层级顺序的,层级高的覆盖在层级低的View之上。
Activity的层级为 TYPE_APPLICATION
PopupWindow的层级为TYPE_APPLICATION_PANEL
Dialog的层级为TYPE_SYSTEM_OVERLAY
而我们只需要添加一个层级为TYPE_APPLICATION_SUB_PANEL
的View到Window上面即可
android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@308ebe3 -- permission denied for window type 2000
我们在这里验证了View的层级为2000以下的时候是不需要申请权限的
那么我们要实现这个WindowManager添加的View显示在Dialog之上,就只剩剩下一个东西了,就是降低Dialog的背景等级 或者 改为使用PopUpWindow来代替Dialog。
我采用的是,改Dialog的背景阴影为Activity的透明度
这里的处理方式是将传入的Activity绑定lifecycle,然后在onDestroy的时候,将Puddingdismiss
掉
Pudding类继承LifecycleObserver
class Pudding : LifecycleObserver
绑定生命周期,并且Activity的每个生命周期都可以通知到Pudding
activity.lifecycle.addObserver(this)
监听状态
// window manager must associate activity's lifecycle
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy(owner: LifecycleOwner) {
choco.hide(windowManager ?: return, true)
owner.lifecycle.removeObserver(this) // 移除监听事件
}