Best practice of running AsyncTask from a custom view

Very often there is an irregular flow of calculations. Later we will need to update the Activity or Fragment with the result of the calculation.

All the time I follow the recommendations below. It works for me so far.

AsyncTask required for onPostExecute UI fragment

  • Use the setRetainInstance(true) fragment without an interface.
  • Use the setTargetFragment and getTargetFragment
  • Refer to https://stackoverflow.com>

AsyncTask must run onPostExecute UI Activity

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

The reason I ask, in my user view, a specific touch event will cause it to redraw with a new bitmap. Creating a new bitmap takes a lot of time. Therefore, I plan to launch AsyncTask, create such a bitmap and return to the user view. However, a configuration change may cause the user view to be recreated. Therefore, I need to make sure that my AsyncTask can have the correct View link during onPostExecute .

+5
source share
2 answers

Assuming that you use AsyncTask only for drawing-related operations (otherwise you should really rethink your logic - as follows from the comments), 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 } } } 

What a safe implementation would look like. It has some disadvantages and important parts:

  • In no case should your AsyncTask contain a strong reference to the View (that the class is declared as static and holds WeakReference until View )
  • If you are no longer interested in the AsyncTask result, cancel it.
  • This implementation will simply throw the useful result out of the canceled AsyncTask . If this is a problem, I would suggest completely removing AsyncTask from View and looking for other solutions (separate Executor or HandlerThread ).

Also onPostExecute of AsyncTask will be called from the same looper thread that started it (in your case, this main thread, so it doesn’t matter if you start it with Activity or View ) or anywhere else it all depends on how difficult it would be manage these tasks).

+3
source

I will give you a general idea that you can apply everything you need (including from Thread or ThreadExecutor (instead of relying solely on AsyncTask);

you can directly use the library to handle events , where events are sent on a common "bus", and any class can register on the bus and listen to these events:

and for this I will refer to Otto, it works well and is very powerful: http://square.imtqy.com/otto/

otherwise you can implement it yourself using LocalBroadcastManager https://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html as follows:

LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context) <to get a link to it.

onEventComplete: (happens in your stream or in 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 in 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)

0
source

Source: https://habr.com/ru/post/1206817/


All Articles