My program does some network activity in a background thread. Before starting, it pops up a progress dialog. The dialog is dismissed on the handler. This all works fine, except when screen orientation changes while the dialog is up (and the background thread is going). At this point the app either crashes, or deadlocks, or gets into a weird stage where the app does not work at all until all the threads have been killed.
How can I handle the screen orientation change gracefully?
The sample code below matches roughly what my real program does:
public class MyAct extends Activity implements Runnable {
public ProgressDialog mProgress;
// UI has a button that when pressed calls send
public void send() {
mProgress = ProgressDialog.show(this, "Please wait",
"Please wait",
true, true);
Thread thread = new Thread(this);
thread.start();
}
public void run() {
Thread.sleep(10000);
Message msg = new Message();
mHandler.sendMessage(msg);
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mProgress.dismiss();
}
};
}
Stack:
E/WindowManager( 244): Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager( 244): android.view.WindowLeaked: Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager( 244): at android.view.ViewRoot.<init>(ViewRoot.java:178)
E/WindowManager( 244): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:147)
E/WindowManager( 244): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:90)
E/WindowManager( 244): at android.view.Window$LocalWindowManager.addView(Window.java:393)
E/WindowManager( 244): at android.app.Dialog.show(Dialog.java:212)
E/WindowManager( 244): at android.app.ProgressDialog.show(ProgressDialog.java:103)
E/WindowManager( 244): at android.app.ProgressDialog.show(ProgressDialog.java:91)
E/WindowManager( 244): at MyAct.send(MyAct.java:294)
E/WindowManager( 244): at MyAct$4.onClick(MyAct.java:174)
E/WindowManager( 244): at android.view.View.performClick(View.java:2129)
E/WindowManager( 244): at android.view.View.onTouchEvent(View.java:3543)
E/WindowManager( 244): at android.widget.TextView.onTouchEvent(TextView.java:4664)
E/WindowManager( 244): at android.view.View.dispatchTouchEvent(View.java:3198)
I have tried to dismiss the progress dialog in onSaveInstanceState, but that just prevents an immediate crash. The background thread is still going, and the UI is in partially drawn state. Need to kill the whole app before it starts working again.
This is my proposed solution:
void showProgressDialog()
method can be added to the fragment-activity listener interface for this purpose.The simplest and most flexible solution is to use an AsyncTask with a static reference to ProgressBar. This provides an encapsulated and thus reusable solution to orientation change problems. This solution has served me well for varying asyncronous tasks including internet downloads, communicating with Services, and filesystem scans. The solution has been well tested on multiple android versions and phone models. A complete demo can be found here with specific interest in DownloadFile.java
I present the following as a concept example
Usage in an Android Activity is simple
i have found and easier solution to handle threads when orientation change. You can just keep an static reference to your activity/fragment and verify if its null before acting on the ui. I suggest using a try catch too:
If you create a background
Service
that does all the heavy lifting (tcp requests/response, unmarshalling), theView
andActivity
can be destroyed and re-created without leaking window or losing data. This allows the Android recommended behavior, which is to destroy an Activity on each configuration change (eg. for each orientation change).It is a bit more complex, but it is the best way for invoking server request, data pre/post-processing, etc.
You may even use your
Service
to queue each request to a server, so it makes it easy and efficient to handle those things.The dev guide has a full chapter on
Services
.If you're struggling with detecting orientation change events of a dialog INDEPENDENT OF AN ACTIVITY REFERENCE, this method works excitingly well. I use this because I have my own dialog class that can be shown in multiple different Activities so I don't always know which Activity it's being shown in. With this method you don't need to change the AndroidManifest, worry about Activity references, and you don't need a custom dialog (as I have). You do need, however, a custom content view so you can detect the orientation changes using that particular view. Here's my example:
Setup
Implementation 1 - Dialog
Implementation 2 - AlertDialog.Builder
Implementation 3 - ProgressDialog / AlertDialog
I have an implementation which allows the activity to be destroyed on a screen orientation change, but still destroys the dialog in the recreated activity successfully. I use
...NonConfigurationInstance
to attach the background task to the recreated activity. The normal Android framework handles recreating the dialog itself, nothing is changed there.I subclassed AsyncTask adding a field for the 'owning' activity, and a method to update this owner.
In my activity class I added a field
backgroundTask
referring to the 'owned' backgroundtask, and I update this field usingonRetainNonConfigurationInstance
andgetLastNonConfigurationInstance
.Suggestions for further improvement:
backgroundTask
reference in the activity after the task is finished to release any memory or other resources associated with it.ownerActivity
reference in the backgroundtask before the activity is destroyed in case it will not be recreated immediately.BackgroundTask
interface and/or collection to allow different types of tasks to run from the same owning activity.