Activity has leaked window that was originally added

Posted ihrthk

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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$DecorView38d91f0 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);

以上是关于Activity has leaked window that was originally added的主要内容,如果未能解决你的问题,请参考以下文章