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.
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.