Sonntag, 26. Februar 2012

AsyncTask and AsyncTaskLoader

As soon as you develop some larger application based on the android platform, you will soon discover, that the system refuses to execute runtime-extensive tasks in the UI thread. This are accesses via the network, but also to the file system, to preferences and to the database. Depending on the API level it leads to a corresponding message ("application not responding") or even to an exception.

So the relevant topic has to be rolled out to a asynchronous task. Not rarely such a task will have to change the UI after being completed successfully, for instance to show the results. Since it is not possible to access the UI thread from this thread again, the synchronisation is part of the problem.

The familiar approach for a Java developper would be simply to start a java thread. But from this thread it is not possible to access the UI. For this case the android Activity provides a "runOnThread" method. This given a Runnable to process the tasks in the UI thread. Schematically it would look like this:

Thread thread = new Thread(new Runnable() {
   public void run() {
      // ...
      // do long running tasks here
      // …

      // running in Activity context
      runOnUiThread(new Runnable() {
         public void run() {
            // ...
            // update UI here
            // ...
         }
      });
   }
});
thread.start();


A little more elegant and "android like" is the usage of the "AsyncTask<>". It will be subclassed and some methods have to be implemented. The template parameters define the parameters of the "doInBackground" call, the "onProgressUpdate" method and the type of the return value for the "onPostExecute" method. The latter will be called in the context of the UI thread.


public void useTask() {
   new FooTask().execute("param1", "param2");
}

class FooTask extends AsyncTask {
   protected String doInBackground(String... params) {
      int progress = 0;
      String result = params.length > 0 ? params[0] : "";
      // ...
      // do long running tasks here
      // ...
      publishProgress(progress);
      // ...
      // work
      // ...
      return result;
   }
   protected void onProgressUpdate(Integer... progress) {
      // ...
      // propagate progress to UI
      //
   }
   protected void onPostExecute(String result) {
      // ...
      // update UI here
      // ...
   }
}


Another possibility to solve the problem would be to implement a specific service for the task. The communication between service and UI thread would be done via Intents and BroadcastReceiver, making the implementation quite complex and unclear.

All these approaches have the downside, that it has to be reagarded, if the Activity is terminated prematurely. This can soon be the case for instance just by turning the device. The asynchronous task in this case would continue running in its thread. The processing of the results could soon lead to unrequested results.

Since Android 3.0, API-Level 11 another possibility enters the game with the "Loader" concept. By using the V4 support library it is also available for earlier API levels. Loader in Android are managed by a Loader manager, that for instance also should handle the case of premature termination of the Activity. The name implicates that the Loader specifically is destined for such topics where by access of some (external) resource data has to be loaded. For the case described in this post, specifically the "AsyncTaskLoader<>" is relevant.

Again it will be subclassed with the type of the return value of the loading operation as parameter of the template. The class to use the loader should implement the interface "LoaderManger.LoaderCallbacksy<>" that defines the methods for the callback. Additionally there is defined a "onCreateLoader"
method, that has to instatiate the appropriate Loader - depending on its parameter. The proper instance will always and only be requested via the LoaderManager.

The Loader subclass just has to implement the "loadInBackground" method. Parameters may be propagated to the Loader subclass via the constructor.
Schematically it looks like this:

class FooLoader extends AsyncTaskLoader {
   public FooLoader(Context context, Bundle args) {
      super(context);
      // do some initializations here
   }
   public String loadInBackground() {
      String result = "";
      // ...
      // do long running tasks here
      // ...
      return result;
   }
}

class FooLoaderClient implements LoaderManager.LoaderCallbacks {
   Activity context;
   // to be used for support library:
   // FragmentActivity context2;
   public Loader onCreateLoader(int id, Bundle args) {
      // init loader depending on id
      return new FooLoader(context, args);
   }
   public void onLoadFinished(Loader loader, String data) {
      // ...
      // update UI here
      //
   }
   public void onLoaderReset(Loader loader) {
      // ...
   }
   public void useLoader() {
      Bundle args = new Bundle();
      // ...
      // fill in args
      // ...
      Loader loader = 
         context.getLoaderManager().initLoader(0, args, this);
      // with support library: 
      // Loader loader = 
      //    context2.getSupportLoaderManager().initLoader(0, args, this);
      // call forceLoad() to start processing
      loader.forceLoad();
   }
}


Which one of the possibilities described you choose, depends on the specific context. The Loader mechanism might be inflexible in some cases, since it is intended for loading data specifically. Since it uses a general concept of Android, it will surely get more into focus in the future.

Kommentare:

  1. Thnx for Brief Explanation

    AntwortenLöschen
  2. how to use progressState for a progressdialogfragment for example?

    AntwortenLöschen
  3. Much better explained than the documentation

    AntwortenLöschen
  4. Excellent post thanks. Your AsyncTaskLoader example is much simpler and easier to understand than the one in the Android documentation.

    AntwortenLöschen
  5. Really nice post!

    As Bluemercury mentions the progressdialog, I am also curious about this. Currently I use a specially constructed dialogfragment with an AsyncTask to handle progress dialog updates and orientation changes gracefully.

    Would AsyncTaskLoader enable getting rid of this rather tedious approach?

    AntwortenLöschen
  6. How to use Loader for server call??

    AntwortenLöschen