Background Task, Progress Dialog, Orientation Change - Is There Any 100% Working Solution

Background task, progress dialog, orientation change - is there any 100% working solution?

Step #1: Make your AsyncTask a static nested class, or an entirely separate class, just not an inner (non-static nested) class.

Step #2: Have the AsyncTask hold onto the Activity via a data member, set via the constructor and a setter.

Step #3: When creating the AsyncTask, supply the current Activity to the constructor.

Step #4: In onRetainNonConfigurationInstance(), return the AsyncTask, after detaching it from the original, now-going-away activity.

Step #5: In onCreate(), if getLastNonConfigurationInstance() is not null, cast it to your AsyncTask class and call your setter to associate your new activity with the task.

Step #6: Do not refer to the activity data member from doInBackground().

If you follow the above recipe, it will all work. onProgressUpdate() and onPostExecute() are suspended between the start of onRetainNonConfigurationInstance() and the end of the subsequent onCreate().

Here is a sample project demonstrating the technique.

Another approach is to ditch the AsyncTask and move your work into an IntentService. This is particularly useful if the work to be done may be long and should go on regardless of what the user does in terms of activities (e.g., downloading a large file). You can use an ordered broadcast Intent to either have the activity respond to the work being done (if it is still in the foreground) or raise a Notification to let the user know if the work has been done. Here is a blog post with more on this pattern.

progressDialog app crashes on screen rotation

That is because of dialog will became null.
You can resolve these using two different ways.

  1. Stop recreating whole activity. i.e setting
    You can avoid activity recreation by adding following to your application's manifest file.

    android:configChanges="orientation|keyboardHidden|screenSize"
    As follows

    <activity
    android:name=".your activity"
    android:configChanges="orientation|keyboardHidden|screenSize"
    android:label="@string/app_name" >
    </activity>
  2. Show/dismiss the dialog within AsyncTask during onPreExecute/onPostExecute as usual, though in case of orientation-change create/show a new instance of the dialog in the activity and pass its reference to the task. Refer below code and do necessary steps.

public class MainActivity extends Activity {

    private Button mButton;
private MyTask mTask = null;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

MyTask task = (MyTask) getLastNonConfigurationInstance();
if (task != null) {
mTask = task;
mTask.mContext = this;
mTask.mDialog = new ProgressDialog(MainActivityProgress.this);
mTask.mDialog.setMessage("Please wait...");
mTask.mDialog.setIndeterminate(false);
mTask.mDialog.setMax(100);
mTask.mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mTask.mDialog.setCancelable(true);
mTask.mDialog.show();
}

mButton = (Button) findViewById(R.id.button1);
mButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mTask = new MyTask(MainActivity.this);
mTask.execute();
}
});
}

@Override
public Object onRetainNonConfigurationInstance() {
String str = "null";
if (mTask != null) {
str = mTask.toString();
mTask.mDialog.dismiss();
}
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
return mTask;
}

private class MyTask extends AsyncTask<Void, Void, Void> {
private ProgressDialog mDialog;
private MainActivity mContext;

public MyTask(MainActivity context) {
super();
mContext = context;
}

protected void onPreExecute() {
mDialog = new ProgressDialog(MainActivityProgress.this);
mDialog.setMessage("Please wait...");
mDialog.setIndeterminate(false);
mDialog.setMax(100);
mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mDialog.setCancelable(true);
mDialog.show();
}

protected void onPostExecute(Void result) {
mContext.mTask = null;
mDialog.dismiss();
}

@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(5000);
return null;
}
}
}

Ok, So after editing your code it will look as below:

public class MainActivityProgress extends Activity {

Button button;

public static final int progress_bar_type = 0;

private static String file_url = "http://farm1.static.flickr.com/114/298125983_0e4bf66782_b.jpg";
private DownloadFileFromURL mTask = null;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_activity_progress);
button = (Button) findViewById(R.id.btn_download);
DownloadFileFromURL task = (DownloadFileFromURL) getLastNonConfigurationInstance();
if (task != null) {
mTask = task;
mTask.mContext = this;
mTask.mDialog = ProgressDialog.show(MainActivityProgress.this,
"Downloading file.", "Please wait...", true);
}

button.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
mTask = new DownloadFileFromURL(MainActivityProgress.this);
mTask.execute(file_url);
}
});
}

@Override
public Object onRetainNonConfigurationInstance() {
String str = "null";
if (mTask != null) {
str = mTask.toString();
mTask.mDialog.dismiss();
}
return mTask;
}

