从老生常谈的WindowManager到手写一个Alert弹窗控件

Window & WindowManager

从老生常谈的WindowManager到手写一个Alert弹窗控件

Window的添加与删除

每一个Window都对应着一个View和一个ViewRootImpl。Window和View通过ViewRootImpl来建立联系。

ViewRootImpl是Window的实际展现形式,因为WindowManager操作的都是View,而ViewRootImpl是继承的ViewParent

通过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本身上了。

删除过程

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);
 }
复制代码
Window与Activity

Activity的启动过程很复杂,最终会由ActivityThread中的performLaunchActivity()来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用其attach方法为其关联运行过程中所依赖的一系列上下文环境变量

Activityattach()中,新建一个Window实例作为自己的成员变量,它的类型为PhoneWindow,这是抽象类Window的一个子类。然后设置mWindowWindowManager

Activity的布局是如何加载到Window上的

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));

DecorView添加到WIndowManager上

在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);
    }

复制代码
不同的Activity对应的WindowManager是否相同
  • 相同WindowManager不同window
  • 他们所依赖的WindowManager,是通过context.getSystemService()方法获取的,所以不管有多少Activity,Dialog,WindowManager的"实例"只有一个
  • WindowManager的实现类是WindowManagerImpl,而WindowManagerImpl中的addView,removeView等方法,又是直接调用了WindowManagerGlobal的相应方法。
  • WindowManagerImpl中依赖的WindowManagerGlobal也是单例模式创建的,所以app范围内,WindowManagerGlobal实例也只有一个
  • 于是就解释了,不同的WindowManager为什么可以移除对方的View
Window 层级关系

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上面即可

为什么不把View的层级设置的高一点呢?
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的透明度

Window Leak

这里的处理方式是将传入的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) // 移除监听事件
}
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值