doubleA
doubleA

Reputation: 2456

Async Task crashing on backbutton pressed.

What happens to an async task when the calling fragment is paused/removed? I am assuming that the task completes but it would be great of someone can just confirm that. I am trying to find a fix to my crash.

I have an async task that is a recursive image loader. The image loader is an inner class of the listFragment that it loads images for. The image loader works perfectly unless i press back button before it has finished loading all the images. When i press the back button before all the images are done loading the application crashes. I feel like the image loader loses access to the adapter but I am not sure if i am attacking this problem correctly. I am unsure of how to fix this issue within my async task. If someone could help me out. Even bounce an idea off my head to get me thinking differently.

Any help would be awesome. Thankyou for your time to even look.

ImageLoader

private class ImageLoaderTask extends
        AsyncTask<HashMap<String, Object>, Void, HashMap<String, Object>> {

    @Override
    protected HashMap<String, Object> doInBackground(
            HashMap<String, Object>... hm) {
        InputStream in = null;

        String url = (String) hm[0].get("image");
        int pos = (Integer) hm[0].get("pos");
        url = "http://kzfr.org/u/img/small/" + url;
        URL mUrl;

        try {
            mUrl = new URL(url);
            URLConnection urlConnect = (URLConnection) mUrl
                    .openConnection();
            in = urlConnect.getInputStream();

            File cacheDirectory = getSherlockActivity().getBaseContext()
                    .getCacheDir();

            File tmp = new File(cacheDirectory.getPath() + "/kzfr_small"
                    + pos + ".png");

            FileOutputStream fOut = new FileOutputStream(tmp);

            Bitmap b = BitmapFactory.decodeStream(in);
            b.compress(Bitmap.CompressFormat.PNG, 100, fOut);
            fOut.flush();
            fOut.close();

            HashMap<String, Object> hmBmp = new HashMap<String, Object>();
            hmBmp.put("blank_start", tmp.getPath());
            hmBmp.put("pos", pos);
            return hmBmp;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(HashMap<String, Object> result) {
        // this is the img path that leads to a local image that was just
        // saved on the phone.
        String imgPath = (String) result.get("blank_start");
        // the position of the image in the listview
        int position = (Integer) result.get("pos");
        // local adapter
        SimpleAdapter adapter = (SimpleAdapter) getListAdapter();
        // hashmap entry from the local adapter at the position of the image
        // that we just loaded.
        HashMap<String, Object> hm = (HashMap<String, Object>) adapter
                .getItem(position);
        // replace the actual blank image with the image path that we just
        // loaded
        hm.put("blank_start", imgPath);
        // update listview
        adapter.notifyDataSetChanged();
        // we are done with the current position increment position and move
        // on.
        position++;
        // this while loop finds the next position in the adapter that does
        // not have false for an image path. False indicates that there is
        // no image and i should load a default image.
        while (position < adapter.getCount()) {
            hm = (HashMap<String, Object>) adapter.getItem(position);
            imgPath = (String) hm.get("image");

            if (!imgPath.equalsIgnoreCase("false")) {
                break;
            }
            position++;
        }

        // checks to make sure that the position is not out of bounds on the
        // adapter. If in bounds then there might be more images to load.
        // Start a new ImageLoader. This is a recursive Class.
        if (position < adapter.getCount()) {
            ImageLoaderTask imageLoader = new ImageLoaderTask();

            hm.put("pos", (position));
            imageLoader.execute(hm);
        }

    }

StackTrace

01-14 20:09:28.752: E/AndroidRuntime(6069): FATAL EXCEPTION: main
01-14 20:09:28.752: E/AndroidRuntime(6069): java.lang.NullPointerException
01-14 20:09:28.752: E/AndroidRuntime(6069):     at com.appdomum.doublea.ListFrag$ImageLoaderTask.onPostExecute(ListFrag.java:272)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at com.appdomum.doublea.ListFrag$ImageLoaderTask.onPostExecute(ListFrag.java:1)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.os.AsyncTask.finish(AsyncTask.java:417)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.os.AsyncTask.access$300(AsyncTask.java:127)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:429)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.os.Handler.dispatchMessage(Handler.java:99)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.os.Looper.loop(Looper.java:130)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.app.ActivityThread.main(ActivityThread.java:3806)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at java.lang.reflect.Method.invokeNative(Native Method)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at java.lang.reflect.Method.invoke(Method.java:507)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at     com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at   com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at dalvik.system.NativeStart.main(Native Method)

Upvotes: 3

Views: 2006

Answers (2)

andr
andr

Reputation: 16054

I think you're on the right track here.

When writing custom AsyncTasks many developers forget, that it is necessary to handle task cancellation. Simply cancelling the task doesn't mean that its code won't execute on (for example) Activity/Fragment resources, that might be freed up at the time. This is typically done in these steps:

  1. After creating a task, save a reference to it.
  2. Cancel all running tasks after Activity/Fragment is finished. This is typically done in Activity.onDestroy()/Fragment.onDestroy(). Remove all references to cancelled tasks.
  3. In task's code:
    1. In doInBackground() periodically check if it isCancelled(). If so, tidy up and abort further execution.
    2. Eventually handle cancellation in onCancelled() (runs on UI thread).
    3. Perform any handling of cancelled task in onPostExecute() (which is executed with a null result if the task was cancelled).

Upvotes: 4

devunwired
devunwired

Reputation: 63293

What happens to an async task when the calling fragment is paused/removed? I am assuming that the task completes but it would be great of someone can just confirm that.

That is incorrect; the framework does not do the work for you by canceling work you are doing in background threads. The reason callbacks like onPause() and onResume() are called is to notify your application that you need to clean up and cancel any running work that is not in a long-lived component like a Service.

In this case, if you don't cancel the task is continues to run. Since this class is defined as a non-static inner class, it also means you've created a temporary memory leak because the AsyncTask is holding an implicit reference to the Fragment, and since the task is attached to a running thread neither object (or their members) can be garbage collected even after onDestroy().

You need to implement your AsyncTask in such a way that it will check for the cancel flag you can manually cancel the work from onPause() or a similar callback.

Upvotes: 1

Related Questions