Activity has leaked window that was originally added

Activity has leaked window that was originally added

问题日志

相信很多同学都遇到过这个问题window leak。日志如下

E: android.view.WindowLeaked: Activity me.zhangls.rxjava2sampledemo.MainActivity has leaked window com.android.internal.policy.PhoneWindow$DecorView{38d91f0 V.E...... R.....I. 0,0-1026,476} that was originally added here
       at android.view.ViewRootImpl.<init>(ViewRootImpl.java:471)
       at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:309)
       at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
       at android.app.Dialog.show(Dialog.java:325)
       at android.support.v7.app.AlertDialog$Builder.show(AlertDialog.java:953)
       at me.zhangls.rxjava2sampledemo.MainActivity.onCreate(MainActivity.java:29)
       at android.app.Activity.performCreate(Activity.java:6293)
       at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1113)
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2603)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2720)
       at android.app.ActivityThread.-wrap12(ActivityThread.java)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1567)
       at android.os.Handler.dispatchMessage(Handler.java:111)
       at android.os.Looper.loop(Looper.java:207)
       at android.app.ActivityThread.main(ActivityThread.java:5917)
       at java.lang.reflect.Method.invoke(Native Method)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:679)

错误做法

咋一看日志,和我们的代码相关的就是dialog.show这一步。所以很多同学会这样做:

if(!isFinishing() && !isDestroyed()){
    dialog.show();
}

原理分析

上面的做法是不正确的,具体原因我们接着说。找到出现那个日志那个代码,以及分析这个调用过程我们就会发现。是先回调Activity的onDestory,然后在打印window leak。

所以出现这个问题的原因是:当Activity销毁的时候,Window没有及时关闭。

1.ActivityThread.handleDestroyActivity

//ActivityThread.java

private void handleDestroyActivity(IBinder token, 
    boolean finishing, int configChanges, boolean getNonConfigInstance) {

    //这是关键方法,执行销毁Activity
    ActivityClientRecord r = performDestroyActivity(token, finishing,configChanges, getNonConfigInstance);
    if (r != null) {
        cleanUpPendingRemoveWindows(r);
        WindowManager wm = r.activity.getWindowManager();
        View v = r.activity.mDecor;
        if (v != null) {
            if (r.activity.mVisibleFromServer) {
                mNumVisibleActivities--;
            }
            IBinder wtoken = v.getWindowToken();
            if (r.activity.mWindowAdded) {
                if (r.onlyLocalRequest) {
                    // Hold off on removing this until the new activity's
                    // window is being added.
                    r.mPendingRemoveWindow = v;
                    r.mPendingRemoveWindowManager = wm;
                } else {
                    wm.removeViewImmediate(v);
                }
            }
            if (wtoken != null && r.mPendingRemoveWindow == null) {
                WindowManagerGlobal.getInstance().closeAll(wtoken,
                        r.activity.getClass().getName(), "Activity");
            }
            r.activity.mDecor = null;
        }
        if (r.mPendingRemoveWindow == null) {
            // If we are delaying the removal of the activity window, then
            // we can't clean up all windows here.  Note that we can't do
            // so later either, which means any windows that aren't closed
            // by the app will leak.  Well we try to warning them a lot
            // about leaking windows, because that is a bug, so if they are
            // using this recreate facility then they get to live with leaks.
            WindowManagerGlobal.getInstance().closeAll(token,
                    r.activity.getClass().getName(), "Activity");
        }

        // Mocked out contexts won't be participating in the normal
        // process lifecycle, but if we're running with a proper
        // ApplicationContext we need to have it tear down things
        // cleanly.
        Context c = r.activity.getBaseContext();
        if (c instanceof ContextImpl) {
            ((ContextImpl) c).scheduleFinalCleanup(
                    r.activity.getClass().getName(), "Activity");
        }
    }
    if (finishing) {
        try {
            ActivityManagerNative.getDefault().activityDestroyed(token);
        } catch (RemoteException ex) {
            // If the system process has died, it's game over for everyone.
        }
    }
    mSomeActivitiesChanged = true;
}

2.WindowManagerGlobal.getInstance

//WindowManagerGlobal.java

public static WindowManagerGlobal getInstance() {
    synchronized (WindowManagerGlobal.class) {
        if (sDefaultWindowManager == null) {
            sDefaultWindowManager = new WindowManagerGlobal();
        }
        return sDefaultWindowManager;
    }
}

3.WindowManagerGlobal.closeAll

//WindowManagerGlobal.java

public void closeAll(IBinder token, String who, String what) {
    synchronized (mLock) {
        int count = mViews.size();
        //Log.i("foo", "Closing all windows of " + token);
        for (int i = 0; i < count; i++) {
            //Log.i("foo", "@ " + i + " token " + mParams[i].token
            //        + " view " + mRoots[i].getView());
            if (token == null || mParams.get(i).token == token) {
                ViewRootImpl root = mRoots.get(i);

                //Log.i("foo", "Force closing " + root);
                if (who != null) {
                    WindowLeaked leak = new WindowLeaked(
                            what + " " + who + " has leaked window "
                            + root.getView() + " that was originally added here");
                    leak.setStackTrace(root.getLocation().getStackTrace());
                    Log.e(TAG, "", leak);
                }

                removeViewLocked(i, false);
            }
        }
    }
}

正确做法

正确做法,当然是这样啦

@Override
protected void onDestroy() {
    super.onDestroy();
    if (dialog != null) {
        dialog.cancel();
        dialog = null;
    }
}

运行测试

我说的对不对呢?其实一开始我自己也不十分相信。实践是检验真理的唯一标准,还好找到一段可以出现上述问题的代码。这段代码会让程序卡死,不过这不是重点,我们只要看看还会不会答应那个window leak的日志就好了。

dialog = new AlertDialog.Builder(this)
        .setTitle("这是标题")
        .setMessage("这是内容")
        .show();

Intent myIntent = new Intent(this, MainActivity.class);
myIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
        Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(myIntent);
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值