Android How to reconnect to AsyncTask after onDestroy() and relaunch onCreate()? - android

I have tested the statement that AsyncTasks are not destroyed along with their launching activity. And it is true.
I made the AsyncTask just publish a Log.i() message every 3 seconds for 1 minute. And I put Log.i() messsage in the onDestroy() method of the activity.
I see the activity is destroyed, but the AsyncTask keeps running until it finishes all 20 Log.i() messages.
And I am confused.
What if the AsyncTask had publishProgress() into the destroyed UI?
I guess some sort of exception would occurr, right?
What if the AsyncTask stores data in a global variable of class Application?
No idea here, NullPointer exception?
What if the app is restarted?
It will probably launch a new AsyncTask. Can it reconnect with the still running AsyncTask?
Is the AsyncTask immortal after the mother app is destroyed?
Maybe yes, how do all LogCat apps keep logging messages while the UI application is not visible anymore, maybe destroyed? And when you reopen them they show you the messsages that were generated while it was 'dead'.
All this seems like a discussion, but the question is in the title. I have this orphan AsyncTask, which I would like very much to reconnect to when the app is relaunched, but I don't know how to do it.
I forgot to tell why this is very important. The app gets destroyed when an orientation change occurs. And I don't want to loose the data produced by the AsyncTask, I don't want to stop it and restart it. I just want it to keep going and reconnect after the orientation changes are done.

I hope I've got this right as it's from some old code I no longer use (I now use an IntentService to do what this used to do).
This is what I originally had when I downloaded files in my main Activity...
public class MyMainActivity extends Activity {
FileDownloader fdl = null;
...
// This is an inner class of my main Activity
private class FileDownloader extends AsyncTask<String, String, Boolean> {
private MyMainActivity parentActivity = null;
protected void setParentActivity(MyMainActivity parentActivity) {
this.parentActivity = parentActivity;
}
public FileDownloader(MyMainActivity parentActivity) {
this.parentActivity = parentActivity;
}
// Rest of normal AsyncTask methods here
}
}
The key is to use onRetainNonConfigurationInstance() to 'save' the AsyncTask.
Override
public Object onRetainNonConfigurationInstance() {
// If it exists then we MUST set the parent Activity to null
// before returning it as once the orientation re-creates the
// Activity, the original Context will be invalid
if (fdl != null)
fdl.setParentActivity(null);
return(fdl);
}
I then have a method called doDownload() which is called from onResume() if a Boolean indicating downloadComplete is true. The Boolean is set in the onPostExecute(...) method of FileDownloader.
private void doDownload() {
// Retrieve the FileDownloader instance if previousy retained
fdl = (FileDownloader)getLastNonConfigurationInstance();
// If it's not null, set the Context to use for progress updates and
// to manipulate any UI elements in onPostExecute(...)
if (fdl != null)
fdl.setParentActivity(this);
else {
// If we got here fdl is null so an instance hasn't been retained
String[] downloadFileList = this.getResources().getStringArray(R.array.full_download_file_list);
fdl = new FileDownloader(this);
fdl.execute(downloadFileList);
}
}

Related

OnSaveInstanceState/ RestCalls

