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的主要内容,如果未能解决你的问题,请参考以下文章

Android Activity has leaked window that was originally added

Activity has leaked window 原来是加了logcat错误

(原)android系统下绑定Server的时候报MainActivity has leaked ServiceConnection的错误

has leaked ServiceConnection com.baidu.location.LocationClient

修复A Native Collection has not been disposed, resulting in a memory leak.

Android 模拟器向 logcat 发送垃圾邮件,内容为“Service com.android.exchange.ExchangeService has leaked ServiceConnec