My application has a refresh button on the main activity. When the user presses that button, a new thread is created which starts updating the SQLite database. When this thread started, user could possibly get into another activies of the application.
The problem is these other activities(ListActivity) should be updated according to the DB when that background thread is completed. How could I provide that. I tried getting current task with ActivityManager but It requires extra permission which I dont want.
Edit:
Sorry seems I misunderstood you. Please take a look at the following code, it is similar to Chinaski's (you just use an interface for the callback methods) but I added a bit more to ensure you know how to use it in a way that will avoid memory leaks.
Note how the activity detaches during onDestroy -- alternatively you could use a WeakReference, however these days you'd use a Fragment with setRetainInstance(true) and completely avoid the detaching/attaching as the fragment would be retained.
MyAsyncTask
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private Callback mCallback;
private boolean mIsComplete = false;
private boolean mHasCallbackBeenCalled = false;
public MyBackgroundTask(Callback callback) {
mCallback = callback;
}
/** Only safe to call this from the UI thread */
public void attach(Callback callback) {
mCallback = callback;
if (mIsComplete && !mHasCallbackBeenCalled) {
fireCallback();
}
}
/** Only safe to call this from the UI thread */
public void detach() {
mCallback = callback;
}
#Override
public void doInBackground() {
// do the heavy stuff here
return null;
}
#Override
public void onPostExecute(Void result) {
mIsComplete = true;
fireCallback();
}
private void fireCallback() {
if (mCallback != null) {
mCallback.callbackMethod();
mHasCallbackBeenCalled = true;
}
}
public static interface Callback {
public void callbackMethod();
}
}
MyActivity
public class MyActivity extends Activity implements MyAsyncTask.Callback {
private MyAsyncTask mTask;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check for a retained task after a configuration change
// e.g. a rotation
if (getLastNonConfigurationInstance() != null) {
mTask = (MyAsyncTask) getLastNonConfigurationInstance();
// Re-attach the task
mTask.attach(this);
}
}
#Override
public void onDestroy() {
// Detach from task to avoid memory leak
if (mTask != null) {
mTask.detach();
}
super.onDestroy();
}
#Override
public Object onRetainNonConfigurationInstance() {
// Retain the async task duration a rotation
return mTask;
}
/** Callback method */
#Override
public void callbackMethod() {
// Do something here
}
}
You could make a singleton in which you will have your thread and a queue of "tasks". When a task is finished, you check / launch the next task, and when you add a task, you launch it, or add it in the queue if a task is already running.
I don't say this is the best solution, but it's one.
Related
I've been using AsyncTasks for a while however, I've recently encountered a scenario where I'm unsure of how to handle correctly. Since I thought it would be a somewhat common scenario I decided to ask the question here.
So, I'm trying to use an AsyncTask to make a simple call to sign a user in to the app. After the call completes, if it succeeds, the user should be taken to another activity. This logic is simple. The problem arrises when the user navigates away from the app before the sign in call returns. In such a case, what should I do in onPostExecute()?
What I've seen some apps do is they continue with the call anyways, as long as the activity is still around, and will launch the next activity. However this creates a weird experience where the user navigates away from the app, then several seconds later, the app just pops back up in their face. Of course, I would like to avoid doing this.
Update
Example code:
public class ExampleActivity extends Activity {
private boolean mIsPaused;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Button btnSignIn = (Button) findViewById(R.id.btn_sign_in);
btnSignIn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
new SignInTask(ExampleActivity.this).execute();
}
});
...
}
#Override
protected void onPause() {
super.onPause();
mIsPaused = true;
}
#Override
protected void onResume() {
super.onResume();
mIsPaused = false;
}
private boolean isPaused() {
return mIsPaused;
}
...
private static class SignInTask extends AsyncTask<Void, Void, SomeResult> {
private final WeakReference<ExampleActivity> mAct;
public SignInTask(ExampleActivity act) {
mAct = new WeakReference<ExampleActivity>(act);
}
#Override
protected SomeResult doInBackground(Void... params) {
return mApi.signIn(creds);
}
#Override
protected void onPostExecute(SomeResult result) {
if (result.getCode() == OK) {
ExampleActivity act = mAct.get();
if (act != null) {
if (act.isPaused()) {
// do something
} else {
startActivity(new Intent(act, NextActivity.class));
}
} else {
// do something
}
}
}
}
}
made your AsyncTask class as static inner class.
Pretty interesting problem... Going with what you've started by using booleans, you could save the response the Activity receives to the SharedPreferences in the event it is paused, or continue processing normally if it is not. If the Activity later resumes (or is recreated), check whether or not there is a saved response and handle accordingly. I was thinking something along the lines of:
import org.json.JSONObject;
import android.app.Activity;
import android.os.Bundle;
public class TaskActivity extends Activity {
private static final String KEY_RESPONSE_JSON = "returned_response";
private boolean paused = false;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// don't setup here, wait for onPostResume() to figure out what to do
}
#Override
public void onPostResume(){
super.onPostResume();
paused = false;
if(isSavedResponseAvailable()) processResponse(getSavedResponse());
else setup();
}
#Override
public void onPause(){
paused = true;
super.onPause();
}
private void setup(){
// normal setup
}
public void onReceiveResponse(JSONObject response){
if(paused) setSavedResponse(response);
else processResponse(response);
}
private void processResponse(JSONObject response){
// Continue with processing as if they never left
getSharedPreferences(this.getClass().getName(), 0).edit().clear().commit(); // Clear everything so re-entering won't parse old data
}
private boolean isSavedResponseAvailable(){
return getSavedResponse() != null;
}
private JSONObject getSavedResponse(){
try{
return new JSONObject(getSharedPreferences(this.getClass().getName(), 0).getString(KEY_RESPONSE_JSON, ""));
}
catch(Exception e){ }
return null;
}
private void setSavedResponse(JSONObject response){
getSharedPreferences(this.getClass().getName(), 0).edit().putString(KEY_RESPONSE_JSON, response.toString()).commit();
}
}
Clearly that's assuming your response from the task is JSON, but there's no reason you couldn't extend that to save the data individually and rebuild the necessary response object from the saved preference data.
As far as clean approaches go, though... I give this about a 3/10, but I can't think of anything better (well, other than making the TaskActivity abstract and forcing implementations to override setup(), processResponse(), isResponseAvailable(), getSavedResponse(), and setSavedResponse(), but that would only be mildly better for like a 4/10)
I would suggest putting a try/catch statement in the post execute - as far as I know what would happen in this situation is that you would get some kind of Window Manager exception.
What I would STRONGLY recommend, however, is stopping any async tasks (with the cancel method) on the onPause method, meaning that you won't interrupt them.
http://developer.android.com/reference/android/os/AsyncTask.html#cancel(boolean)
public final boolean cancel (boolean mayInterruptIfRunning)
Added in API level 3
Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
Calling this method will result in onCancelled(Object) being invoked on the UI thread after doInBackground(Object[]) returns. Calling this method guarantees that onPostExecute(Object) is never invoked. After invoking this method, you should check the value returned by isCancelled() periodically from doInBackground(Object[]) to finish the task as early as possible.
Parameters
mayInterruptIfRunning true if the thread executing this task should be interrupted; otherwise, in-progress tasks are allowed to complete.
Returns
false if the task could not be cancelled, typically because it has already completed normally; true otherwise
See Also
isCancelled()
onCancelled(Object)
boolean isRunning; //set it to true in onResume, and false in onStop
boolean isWaiting; // set it to true in onPostExecute, if "isRunning" is false
check in onResume whether isWaiting is true, if yes, take user to another screen.
Use the cancel() of AsynchTask class onBackPress() of Activty class
public class ExampleActivity extends Activity {
private boolean mIsPaused;
SignInTask singleTaskObj;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Button btnSignIn = (Button) findViewById(R.id.btn_sign_in);
btnSignIn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
singleTaskObj = new SignInTask(ExampleActivity.this).execute();
}
});
...
}
#Override
protected void onPause() {
super.onPause();
mIsPaused = true;
}
#Override
protected void onResume() {
super.onResume();
mIsPaused = false;
}
protected void onBackPressed()
{
singleTaskObj.cancel();
}
private boolean isPaused() {
return mIsPaused;
}
...
private static class SignInTask extends AsyncTask<Void, Void, SomeResult> {
private final WeakReference<ExampleActivity> mAct;
public SignInTask(ExampleActivity act) {
mAct = new WeakReference<ExampleActivity>(act);
}
#Override
protected SomeResult doInBackground(Void... params) {
return mApi.signIn(creds);
}
#Override
protected void onPostExecute(SomeResult result) {
if (result.getCode() == OK) {
ExampleActivity act = mAct.get();
if (act != null) {
if (act.isPaused()) {
// do something
} else {
startActivity(new Intent(act, NextActivity.class));
}
} else {
// do something
}
}
}
}
}
I am running a book services app on android where the user downloads the book onto his device.The book files are 1mb and above.
Can Async Task be considered as the best practice for this kind of operation.
Are there any other approaches to performing downloads in the background thread.
Please Advice.
Generally it is beleived that AsyncTask is not meant for long running tasks, but sometimes it is a simple way to perform a simple task (no matter how much time it will take). But the thing is,some of the developers perform it in a wrong way.
public class MainActivity extends Activity {
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// Somewhere the AsyncTask is started
public class MyAsyncTask extends AsyncTask<Void, Void, String> {
#Override protected String doInBackground(Void... params) {
// Do work
return result;
}
#Override protected void onPostExecute(String result) {
Log.d("MyAsyncTask", "Received result: " + result);
}
}
}
The Problem with the above code is, When you start an AsyncTask inside an Activity and you rotate the device or any configuration change happens, the Activity will get destroyed and a new instance will be created. But the AsyncTask will not die and keep on going until it completes. Problems1) Since activities are heavy, this could lead to memory issues if several AsyncTask are started.2) Another issue is that the result of the AsyncTask could be lost, if it's intended to act on the state of the activity.
So we need to fix two problems
1) Activity should not be kept in memory when destroy by the framework.
2) Result of the AsyncTAsk should be delivered to the current Activity Instance.
And to Solve these Problems we need otto http://square.github.io/otto/
To use otto create a MyBus.java which we have to use it as a singleton
public class MyBus {
private static final Bus BUS = new Bus();
public static Bus getInstance() {
return BUS;
}
}
Create a AsyncTaskResultEvent.java file
public class AsyncTaskResultEvent {
private String result;
public AsyncTaskResultEvent(String result) {
this.result = result;
}
public String getResult() {
return result;
}
}
Now in your MainActivity, Do AsyncTask
public class MyAsyncTask extends AsyncTask<Void, Void, String> {
#Override protected String doInBackground(Void... params) {
Random random = new Random();
final long sleep = random.nextInt(10);
try {
Thread.sleep(sleep * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Slept for " + sleep + " seconds";
}
#Override protected void onPostExecute(String result) {
MyBus.getInstance().post(new AsyncTaskResultEvent(result));
}
}
Now the Activity that starts the AsyncTask will get the result later.
To fix the memory leak problem add the below code in MainActivity
#Override protected void onDestroy() {
MyBus.getInstance().unregister(this);
super.onDestroy();
}
#Subscribe public void onAsyncTaskResult(AsyncTaskResultEvent event) {
Toast.makeText(this, event.getResult(), Toast.LENGTH_LONG).show();
}
and put this line of code in your OnCreate Method
MyBus.getInstance().register(this);
Now If a configuration change happens we'll automatically be notified of the result in the new Activity instance since it's registered to the event bus.
An activity launches a long-running async task that runs in a background thread. This background task should return a result to the activity when completed (for instance, to notify a user about an operation success or failure)
Result could arrive at any time. Problems are that:
- the original instance object of the activity could be destroyed (e.g. a screen was rotated)
- there is no ready activity (the old activity destroyed, the new one is not started yet)
- a user changed the visible activity (pressed a back button, or pressed a home button, etc)
Please advice a design pattern for such technical task.
Result could arrive at any time. Problems are that: - the original instance object of the activity could be destroyed (e.g. a screen was rotated)
You could implement a rotation-aware AsyncTask, as described by Mark Murphy ("CommonsWare") here.
there is no ready activity (the old activity destroyed, the new one is not started yet)
If you mean because of the transition between onRetainNonConfigurationInstance() and onCreate(), read the post above, he explains why this shouldn't be a problem.
a user changed the visible activity (pressed a back button, or pressed a home button, etc)
Otherwise, if you mean different Activities, long term data loading, I believe you could implement your own Loader (e.g., an AsyncTaskLoader) with satisfactory results, since they abstract all the usual life cycle overhead you would otherwise need to take care of.
There are a few ways to tackle this problem.
AsyncTask:
public class AsyncTask<Void, Void, T extends Parcelable> {
private final Context mApplicationContext;
public MyAsyncTask(Context context) {
mApplicationContext = context.getApplicationContext();
}
public void onPostExecute(T result) {
Intent intent = new Intent(String.format("%s.DONE", mApplicationContext.getPackageName()));
intent.putExtra("Data", result);
mApplicationContext.sendBroadcast(intent);
}
}
And then in your activity:
public class FooActivity extends Activity {
BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent data) {
if (TextUtils.equals(data.getAction(), String.format("%s.DONE", context.getPackageName()) {
Object o = data.getParacelableExtra("DONE");
// DO SOMETHING WITH IT
}
}
};
public void onCreate(Bundle b) {
...
registerReceiver(mReceiver, new IntentFilter(String.format("%s.DONE", getPackageName())));
}
public void onDestroy() {
...
unregisterReceiver(mReceiver);
}
}
OR
try this:
public class MYActivity extends Activity implements MyCustomAsyncTask.Listener {
private MyCustomAsyncTask<?, ?, ?> mTask;
public Object onRetainNonConfigurationInstance() {
if (mTask != null) {
mTask.setListener(null);
}
return mTask;
}
public void onCreate(Bundle b) {
mTask = getLastNonConfigurationInstance();
if (mTask == null) {
mTask = new MyCustomAsyncTask(....);
}
mTask.setListener(this);
}
MyCustomAsyncTask
public class MyCustomAsyncTask<Void, Void, Object> extends AsyncTask {
public interface Listener {
public void doSomethingWithThis(Object o);
}
private mListener;
public void setListener(Listener listener) {
mListener = listener;
}
public void onPostExecute(Object result) {
if (mListener != null) {
mListener.doSomethingWithThis(result);
}
}
...
}
I read a lot on how to save my instance state or how to deal with my activity getting destroyed during screen rotation.
There seem to be a lot of possibilities but I haven't figured out which one works best for retrieving results of an AsyncTask.
I have some AsyncTasks that are simply started again and call the isFinishing() method of the activity and if the activity is finishing they wont update anything.
The problem is that I have one Task that does a request to a web service that can fail or succeed and restarting the task would result in a financial loss for the user.
How would you solve this? What are the advantages or disadvantages of the possible solutions?
You can check out how I handle AsyncTasks and orientation changes at code.google.com/p/shelves. There are various ways to do it, the one I chose in this app is to cancel any currently running task, save its state and start a new one with the saved state when the new Activity is created. It's easy to do, it works well and as a bonus it takes care of stopping your tasks when the user leaves the app.
You can also use onRetainNonConfigurationInstance() to pass your AsyncTask to the new Activity (be careful about not leaking the previous Activity this way though.)
This is the most interesting question I've seen regarding to Android!!! Actually I've been already looking for the solution during the last months. Still haven't solved.
Be careful, simply overriding the
android:configChanges="keyboardHidden|orientation"
stuff is not enough.
Consider the case when user receives a phone call while your AsyncTask is running. Your request is already being processed by server, so the AsyncTask is awaiting for response. In this moment your app goes in background, because the Phone app has just come in foreground. OS may kill your activity since it's in the background.
My first suggestion would be to make sure you actually need your activity to be reset on a screen rotation (the default behavior). Every time I've had issues with rotation I've added this attribute to my <activity> tag in the AndroidManifest.xml, and been just fine.
android:configChanges="keyboardHidden|orientation"
It looks weird, but what it does it hand off to your onConfigurationChanged() method, if you don't supply one it just does nothing other than re-measure the layout, which seems to be a perfectly adequate way of handling the rotate most of the time.
Why don't you always keep a reference to the current AsyncTask on the Singleton provided by Android?
Whenever a task starts, on PreExecute or on the builder, you define:
((Application) getApplication()).setCurrentTask(asyncTask);
Whenever it finishes you set it to null.
That way you always have a reference which allows you to do something like, onCreate or onResume as appropriated for your specific logic:
this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();
If it's null you know that currently there is none running!
:-)
The most proper way to this is to use a fragment to retain the instance of the async task, over rotations.
Here is a link to very simple example making it easy to follow integrate this technique into your apps.
https://gist.github.com/daichan4649/2480065
In Pro android 4. author has suggest a nice way, that you should use weak reference.
Weak reference note
To my point of view, it's better to store asynctask via onRetainNonConfigurationInstance decoupling it from the current Activity object and binding it to a new Activity object after the orientation change. Here I found a very nice example how to work with AsyncTask and ProgressDialog.
Android : background processing/Async Opeartion with configuration change
To maintain the states of async opeartion during background process:
you can take an help of fragments.
See the following steps :
Step 1: Create a headerless fragment let say background task and add a private async task class with in it.
Step 2 (Optional Step): if you want to put a loading cursor on top of your activity use below code:
Step 3: In your main Activity implement BackgroundTaskCallbacks interface defined in step 1
class BackgroundTask extends Fragment {
public BackgroundTask() {
}
// Add a static interface
static interface BackgroundTaskCallbacks {
void onPreExecute();
void onCancelled();
void onPostExecute();
void doInBackground();
}
private BackgroundTaskCallbacks callbacks;
private PerformAsyncOpeation asyncOperation;
private boolean isRunning;
private final String TAG = BackgroundTask.class.getSimpleName();
/**
* Start the async operation.
*/
public void start() {
Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********");
if (!isRunning) {
asyncOperation = new PerformAsyncOpeation();
asyncOperation.execute();
isRunning = true;
}
Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********");
}
/**
* Cancel the background task.
*/
public void cancel() {
Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********");
if (isRunning) {
asyncOperation.cancel(false);
asyncOperation = null;
isRunning = false;
}
Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********");
}
/**
* Returns the current state of the background task.
*/
public boolean isRunning() {
return isRunning;
}
/**
* Android passes us a reference to the newly created Activity by calling
* this method after each configuration change.
*/
public void onAttach(Activity activity) {
Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********");
super.onAttach(activity);
if (!(activity instanceof BackgroundTaskCallbacks)) {
throw new IllegalStateException(
"Activity must implement the LoginCallbacks interface.");
}
// Hold a reference to the parent Activity so we can report back the
// task's
// current progress and results.
callbacks = (BackgroundTaskCallbacks) activity;
Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********");
}
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********");
super.onCreate(savedInstanceState);
// Retain this fragment across configuration changes.
setRetainInstance(true);
Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********");
}
public void onDetach() {
super.onDetach();
callbacks = null;
}
private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> {
protected void onPreExecute() {
Log.d(TAG,
"********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********");
if (callbacks != null) {
callbacks.onPreExecute();
}
isRunning = true;
Log.d(TAG,
"********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********");
}
protected Void doInBackground(Void... params) {
Log.d(TAG,
"********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********");
if (callbacks != null) {
callbacks.doInBackground();
}
Log.d(TAG,
"********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********");
return null;
}
protected void onCancelled() {
Log.d(TAG,
"********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********");
if (callbacks != null) {
callbacks.onCancelled();
}
isRunning = false;
Log.d(TAG,
"********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********");
}
protected void onPostExecute(Void ignore) {
Log.d(TAG,
"********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********");
if (callbacks != null) {
callbacks.onPostExecute();
}
isRunning = false;
Log.d(TAG,
"********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********");
}
}
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
}
public void onStart() {
super.onStart();
}
public void onResume() {
super.onResume();
}
public void onPause() {
super.onPause();
}
public void onStop() {
super.onStop();
}
public class ProgressIndicator extends Dialog {
public ProgressIndicator(Context context, int theme) {
super(context, theme);
}
private ProgressBar progressBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.progress_indicator);
this.setCancelable(false);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN);
}
#Override
public void show() {
super.show();
}
#Override
public void dismiss() {
super.dismiss();
}
#Override
public void cancel() {
super.cancel();
}
public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{
private static final String KEY_CURRENT_PROGRESS = "current_progress";
ProgressIndicator progressIndicator = null;
private final static String TAG = MyActivity.class.getSimpleName();
private BackgroundTask task = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(//"set your layout here");
initialize your views and widget here .............
FragmentManager fm = getSupportFragmentManager();
task = (BackgroundTask) fm.findFragmentByTag("login");
// If the Fragment is non-null, then it is currently being
// retained across a configuration change.
if (task == null) {
task = new BackgroundTask();
fm.beginTransaction().add(task, "login").commit();
}
// Restore saved state
if (savedInstanceState != null) {
Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: "
+ task.isRunning());
if (task.isRunning()) {
progressIndicator = new ProgressIndicator(this,
R.style.TransparentDialog);
if (progressIndicator != null) {
progressIndicator.show();
}
}
}
}
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
// save the current state of your operation here by saying this
super.onSaveInstanceState(outState);
Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: "
+ task.isRunning());
outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning());
if (progressIndicator != null) {
progressIndicator.dismiss();
progressIndicator.cancel();
}
progressIndicator = null;
}
private void performOperation() {
if (!task.isRunning() && progressIndicator == null) {
progressIndicator = new ProgressIndicator(this,
R.style.TransparentDialog);
progressIndicator.show();
}
if (task.isRunning()) {
task.cancel();
} else {
task.start();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
if (progressIndicator != null) {
progressIndicator.dismiss();
progressIndicator.cancel();
}
progressIndicator = null;
}
#Override
public void onPreExecute() {
Log.i(TAG, "CALLING ON PRE EXECUTE");
}
#Override
public void onCancelled() {
Log.i(TAG, "CALLING ON CANCELLED");
if (progressIndicator != null) {
progressIndicator.dismiss();
progressIndicator.cancel();
}
public void onPostExecute() {
Log.i(TAG, "CALLING ON POST EXECUTE");
if (progressIndicator != null) {
progressIndicator.dismiss();
progressIndicator.cancel();
progressIndicator = null;
}
}
#Override
public void doInBackground() {
// put your code here for background operation
}
}
One thing to consider is whether the result of the AsyncTask should be available only to the activity that started the task. If yes, then Romain Guy's answer is best. If it should be available to other activities of your application, then in onPostExecute you can use LocalBroadcastManager.
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));
You will also need to make sure that activity correctly handles situation when broadcast is sent while activity is paused.
Have a look at this post. This Post involves AsyncTask performing long running operation and memory leak when screen rotation happens both in one sample application. The sample app is available on the source forge
My solution.
In my case i've got a chain of AsyncTasks with the same context. Activity had an access only to first one. To cancel any running task i did the following:
public final class TaskLoader {
private static AsyncTask task;
private TaskLoader() {
throw new UnsupportedOperationException();
}
public static void setTask(AsyncTask task) {
TaskLoader.task = task;
}
public static void cancel() {
TaskLoader.task.cancel(true);
}
}
Task doInBackground():
protected Void doInBackground(Params... params) {
TaskLoader.setTask(this);
....
}
Activity onStop() or onPause():
protected void onStop() {
super.onStop();
TaskLoader.cancel();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
final AddTask task = mAddTask;
if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
final String bookId = task.getBookId();
task.cancel(true);
if (bookId != null) {
outState.putBoolean(STATE_ADD_IN_PROGRESS, true);
outState.putString(STATE_ADD_BOOK, bookId);
}
mAddTask = null;
}
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) {
final String id = savedInstanceState.getString(STATE_ADD_BOOK);
if (!BooksManager.bookExists(getContentResolver(), id)) {
mAddTask = (AddTask) new AddTask().execute(id);
}
}
}
you can also add
android:configChanges="keyboardHidden|orientation|screenSize"
to your manifest example i hope it help
<application
android:name=".AppController"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="#style/AppTheme">
When an Activity terminates, e.g. after screen orientation changing, is that possible to change an AsyncTask activity context? Else it will create an error because when the activity terminates AsyncTask's activity context is gone too.
My homework done is the following:
public void onSaveInstanceState(Bundle savedInstanceState) <- doesn't solve
public Object onRetainNonConfigurationInstance() <- doesn't solve
android:configChanges="keyboardHidden|orientation"
<- solved but doesn't handle well relative layouts
What do you pass on your onRetainNonConfigurationInstance()? What I do is pass an object to it containing the AsyncTask, and then I try to retrieve the value in getLastNonConfigurationInstance().
EDIT: On second thought, it would depend on what you want to do after a configuration change. If you want to terminate the AsyncTask, and then call cancel() on it. If you want to continue its processing even after an orientation change, then you have to hold on to the task.
You can do that by saving the Activity in the AsyncTask like this:
private MyAsyncTask searchTask;
#Override
public void onCreate(Bundle savedInstance){
super.onCreate(savedInstance);
if (getLastNonConfigurationInstance()!=null) {
SavedObject savedObj = (SavedObject)getLastNonConfigurationInstance();
searchTask = savedObj.getAsyncTask();
searchTask.attach(this);
} else {
searchTask = new MyAsyncTask(this);
searchTask.execute();
}
}
#Override
public Object onRetainNonConfigurationInstance(){
searchTask.detach();
final SavedObject savedObj = new SavedObject();
savedObj.setAsyncTask(searchTask);
return savedObj;
}
private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
MyActivity parentActivity = null;
MyAsyncTask (MyActivity activity) {
attach(activity);
}
void attach(MyActivity activity) {
this.parentActivity=activity;
}
void detach() {
parentActivity=null;
}
// Do your thread processing here
}
private class SavedObject {
private MyAsyncTask asyncTask;
public void setAsyncTask(MyAsyncTask asyncTask){
this.asyncTask = asyncTask;
}
public MyAsyncTask getAsyncTask() {
return asyncTask;
}
}
in the OnCancel method of your asynch task put finish();
public void onCancel(DialogInterface dialog) {
cancel(true);
dialog.dismiss();
finish();
}