PopupWindow.showAtLocation() causes WindowManager$BadTokenException when using onSaveInstanceState() to handle device rotation

StackOverflow https://stackoverflow.com/questions/18119256

Question

I have been working on a straight-forward file manager application. Devices that I have been using for testing are LG Nexus 4 (v4.3) and Xperia x10i (v2.3.7). The performance on x10i, although a bit sluggish, has been problem-free.

On rotation:

There are several PopupWindows that I keep track of. I use flags to determine which PopupWindow is currently on screen (presently, at most one PopupWindow is on screen at any given time). In onSaveInstanceState(Bundle), I save these flags using the Bundle. In onCreate(Bundle), I retrieve these flags and use them in onPostExecute() of an AsyncTask(called in onResume() and used for populating the ListView with data).

The problem:

If a PopupWindow is showing when the device is rotated, the activity is destroyed, recreated, and the PopupWindow is displayed again. This works well on both the devices. But, today, I rotated x10i from 90 degrees to 270 degrees while the search popup was showing. The app crashed because of the following exception:

08-08 01:55:51.961: E/AndroidRuntime(32373): FATAL EXCEPTION: main
08-08 01:55:51.961: E/AndroidRuntime(32373): android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.view.ViewRoot.setView(ViewRoot.java:544)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.view.Window$LocalWindowManager.addView(Window.java:424)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.widget.PopupWindow.invokePopup(PopupWindow.java:907)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.widget.PopupWindow.showAtLocation(PopupWindow.java:767)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at com.apprehension.phylerfilemanager.Phyler.showPopupSearch(Phyler.java:2852)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at com.apprehension.phylerfilemanager.Phyler$DisplayFilesTask.onPostExecute(Phyler.java:3453)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at com.apprehension.phylerfilemanager.Phyler$DisplayFilesTask.onPostExecute(Phyler.java:1)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.os.AsyncTask.finish(AsyncTask.java:417)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.os.AsyncTask.access$300(AsyncTask.java:127)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:429)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.os.Handler.dispatchMessage(Handler.java:99)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.os.Looper.loop(Looper.java:123)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at android.app.ActivityThread.main(ActivityThread.java:3701)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at java.lang.reflect.Method.invokeNative(Native Method)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at java.lang.reflect.Method.invoke(Method.java:507)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:862)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:620)
08-08 01:55:51.961: E/AndroidRuntime(32373):    at dalvik.system.NativeStart.main(Native Method)

Line 2852:

popupWindowSearch.showAtLocation(popupViewSearch, Gravity.CENTER, 0, 0);    

If I rotate and pause on every 90 degrees, the problem is not there. The crash happens when the device undergoes 180 degree rotation without a pause.

Saving the flags:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);            

    if (searchPopup) {
        outState.putBoolean("searchPopup", searchPopup);
        outState.putString("searchKeyword", searchKeyword);
        outState.putInt("searchType", searchType);          
    }

    if (....) {
        ........
        ........
    }
}

Retrieving the flags in onCreate(Bundle):

if (savedInstanceState != null) {

    rotated = true;

    if (savedInstanceState.containsKey("searchPopup")) {
        searchPopup = true;
        searchKeyword = savedInstanceState.getString("searchKeyword");
        searchType = savedInstanceState.getInt("searchType");
    }

    ....
    ....
}

An AsyncTask is executed from onResume(). In onPostExecute() of this AsyncTask:

if (rotated) {
    rotated = false;
    if (searchPopup) {
        showPopupSearch(searchType, searchKeyword);     // Line 3453                        
    }
    ....
    ....
} else {
    searchPopup = false;
    ....
    ....
}

The exception is not thrown while testing on Nexus 4. I have also tried posting a Runnable to mContentView's (the activity's main view) message queue. The problem persists.

I think there is a problem in the way I am handling screen rotation. In apps that I have used, the screen rotation and layout changes happen smoothly. In my app's case, you can literally tell that the PopupWindow is being dismissed and recreated. Do most apps handle screen rotation using android:configChanges="keyboardHidden|orientation|screenSize"? I have read that this approach isn't correct.

Was it helpful?

Solution

What is most likely happening is that the x10i is doing two Activity instantiations. This is causing two AsyncTasks to run. The first one will end up having a reference to an instance of an Activity that, in the eyes of the framework and the window manager, no longer exists (or should exist), causing a null token and the resulting exception.

In your Activity#onStop you should probably set AsyncTask#cancel and in AsyncTask#onPostExecute check if its canceled and if so don't create the popup window.


Actual solution:

Create a flag in the Activity that is set to false in onCreate(). In onStop() set it to true and then in onPostExecute check if it is set and if so do not show the popup window.

OTHER TIPS

Showing PopupWindow as

final View parent = findViewById(R.id.{parentId});
parent.post(new Runnable() {
    @Override
    public void run() {
        mPopup.showAtLocation(parent, ...);
    }
});

has solved the issue.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top