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