What happens to an AsyncTask when the launching activity is stopped/destroyed while it is still running?
Asked Answered
G

2

9

I've seen few questions nearly identical to mine, but I couldn't find a complete answer that satisfies all my doubts.. so here I am.. Suppose that you have an activity with an inner class that extends the AsyncTask class like this:

public class MyActivity extends Activity {            
    private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { 
        protected Bitmap doInBackground(String... urls) {
            return DownloadImage(urls[0]); 
        }
        protected void onPostExecute(Bitmap result) {
            ImageView img = (ImageView) findViewById(R.id.img); 
            img.setImageBitmap(result);
        }  
    }

    @Override
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main);
        new DownloadImageTask().execute("http://mysite.com/image.png")
    }
}

Suppose that the activity is paused or destroyed (maybe the two cases are different) while the DownloadImageTask is still running in background.. then, the DownloadImageTask's methods that run on the activity UI thread can be triggered and the DownloadImageTask may try to access Activity's methods (it is an inner class, so it can access the methods and instance variables of the outer class) with a paused or destroyed Activity, like the call to findViewByID in the example below.. what happens then? Does it silently fail? Does it produce any exception? Will the user be notified that something has gone wrong?

If we should take care that the launching thread (the Activity in this case) is still alive when running-on-UI methods are invoked, how can we accomplish that from within the AsyncTask?

I'm sorry if you find this as a duplicate question, but maybe this question is a bit more articulated and someone can answer with greater detail

Germinant answered 24/8, 2012 at 21:41 Comment(8)
When you ran this code, did it silently fail, produce any exception, or notify the user something has gone wrong?Catbird
I've not run such a code yet, I'll do it the next few days.. I was wondering about it before going on in my app's implementation. I'll update this post as soon as I can run a test. Thank you @CatbirdGerminant
My point is that the behavior is undocumented, particularly given the fact that we're not seeing anything else in the activity. findViewById() might return null for R.id.img, or it might not. And that behavior could conceivably vary from device to device, based on Android OS release, device manufacturer alterations, and ROM mods.Catbird
It seems strange to me that the documentation does not say anything about what you should do in such a situation... One of the main uses of AsyncTask is to do something in background and then do something in the UI thread, i.e. update the interface or build a dialog to inform the user.. I'll do some tests to understand better..Germinant
"It seems strange to me that the documentation does not say anything about what you should do in such a situation" -- I'd start by looking at the return value from findViewById() rather than blindly assuming success. I'd also strongly consider looking at using retained fragments to manage your AsyncTasks, or otherwise teaching your AsyncTask that activities can come and go while the background thread is in operation (e.g., user rotates the screen).Catbird
@Catbird this is what I do now: when I create the AsyncTask I'll pass to it a reference to the Activity, then in the Activity onPauseI call a method activityNoMoreAvailable() onto the AsyncTask class, which sets the Activity reference to null and invokes the cancel() method on the AsyncTask. In the onPostExecute method of the AsyncTask I first check if Activity is not null and I also check the return values of findViewById() (which at this point should not be necessary, right?). When onPostExecute() is executing I'm sure no other Activity's methods like (continue..)Germinant
@Catbird (continues from above) onPause are executing, i.e. if I check the Activity reference in the onPostExecute() method and it is set to null, I am sure it will not become null during all the execution of this method since this method and the Activity's methods are all run on the main UI thread and can not be run concurrently. Is it all right?Germinant
That all sounds fairly reasonable.Catbird
U
3

Consider this Task (where R.id.test refers to a valid view in my activity's layout):

public class LongTaskTest extends AsyncTask<Void, Void, Void>{
    private WeakReference<Activity> mActivity;
    public LongTaskTest(Activity a){
        mActivity = new WeakReference<Activity>(a);
    }
    @Override protected Void doInBackground(Void... params) {
        LogUtil.d("LongTaskTest.doInBackground()");
        SystemClock.sleep(5*60*1000);
        LogUtil.d("mActivity.get()==null " + (mActivity.get()==null));
        LogUtil.d("mActivity.get().findViewById(R.id.frame)==null " + (mActivity.get().findViewById(R.id.test)==null));
        return null;
    }
}

If I run this task from an Activity's onCreate like so:

public class Main extends Activity {
    @Override
    public void onCreate(Bundle state) {
        super.onCreate(state);
        setContentView(R.layout.testlayout);
        new LongTaskTest(this).execute();
        finish();
    }
}

No matter how long I sleep the background thread, my log always shows:

LongTaskTest.doInBackground()
mActivity.get()==null false
mActivity.get().findViewById(R.id.frame)==null false

Which is to say that the activity and its views appear to stay alive (even if I manually issue GCs via DDMS). If I had more time I'd look at a memory dump, but otherwise I don't really know why this is the case ... but in answer to your questions it appears that:

  • Does it silently fail? No
  • Does it produce any exception? No
  • Will the user be notified that something has gone wrong? No
Uptown answered 4/9, 2012 at 16:10 Comment(3)
The activity may still be there because your inner class has a reference to it, but how about the views and instance variables in it?Sidneysidoma
That's why I used a WeakReference to Activity ... to ensure that my inner class wasn't the reason Activity was kept alive. As to views and instance variables, my inner class has none except for the WeakReference to Activity. Unless you mean something else? more on WeakReference: developer.android.com/reference/java/lang/ref/…Uptown
@Uptown If in fact LongTaskTest is an inner class, then it has an implicit reference to Activity that you are forgetting - make it a static nested class: #70824Phiz
M
2

The doInBackground() will keep on running even if your Activity gets destroyed(i,e your main thread gets destroyed) because the doInBackground() method runs on the worker's/background thread. There will be a 'problem' in running the onPostExecute() method as it runs on the main/UI thread and you may experience running into unrelated data but there will be no exception shown to the user. Thus, it is always better to cancel your AsyncTask when your activity gets destroyed as there is no reason to run AsyncTask when the Activity is no longer present. Use android Service if you continuously want to download something from the network even when your Component/Activity gets destroyed. Thanks.

Middleweight answered 18/4, 2015 at 10:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.