5

It is quite common to spawn a time consuming computation thread. Later, we require to update Activity or Fragment with computation result.

All the while, I'm following the below guidelines. It works well for me till now.

AsyncTask needs to onPostExecute UI Fragment

  1. Use setRetainInstance(true) UI-less fragment.
  2. Use setTargetFragment and getTargetFragment technique
  3. Please refer to https://stackoverflow.com/a/12303649/72437

AsyncTask needs to onPostExecute UI Activity

  1. Use setRetainInstance(true) UI-less fragment.
  2. Use onAttach and onDetach to store reference to Activity. Google seems doesn't encourage using getActivity. http://developer.android.com/guide/components/fragments.html
  3. Please refer to https://stackoverflow.com/a/16305029/72437

However, how about case for a class derived from View? I plan to launch AsyncTask from the custom View. However, how can I onPostExecute back to the View?

The reason I'm asking so is, in my custom view, certain touch event will trigger it to redraw itself with a new bitmap. Generating the new bitmap is time consuming. Hence, I plan to launch a AsyncTask, to generate such bitmap, and pass back to custom View. However, configuration change might cause custom View to be recreated. Hence, I need to ensure my AsyncTask can have correct View reference during onPostExecute.

Community
  • 1
  • 1
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
  • Are you asking how to retain an `AsyncTask` that lives inside of a `View` across a configuration change? – Alex Lockwood Nov 13 '14 at 16:07
  • 2
    no, no, no ... View derived classes should not know that something like threadings even exists ... they should also not be able to get data by it self, they should be "feeded" ... so the best practice is: **do not doing this at all** – Selvin Nov 13 '14 at 16:08
  • 1
    @Selvin I wouldn't go as far to say that views should never know about threads. Romain Guy even gives an example of a View that needs to clean up its threads in [this Google IO talk](http://youtu.be/NYtB6mlu7vA?t=5m59s) on custom views (see 5:59). That said, you are right that there are many cases where it might be better programming design to put the threads in the activity/fragment instead. It just depends on the use-case. – Alex Lockwood Nov 13 '14 at 16:12
  • 2
    @AlexLockwood Sort of. As in my custom view, certain touch event will trigger it to redraw itself with a new bitmap. Generating the new bitmap is time consuming. Hence, I wish to know the correct way, to proper manage threading from custom View. All the while, I manage threading from Activity & Fragment. But, I never did this on custom View. – Cheok Yan Cheng Nov 13 '14 at 16:13
  • View's don't know anything about configuration changes... that is something activities and fragments should worry about. If the view's thread _really_ must be retained, you'd have to move that logic up into the activity/fragment (or generate the bitmaps inside a service, etc.). – Alex Lockwood Nov 13 '14 at 16:26
  • after your edit i would like to say: take a look at SurfaceView source ... it is similar to your example (OpenGL has it is own thread) – Selvin Nov 13 '14 at 16:29

2 Answers2

5

Assuming that you're using AsyncTask only for drawing-related operations (otherwise you should really revisit your logic - as comments suggest), you can create AsyncTask directly in your custom View class:

class MyView extends View {

    private MyAsyncTask currentTask = null;

    // View details

    @Override
    public void onAttachedToWindow() {
        currentTask = new MyAsyncTask(this);
        currentTask.execute();
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (currentTask != null) {
            currentTask.cancel(true);
            currentTask = null;
        }
    }

    static class MyAsyncTask extends AsyncTask<Void, Void, Bitmap> {

        private WeakReference<MyView> viewRef;

        MyAsyncTask(MyView view) {
            viewRef = new WeakReference<>(view);
        }

        // background task implementation

        @Override
        public void onPostExecute(Bitmap bitmap) {
            MyView view = viewRef.get();
            if (view == null) {
                return;
            }

            // you now can safely update your view - we're on UI thread
        }

    }

}

That's how safe implementation would look like. It has some disadvantages and important parts:

  • At no point your AsyncTask should hold strong reference to View (that's why class is declared as static and holds WeakReference to View)
  • When you're not interested in result from AsyncTask anymore - cancel it
  • This implementation will just throw away possibly useful result from cancelled AsyncTask. If that's the issue - I would suggest to remove AsyncTask from View completely and search for others solutions (separate Executor or HandlerThread).

Also onPostExecute of AsyncTask will be called from the same looper thread which launched it (in your case that's main thread, so it does not matter if you start it from Activity or View, or wherever else, it all depends on how hard it would be to manage those tasks).

Dmitry Zaytsev
  • 23,650
  • 14
  • 92
  • 146
0

I'll give you a general idea that you can go ahead an apply anywhere you need (including from Thread or a ThreadExecutor (instead of relying only on AsyncTask);

you can directly use a library to handle events, where events are dispatched in a common "bus", and any class that wants can register to the bus and listen to those events:

and for that I'll reference you to Otto, it works nice and it's very powerful: http://square.github.io/otto/

alternatively you can implement it yourself with the LocalBroadcastManager https://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html like this:

LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context) < that's to get a ref to it.

onEventComplete: (happens on your thread or AsyncTask

Intent i = new Intent("DOWNLOAD_COMPLETE");
// ps. feel free to attach extras to the intent, in order to pass data back to your activity/fragment/view
lbm.sendBroadcast(i); 

then on your activity/fragment/view you create a receiver

BroadcastReceiver receiver = new BroadcastReceiver(){
    @Override
    public void onReceive (Context context, Intent intent){
       // feel free to read the extras from the intent with data here and update your view
    }
};

onStartListening:

lbm.registerReceiver(receiver, new IntentFilter("DOWNLOAD_COMPLETE"));

onStopListening:

lbm.unregisterReceiver(receiver);

then you MUST start and stop listening during onStart/onStop or onResume/onPause or onAttachedToWindow/onDetachedFromWindow (for views)

Budius
  • 39,391
  • 16
  • 102
  • 144