class DownloadFileFromURL extends AsyncTask<String, String, String> {

private ProgressDialog mDialog;
private MainActivityProgress mContext;

public DownloadFileFromURL(MainActivityProgress context) {
mContext = context;
}

protected void onPreExecute() {
mDialog = ProgressDialog.show(MainActivityProgress.this,
"Downloading file.", "Please wait...", true);
}

@Override
protected String doInBackground(String... f_url) {
SystemClock.sleep(5000);
int count;
try {
URL url = new URL(f_url[0]);
URLConnection conection = url.openConnection();
conection.connect();

int lenghtOfFile = conection.getContentLength();

InputStream input = new BufferedInputStream(url.openStream(),
8192);

OutputStream output = new FileOutputStream(
"/sdcard/downloadedfile.jpg");

byte data[] = new byte[1024];

long total = 0;

while ((count = input.read(data)) != -1) {
total += count;
publishProgress("" + (total * 100) / lenghtOfFile);

output.write(data, 0, count);
}

output.flush();

output.close();
input.close();

} catch (Exception e) {
Log.e("Error: ", e.getMessage());
}

return null;
}
protected void onProgressUpdate(String... progress) {
mDialog.setProgress(Integer.parseInt(progress[0]));
}
@Override
protected void onPostExecute(String file_url) {
mContext.mTask = null;
if (mDialog.isShowing()) {
mDialog.dismiss();
}

String imagePath = Environment.getExternalStorageDirectory()
.toString() + "/downloadedfile.jpg";
Log.e("imagePath: ", imagePath);
// imageView.setImageDrawable(Drawable.createFromPath(imagePath));
}

}

}

Android Dialog Window Leaked During Orientation Change

I know you said you can't use android:configChanges="keyboardHidden|orientation|screenSize" because you have different layouts for landscape and portrait, but take a look aAndroid documentation concerning Handling the Configuration Change Yourself:

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);

// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
setContentView(R.layout.landscapeLayout);
//update your view elements, if any
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
setContentView(R.layout.portraitLayout);
//update your view elements, if any
}
}

That way you can mantain your different layouts, and keep the reference to your dialog

How to deal with orientation change with a ProgressDialog showing?

Try adding this attribute android:configChanges="orientation" to your Activity element in the AndroidManifest.xml file.

Threads and device orientation

Our way of handling this is to start the async task from a sticky app.Service and allow the async task to communicate with the parent Service which in turn communicates with anyone listening to it's broadcast events via the BroadcastReceiver framework. I have gone someway to describe this mechanism here.

On orientation change, your app.Activity will be destroyed but the background Service won't be (it can be onLowMemory & a myriad of other circumstances but it probably won't). On recreating the activity you can check to see if your service is still running via the solution I posted here. Based on that result you can decide what to do with the UI, redisplay/re-add the progress dialogue or whatever and re-register your receiver to listen for events coming out of the Service.

The responsibilities between these layers works like this;

  1. AsyncTask

    • Does the work.
    • Reports back what it's up to and when it's done by firing events (Intents)
    • That's it
  2. Service

    • Hosts an orientation unaware container for the AsyncTask
    • Registers listeners for events emanating from the AsyncTask in #onCreate
    • Executes the AsyncTask in #onStartCommand
    • Stops itself (see stopSelf()) when it receives the "I have finished" event from the AsyncTask
    • Fires events describing progress to anyone listening
  3. Activity

    • Starts the Service which in turn starts the AsyncTask.
    • Registers listeners for events emanating from the Service in #onCreate
    • Checks whether a Service is running already during onCreate to make a decision as to whether work is ongoing or needs starting.

3 elements with discrete unconfused roles. That's the way we like it. The only nuance with this approach is the use of the BroadcastReceivers. If you are comfortable with those then you are golden.

How to handle Activity when Orientation changes?

I would say you have two options :

Either you force your activity not to be able to change orientation :

<activity android:name="MainActivity" android:configChanges="keyboardHidden|orientation"> 

in your manifest (documentation), which will tell the system that you want to handle orientation changes by yourself, and in this case, you won't do anything. But, you'd rather add some code in onPause() and onSaveInstanceState() because this activity might be interrupted by Android when you receive a phone call for instance. So you need to handle some saving.

Or, you choose to prevent the display of the dialog when has already been displayed, or when your background thread is running. This is easy is you use an AsyncTask for your download part, because you can use AsyncTask.getStatus which will return you the status of the task. If it is already in finished status, you have to cancel the dialog.



Related Topics



Leave a reply



Submit