I am relatively new to Android development, and I have a question about onSaveInstanceState(). I am currently working on a login Activity for an app. To check to see if the user can login to their account, I perform a rest call to a server and, based on the response-code, see if I should grant access to the user. The root of my question is based on the fact that I am trying to avoid passing the Activity's Context to my rest-call class. To do this, I create a boolean field in my login Activity representing whether or not the rest-call was successful and a runnable that updates said boolean that I pass to the rest-call class. I know this goes against the idea of an AsyncTask, but I can't find any alternative to simply putting up a dialog box telling the user to wait while this happens. My questions are below.
1) If I use savedInstanceState() in the onCreate method, how do I instantiate this boolean field for the first time barring null checking an Object boolean? What I mean by this is that after the Activity is destroyed for whatever reason (such as orientation change, etc...) I will use the boolean value stored in my overriden onSaveInstanceState method; however, when it is created for the first time, it has no reference to a boolean value so it has to create one.
2) Does this Runnable even help? I did it so that I wouldn't have to pass the context, but if the Activity is going to be deleted before the RestCall(AsyncTask) is complete, does it really matter whether you pass the context or a Runnable affecting a field of the Activity? The more I think about this, the more I believe it is not going to make much of a difference as it will still result in it pointing to a non-existent object. I am trying to avoid using a Singleton design as I have gathered it is not optimal, but because of the potential lag in time with an AsyncTask, I am beginning to think that it may not be avoidable.
I know onSaveInstanceState() is a topic that has been brought up a lot on StackOverflow, however, I could not find an answer to these questions. I apologize if there has already been a thread for this, but any help or guidance on this would be greatly appreciated! Thank You!
Login Activities' setup:
public class LoginActivity extends Activity implements View.OnClickListener {
private EditText username_et;
private EditText password_et;
private Button login_b;
private boolean login_success = true;
private Runnable run;
/**
* Instances created when app starts
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view_login);
// login_success = false;
login_success = savedInstanceState.getBoolean("login_success");
username_et = (EditText) findViewById(R.id.username_text);
username_et.setOnClickListener(LoginActivity.this);
password_et = (EditText) findViewById(R.id.password_text);
password_et.setOnClickListener(LoginActivity.this);
login_b = (Button) findViewById(R.id.login_button);
login_b.setOnClickListener(LoginActivity.this);
run = new Runnable() {
#Override
public void run() {
login_success = true;
}
};
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState){
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putBoolean("login_success", login_success);
}
Congratulations. You just discovered Android's dirty little secret.
AsyncTask has an inherent design flaw. It doesn't deal well with configuration changes that happen during background task execution because of exactly the problem you mentioned. It needs to hold a reference to the activity, but there's no guarantee that the reference will still be valid by the time the background task completes.
Here are two ways to overcome this problem:
I refer you to Alex Lockwood's excellent blog post on using hidden fragments with setRetainInstance(true) to span activity destruction and recreation. This is a more involved solution than the next one, but this solution has the advantage that you can still report progress with callbacks. If you were intending to call publishProgress() in your AsyncTask, then this is the method you should use.
Use a Loader. Loaders were designed around database data retrieval in the background, but the fact is that they can also be used to handle remote server access in the background as well. I use a Loader for the majority of my remote server tasks.
Here's an example:
public static class ResetPasswordLoader extends AsyncTaskLoader<Pair<CharSequence, Exception>> {
private static final String TAG = "ResetPasswordLoader ";
private String mEmail;
public ResetPasswordLoader(Context context, String email) {
super(context);
mEmail = email;
// set the content-changed flag
onContentChanged();
}
#Override
protected void onStartLoading() {
// only start the load if the content-changed flag is set
// takeContentChanged() returns the value of the flag before it is cleared
if (takeContentChanged()) {
forceLoad();
}
}
#Override
public Pair<CharSequence, Exception> loadInBackground() {
CharSequence result = null;
Exception exc = null;
try {
result = Service.getInstance().resetPassword(mEmail);
} catch (RemoteServiceException e) {
exc = e;
Log.e(TAG, "loadInBackground(), email = " + mEmail, e);
}
return new Pair<>(result, exc);
}
}
Also, in my onLoadFinished() override I make sure to call loaderManager.destroyLoader() on the loader's id.
Again, Alex Lockwood's blog has some great articles on loaders as well.
For the UI, something I do frequently is put up a indeterminate progress bar over the UI upon calling loaderManager.initLoader(). I also set a boolean like mProgressShown. This boolean gets saved in onSaveInstanceState, so when the activity/fragment is created again, I restore the boolean value which tells me to show the progress bar immediately. Some time later onLoadFinished will be called and I clear mProgressShown and hide the progress bar.

Use of static variable associated with activity in async task

I have an activity A with static variable s. I pass the activity instance to async task for some processing. Suppose, in the mean time activity is closed (garbage collected) by android OS and AsyncTask is running in background. In AsyncTask now it is trying to access A.s which is not available so, it throws an exception. Is there any Solution for this. Thanks in advance.
Stop the asyncTask when the activity is closed. So the asyncTask will not try to access the activity anymore.
Try the following, don't keep s as static just pass it to the doInBackground method and collect back the result from onPostExecute method .
private class someLongRunningOperation extends AsyncTask<ArrayList, Progress, Result>{
#Override
protected Result doInBackground(ArrayList... params) {
// TODO Auto-generated method stub
String s=params[0];
//Do operations on the data
...
return s;
}
}
Call:
someLongRunningOperation longTask= new someLongRunningOperation ();
longTask.execute(yourList1, yourList2, yourList3...);
Note that the problem that you described can also happen during the screen orientation changes, If you are using a fragment use
setRetainInstance(true);
to save your data across activity recreations.
Just add null checks to avoid the exception. Example:
if (A != null && A.s != null) {
// proceed with your attempt to update the A.s variable
}

Callback to activity from other class

I have Activity class, Controller class (normal java class use to control number of activity) and BusinessEngine class (normal java class use to process data).
When I need to do some calculation from activity, Activity will call Controller and Controller will call BusinessEngine to do the calculation. When BusinessEngine done with the calculation, it will pass the value back to Controller and finally let the activity know the calculation is complete.
The problem is how I callback Activity from Controller class? Or pass any data to Activity and notify it the data has been change?
Any "long" running tasks must be performed in a background thread. I'm not sure if your currently doing this for your task, so just in case your not, there are a couple of ways to do this. The first is to simply use a AsyncTask, the second is to create your own instance of AbstractExecutorService (AsyncTask uses ThreadPoolExecutor) and use that to post Runnable or Callables to. The later way may save you a lot of re factoring depending on your code base.
Assuming you're now running the task in a background thread, it's necessary to perform your UI updates on the UI thread. There are again a couple of ways to do this. One method is to post a runnable to the method Activity#runOnUiThread, the second is to use a Handler which has previously been created on the UI thread (which Activity#runOnUiThread does behind the scenes).
So, assume your Activity has a method #postResults(final Object o), and your controller has the method #doSomething(final Activity activity).
Your activity would look something like this.
public class MyActivity extends Activity {
Controller controller = ....
ExecutorService service = Executors.newFixedThreadPool(10);
private void startTask() {
Runnable r = new Runnable() {
public void run() {
c.doSomething(MyActivity.this);
}
}
service.submit(r);
}
public void postResults(final Object o) {
Runnable r = new Runnable() {
public void run() {
// Update your UI here
}
}
runOnUiThread(r)
}
}
and your controller
public class Controller {
public void doSomething(final Activity activity) {
// Perform some long running task here
activity.postResults(someObject);
}
}
Obviously this example could be tidied up (for example passing a interface to doSomething rather than the Activity), but hopefully it should be enough to understand what you need to do :)
Why are you looking for the controller to call you Activity? Normally, your Activity must call the controller via its methods and directly get results from them:
// Code in your Activity
result = controller.doSomething(args);
try using a android AsyncTask, if your method takes a long time to process. example
Add your classes to an Async task or if you're calling the classes and passing them from one class to the other.I would say to use static class. And provide some code so we can know how you are passing your data.
If not use general methods to call the superclass or the subclass.
My answer is a bit abstract as information is less.

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

I download some data from internet in background thread (I use AsyncTask) and display a progress dialog while downloading. Orientation changes, Activity is restarted and then my AsyncTask is completed - I want to dismiss the progess dialog and start a new Activity. But calling dismissDialog sometimes throws an exception (probably because the Activity was destroyed and new Activity hasn't been started yet).
What is the best way to handle this kind of problem (updating UI from background thread that works even if user changes orientation)? Did someone from Google provide some "official 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.
The accepted answer was very helpful, but it doesn't have a progress dialog.
Fortunately for you, reader, I have created an extremely comprehensive and working example of an AsyncTask with a progress dialog!
Rotation works, and the dialog survives.
You can cancel the task and dialog by pressing the back button (if you want this behaviour).
It uses fragments.
The layout of the fragment underneath the activity changes properly when the device rotates.
I've toiled for a week to find a solution to this dilemma without resorting to editing the manifest file. The assumptions for this solution are:
You always need to use a progress dialog
Only one task is performed at a time
You need the task to persist when the phone is rotated and the progress dialog to be automatically dismisses.
Implementation
You will need to copy the two files found at the bottom of this post into your workspace. Just make sure that:
All your Activitys should extend BaseActivity
In onCreate(), super.onCreate() should be called after you initialize any members that need to be accessed by your ASyncTasks. Also, override getContentViewId() to provide the form layout id.
Override onCreateDialog() like usual to create dialogs managed by the activity.
See code below for a sample static inner class to make your AsyncTasks. You can store your result in mResult to access later.
final static class MyTask extends SuperAsyncTask<Void, Void, Void> {
public OpenDatabaseTask(BaseActivity activity) {
super(activity, MY_DIALOG_ID); // change your dialog ID here...
// and your dialog will be managed automatically!
}
#Override
protected Void doInBackground(Void... params) {
// your task code
return null;
}
#Override
public boolean onAfterExecute() {
// your after execute code
}
}
And finally, to launch your new task:
mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();
That's it! I hope this robust solution will help someone.
BaseActivity.java (organize imports yourself)
protected abstract int getContentViewId();
public abstract class BaseActivity extends Activity {
protected SuperAsyncTask<?, ?, ?> mCurrentTask;
public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getContentViewId());
mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
if (mCurrentTask != null) {
mCurrentTask.attach(this);
if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
&& mDialogMap.get((Integer) mCurrentTask.dialogId)) {
mCurrentTask.postExecution();
}
}
}
#Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
mDialogMap.put(id, true);
}
#Override
public Object onRetainNonConfigurationInstance() {
if (mCurrentTask != null) {
mCurrentTask.detach();
if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
&& mDialogMap.get((Integer) mCurrentTask.dialogId)) {
return mCurrentTask;
}
}
return super.onRetainNonConfigurationInstance();
}
public void cleanupTask() {
if (mCurrentTask != null) {
mCurrentTask = null;
System.gc();
}
}
}
SuperAsyncTask.java
public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected BaseActivity mActivity = null;
protected Result mResult;
public int dialogId = -1;
protected abstract void onAfterExecute();
public SuperAsyncTask(BaseActivity activity, int dialogId) {
super();
this.dialogId = dialogId;
attach(activity);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
mActivity.showDialog(dialogId); // go polymorphism!
}
protected void onPostExecute(Result result) {
super.onPostExecute(result);
mResult = result;
if (mActivity != null &&
mActivity.mDialogMap.get((Integer) dialogId) != null
&& mActivity.mDialogMap.get((Integer) dialogId)) {
postExecution();
}
};
public void attach(BaseActivity activity) {
this.mActivity = activity;
}
public void detach() {
this.mActivity = null;
}
public synchronized boolean postExecution() {
Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
if (dialogExists != null || dialogExists) {
onAfterExecute();
cleanUp();
}
public boolean cleanUp() {
mActivity.removeDialog(dialogId);
mActivity.mDialogMap.remove((Integer) dialogId);
mActivity.cleanupTask();
detach();
return true;
}
}
Did someone from Google provide some "official solution"?
Yes.
The solution is more of an application architecture proposal rather that just some code.
They proposed 3 design patterns that allows an application to work in-sync with a server, regardless of the application state (it will work even if the user finishes the app, the user changes screen, the app gets terminated, every other possible state where a background data operation could be interrumpted, this covers it)
The proposal is explained in the Android REST client applications speech during Google I/O 2010 by Virgil Dobjanschi. It is 1 hour long, but it is extremely worth watching.
The basis of it is abstracting network operations to a Service that works independently to any Activity in the application. If you're working with databases, the use of ContentResolver and Cursor would give you an out-of-the-box Observer pattern that is convenient to update UI without any aditional logic, once you updated your local database with the fetched remote data. Any other after-operation code would be run via a callback passed to the Service (I use a ResultReceiver subclass for this).
Anyway, my explanation is actually pretty vague, you should definititely watch the speech.
While Mark's (CommonsWare) answer does indeed work for orientation changes, it fails if the Activity is destroyed directly (like in the case of a phone call).
You can handle the orientation changes AND the rare destroyed Activity events by using an Application object to reference your ASyncTask.
There's an excellent explanation of the problem and the solution here:
Credit goes completely to Ryan for figuring this one out.
After 4 years Google solved the problem just calling setRetainInstance(true) in Activity onCreate. It will preserve your activity instance during device rotation. I have also a simple solution for older Android.
you should call all activity actions using activity handler. So if you are in some thread you should create a Runnable and posted using Activitie's Handler. Otherwise your app will crash sometimes with fatal exception.
This is my solution: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog
Basically the steps are:
I use onSaveInstanceState to save the task if it is still
processing.
In onCreate I get the task if it was saved.
In onPause I discard the ProgressDialog if it is shown.
In onResume I show the ProgressDialog if the task is still
processing.

Is AsyncTask really conceptually flawed or am I just missing something?

I have investigated this problem for months now, came up with different solutions to it, which I am not happy with since they are all massive hacks. I still cannot believe that a class that flawed in design made it into the framework and no-one is talking about it, so I guess I just must be missing something.
The problem is with AsyncTask. According to the documentation it
"allows to perform background
operations and publish results on the
UI thread without having to manipulate
threads and/or handlers."
The example then continues to show how some exemplary showDialog() method is called in onPostExecute(). This, however, seems entirely contrived to me, because showing a dialog always needs a reference to a valid Context, and an AsyncTask must never hold a strong reference to a context object.
The reason is obvious: what if the activity gets destroyed which triggered the task? This can happen all the time, e.g. because you flipped the screen. If the task would hold a reference to the context that created it, you're not only holding on to a useless context object (the window will have been destroyed and any UI interaction will fail with an exception!), you even risk creating a memory leak.
Unless my logic is flawed here, this translates to: onPostExecute() is entirely useless, because what good is it for this method to run on the UI thread if you don't have access to any context? You can't do anything meaningful here.
One workaround would be to not pass context instances to an AsyncTask, but a Handler instance. That works: since a Handler loosely binds the context and the task, you can exchange messages between them without risking a leak (right?). But that would mean that the premise of AsyncTask, namely that you don't need to bother with handlers, is wrong. It also seems like abusing Handler, since you are sending and receiving messages on the same thread (you create it on the UI thread and send through it in onPostExecute() which is also executed on the UI thread).
To top it all off, even with that workaround, you still have the problem that when the context gets destroyed, you have no record of the tasks it fired. That means that you have to re-start any tasks when re-creating the context, e.g. after a screen orientation change. This is slow and wasteful.
My solution to this (as implemented in the Droid-Fu library) is to maintain a mapping of WeakReferences from component names to their current instances on the unique application object. Whenever an AsyncTask is started, it records the calling context in that map, and on every callback, it will fetch the current context instance from that mapping. This ensures that you will never reference a stale context instance and you always have access to a valid context in the callbacks so you can do meaningful UI work there. It also doesn't leak, because the references are weak and are cleared when no instance of a given component exists anymore.
Still, it is a complex workaround and requires to sub-class some of the Droid-Fu library classes, making this a pretty intrusive approach.
Now I simply want to know: Am I just massively missing something or is AsyncTask really entirely flawed? How are your experiences working with it? How did you solve these problem?
Thanks for your input.
How about something like this:
class MyActivity extends Activity {
Worker mWorker;
static class Worker extends AsyncTask<URL, Integer, Long> {
MyActivity mActivity;
Worker(MyActivity activity) {
mActivity = activity;
}
#Override
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
}
return totalSize;
}
#Override
protected void onProgressUpdate(Integer... progress) {
if (mActivity != null) {
mActivity.setProgressPercent(progress[0]);
}
}
#Override
protected void onPostExecute(Long result) {
if (mActivity != null) {
mActivity.showDialog("Downloaded " + result + " bytes");
}
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWorker = (Worker)getLastNonConfigurationInstance();
if (mWorker != null) {
mWorker.mActivity = this;
}
...
}
#Override
public Object onRetainNonConfigurationInstance() {
return mWorker;
}
#Override
protected void onDestroy() {
super.onDestroy();
if (mWorker != null) {
mWorker.mActivity = null;
}
}
void startWork() {
mWorker = new Worker(this);
mWorker.execute(...);
}
}
The reason is obvious: what if the
activity gets destroyed which
triggered the task?
Manually disassociate the activity from the AsyncTask in onDestroy(). Manually re-associate the new activity to the AsyncTask in onCreate(). This requires either a static inner class or a standard Java class, plus perhaps 10 lines of code.
It looks like AsyncTask is a bit more than just conceptually flawed. It is also unusable by compatibility issues. The Android docs read:
When first introduced, AsyncTasks were executed serially on a single background thread. Starting with DONUT, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting HONEYCOMB, tasks are back to being executed on a single thread to avoid common application errors caused by parallel execution. If you truly want parallel execution, you can use the executeOnExecutor(Executor, Params...) version of this method with THREAD_POOL_EXECUTOR; however, see commentary there for warnings on its use.
Both executeOnExecutor() and THREAD_POOL_EXECUTOR are Added in API level 11 (Android 3.0.x, HONEYCOMB).
This means that if you create two AsyncTasks to download two files, the 2nd download will not start until the first one finishes. If you chat via two servers, and the first server is down, you will not connect to the second one before the connection to the first one times out. (Unless you use the new API11 features, of course, but this will make your code incompatible with 2.x).
And if you want to target both 2.x and 3.0+, the stuff becomes really tricky.
In addition, the docs say:
Caution: Another problem you might encounter when using a worker thread is unexpected restarts in your activity due to a runtime configuration change (such as when the user changes the screen orientation), which may destroy your worker thread. To see how you can persist your task during one of these restarts and how to properly cancel the task when the activity is destroyed, see the source code for the Shelves sample application.
Probably we all, including Google, are misusing AsyncTask from the MVC point of view.
An Activity is a Controller, and the controller should not start operations that may outlive the View. That is, AsyncTasks should be used from Model, from a class that is not bound to the Activity life cycle -- remember that Activities are destroyed on rotation. (As to the View, you don't usually program classes derived from e.g. android.widget.Button, but you can. Usually, the only thing you do about the View is the xml.)
In other words, it is wrong to place AsyncTask derivatives in the methods of Activities. OTOH, if we must not use AsyncTasks in Activities, AsyncTask loses its attractiveness: it used to be advertised as a quick and easy fix.
I'm not sure it's true that you risk a memory leak with a reference to a context from an AsyncTask.
The usual way of implementing them is to create a new AsyncTask instance within the scope of one of the Activity's methods. So if the activity is destroyed, then once the AsyncTask completes won't it be unreachable and then eligible for garbage collection? So the reference to the activity won't matter because the AsyncTask itself won't hang around.
It would be more robust to keep a WeekReference on your activity :
public class WeakReferenceAsyncTaskTestActivity extends Activity {
private static final int MAX_COUNT = 100;
private ProgressBar progressBar;
private AsyncTaskCounter mWorker;
#SuppressWarnings("deprecation")
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async_task_test);
mWorker = (AsyncTaskCounter) getLastNonConfigurationInstance();
if (mWorker != null) {
mWorker.mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(this);
}
progressBar = (ProgressBar) findViewById(R.id.progressBar1);
progressBar.setMax(MAX_COUNT);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_async_task_test, menu);
return true;
}
public void onStartButtonClick(View v) {
startWork();
}
#Override
public Object onRetainNonConfigurationInstance() {
return mWorker;
}
#Override
protected void onDestroy() {
super.onDestroy();
if (mWorker != null) {
mWorker.mActivity = null;
}
}
void startWork() {
mWorker = new AsyncTaskCounter(this);
mWorker.execute();
}
static class AsyncTaskCounter extends AsyncTask<Void, Integer, Void> {
WeakReference<WeakReferenceAsyncTaskTestActivity> mActivity;
AsyncTaskCounter(WeakReferenceAsyncTaskTestActivity activity) {
mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(activity);
}
private static final int SLEEP_TIME = 200;
#Override
protected Void doInBackground(Void... params) {
for (int i = 0; i < MAX_COUNT; i++) {
try {
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(getClass().getSimpleName(), "Progress value is " + i);
Log.d(getClass().getSimpleName(), "getActivity is " + mActivity);
Log.d(getClass().getSimpleName(), "this is " + this);
publishProgress(i);
}
return null;
}
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
if (mActivity != null) {
mActivity.get().progressBar.setProgress(values[0]);
}
}
}
}
Why not just override the onPause() method in the owning Activity and cancel the AsyncTask from there?
You are absolutely right - that is why a movement away from using async tasks/loaders in the activities to fetch data is gaining momentum. One of the new ways is to use a Volley framework that essentially provides a callback once the data is ready - much more consistent with MVC model. Volley was populised in the Google I/O 2013. Not sure why more people aren't aware of this.
Personally, I just extend Thread and use a callback interface to update the UI. I could never get AsyncTask to work right without FC issues. I also use a non blocking queue to manage the execution pool.
I thought cancel works but it doesn't.
here they RTFMing about it:
""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."
That does not imply, however, that the thread is interruptible. That's a
Java thing, not an AsyncTask thing."
http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d
You would be better off thinking of AsyncTask as something that is more tightly coupled with an Activity, Context, ContextWrapper, etc. It's more of a convenience when its scope is fully understood.
Ensure that you have a cancellation policy in your lifecycle so that it will eventually be garbage collected and no longer keeps a reference to your activity and it too can be garbage collected.
Without canceling your AsyncTask while traversing away from your Context you will run into memory leaks and NullPointerExceptions, if you simply need to provide feedback like a Toast a simple dialog then a singleton of your Application Context would help avoid the NPE issue.
AsyncTask isn't all bad but there's definitely a lot of magic going on that can lead to some unforeseen pitfalls.
As to "experiences working with it": it is possible to kill the process along with all AsyncTasks, Android will re-create the activity stack so that the user will not mention anything.

Categories

Resources