Android中Activity销毁后,页面中的dialog去哪了

Android中Activity销毁后,页面中的dialog去哪了

问题的起源

在解决客户端中内存泄漏时,看到历史代码中有直接创建dialog并调用dialog.show()的代码存在,这让我对此产生了疑问:dialog会泄露吗?Activity销毁时他去哪里了?

来吧,debug试一下。
简单粗暴的测试代码(Activity中):

  val builder = AlertDialog.Builder(this)
 		 //五秒后开启弹窗
        Handler().postDelayed(5000) {
             builder.show()
        }
         //十秒后关闭Activity
        Handler().postDelayed(10000) {
            finish()
        }

模拟器一走,5秒后,弹窗展示正常。可是10秒后,断点压根不经过!(正常back键是可以走到此处的)
debug截图
但是dialog明明已经不见了,它去哪了呢?
一下午的功夫,翻起了源码

追根溯源

Dialog创建过程

dialog创建时,会同步创建一个window,用于dialog的视图管理。

  Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
      ...
	//获取WindowManager
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
	//创建PhoneWindow
        final Window w = new PhoneWindow(mContext);
     ...
    }

Dialog的展示过程

//核心代码
 public void show() {
   ...
 	mDecor = mWindow.getDecorView();
        mWindowManager.addView(mDecor, l);
     ...

        mShowing = true;

        sendShowMessage();
    }

可以看到,dialog展示过程,实际上是使用WindowManager.addview的过程
那么addView里面具体是做了什么呢?

//WindowManagerImpl
 @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

可以看到,WindowManagerImpl的具体实现还是mGlobal去执行的。
ps:mGlobal是WindowManagerGlobal的单例
继续跟

//WindowManagerGlobal
 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
       
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
          
        }
...
        ViewRootImpl root;
 ...
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
		//被添加到了mViews中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

        }
    }

代码中可以看到,dialog的decorView被添加到了WindowManagerGlobal的mViews中,并创建了ViewRootImpl,用于管理view的绘制过程
既然添加是在这里,那移除肯定也在WindowManagerGlobal中了。
带着信心看mRoot跟mViews修改的地方,看到了一个可疑函数:

void doRemoveView(ViewRootImpl root) {
        boolean allViewsRemoved;
        synchronized (mLock) {
            final int index = mRoots.indexOf(root);
            if (index >= 0) {
                mRoots.remove(index);
                mParams.remove(index);
                final View view = mViews.remove(index);
                mDyingViews.remove(view);
            }
            allViewsRemoved = mRoots.isEmpty();
        }
     ...
    }

直觉告诉我,这个地方肯定是dialog消失不见的元凶!
打个断点在这,跑一下试试。

a fewer minutes later

果不其然断点走到了这里两次,经过查看hash值确定,第一次是Activity的移除过程,第二次就是dialog的移除过程。直接上堆栈:
在这里插入图片描述
沿着堆栈,可以看到调用是来自于handler,跟踪message的what,找到了调用位置,是ViewRootImpl中die方法。
在这里插入图片描述
继续追源
在这里插入图片描述
原来在Activity结束的时候确实有涉及到dialog的操作。那我们看看都做了什么呢
在这里插入图片描述
这里明确说明,此时已经泄露了。不妨碍我们继续弄清真相,继续到WindowManagerGlobal中看代码:
在这里插入图片描述
总算是找到了!系统明确说明,这里泄露了!!
好奇心还是在作祟,ViewRootImpl是怎么找到的呢?token是怎么来的?
时间原因,直接说结论了,至于过程,大家有兴趣可以自行看看源码。

结论

  • 关于WindowManager。
    Activity重写了获取WindowManager的方法,使得通过Activity的context获取到的windowManager都是同一个。因此,Dialog的windowManager跟Activity的windowManager都是同一个。同一个WindowManager意味着在添加View的时候,token是同一个。区分开window不是同一个,window用于装载不同的视图。与此同时,decerView也不是同一个。
  • 关于token。
    Activity创建的时候ActivityThread会给他发放一个token,dialog在添加到window的时候,默认是没有token的,但是他有parentWindow,所以将parentWindow的token赋给了dialog的Window。token是管理视图的凭证
  • 关于移除过程。
    Activity结束时handleDestroyActivity,首先调用立即removeViewImmediate,把自身decorView移除。
    同时会通过DecerView的token,调用WindowManagerGlobal的closeAll,异步查找token对应的ViewRoot,并执行移除–这时候已经是异步移除,已经泄露了。
    再然后会通过当前Activity的token去调用gloabalemanage的closeAll(),会传递一个token过去,异步查找token对应的ViewRoot,并执行移除–这时候已经是异步移除,已经泄露了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值