android.view.WindowManager$BadTokenException: ... is your activity running? いじょうもんだい

2584 ワード

これは、activityのviewがレンダリングされていないため、このViewに関連するメソッドが呼び出され、次のような異常が発生します.
After publishing one of our apps on Google Play market I started receiving strange exception on Play Console:

android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@40b47bd8 is not valid; is your activity running?
at android.view.ViewRoot.setView(ViewRoot.java:452)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:283)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:193)
at android.view.WindowManagerImpl$CompatModeWrapper.addView(WindowManagerImpl.java:118)
at android.view.Window$LocalWindowManager.addView(Window.java:532)
at android.app.Dialog.show(Dialog.java:269)
...
This exception occurred while app was trying to notify user from the background thread by opening a Dialog.

I could not reproduce it myself: I tried every device I had and every type of emulator I could think of, but no luck, the app worked just fine. However, according to Google Play console, exception occurred very often and seemed very consistent.

After doing some research I found out that it is possible for my app to try notifying the user while being in the background (device screen is locked, app is sent to background with home button).

So I looked at Android docs on Activity and discovered a very useful method – isFinishing() which is called by Android when Activity enters finishing stage: be it explicit finish() call or activity clean up made by Android.

Using that flag it is very easy to avoid opening dialog from background thread when Activity is finishing:

runOnUiThread(new Runnable() {
   @Override
   public void run() {
	if(!isFinishing()){
		showDialog (
			        new AlertDialog.Builder(MainActivity.this)
				.setTitle(R.string.dialogTitle)
				.setMessage(R.string.dialogText)
				.setCancelable(false)
				.setPositiveButton(R.string.txtOk, 
				new OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
                                          // whatever...						
					}
				})
				.create()
			     );
	   }
   }
});
 

As you can see – the fix is very simple and straightforward: just check if your Activity is going to finish before opening dialog.

または、
new Handler().postDelayed(new Runnable() {
    public void run() {
        //  
    }
}, 100);

または、
findViewById(R.id.main_page_layout).post(new Runnable() {
   public void run() {
     //  
   }
});