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.

Samstag, 18. Februar 2012

AsyncTask und AsyncTaskLoader

Sobald man größere Anwendungen auf der Android-Plattform entwickelt, wird man relativ schnell feststellen, dass sich das System weigert, besonders laufzeitzeit-intensive Aufgaben im UI-Thread abzuarbeiten. Dazu zählen Zugriffe über das Netzwerk, aber auch auf das Filesystem, auf Preferences oder auf die Datenbank. Abhängig vom API-Level führt das zu einer entsprechenden Meldung („Application not responding“) oder sogar zu einer Exception.

Die entsprechende Aufgabe ist also in einen asynchronen Task auszulagern. Nicht selten wird so eine Aufgabe nach erfolgreicher Abarbeitung aber wieder auf das UI zugreifen müssen, beispielsweise um die Ergebnisse darzustellen. Da von diesem Task nicht wieder auf den UI-Thread zugegriffen werden darf, ist die Synchronisation Teil des Problems.

Die für einen Java-Entwickler vertraute Herangehensweise wäre, einfach einen Java-Thread zu starten. Aus diesem Thread kann allerdings nicht auf die UI zugegriffen werden. Dafür stellt die Android-Activity eine „runOnThread“-Methode zur Verfügung. Dieser wird ein Runnable übergeben, dass die Aufgaben im UI-Thread erledigt. Schematisch würde das so aussehen:


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();


Ein wenig eleganter und mehr „Android-like“ ist die Verwendung des "AsyncTask<>". Davon wird eine Subklasse gebildet und einige Methoden sind zu implementieren. Über die Template-Parameter werden die Parameter des „doInBackground“-Aufrufs, der Parameter der „onProgressUpdate“-Methode und der Typ des Rückgabewertes für die „onPostExecute“-Methode parametriert. Letztere wird dann im Kontext des UI-Threads aufgerufen.


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
      // ...
   }
}


Eine weitere Möglichkeit zur Lösung des Problems würde für die Aufgabe einen speziellen Service implementieren. Die Kommunikation zwischen Service und UI-Thread würde dann allerdings über Intents/BroadcastReceiver erfolgen, was die Implementierung recht komplex und unübersichtlich macht.

Alle diese Vorgehensweisen haben den Nachteil, dass berücksichtigt werden muss, ob die Activity vorzeitig beendet wird. Das kann ja beispielsweise schon durch Drehen des Devices schnell mal der Fall sein. Dann würde die ausgelagerte Aufgabe in ihrem Thread weiterlaufen. Die Verarbeitung der Ergebnisse führt dann schnell zu unerwünschten Ergebnissen.

Ab Android 3.0, API-Level 11 kommt mit dem „Loader“-Konzept eine neue Möglichkeit ins Spiel. Unter Verwendung der V4-Support-Library ist das allerdings auch für frühere API-Level verwendbar. Loader werden von Android durch einen Loader-Manager verwaltet, der beispielsweise auch den erwähnten Fall des vorzeitigen Beendens der Activity berücksichtigen soll. Der Name deutet es bereits an, der Loader ist speziell für solche Fälle vorgesehen, wo durch Zugriff auf (externe) Ressourcen Daten geladen werden sollen. Für den beschriebenen Fall ist speziell der "AsyncTaskLoader<>" interessant.

Es wird wieder eine Subklasse gebildet, mit dem Typ des Returnwertes der Ladeoperation als Parameter des Templates. Die verwendende Klasse sollte das Interface "LoaderManager.LoaderCallbacks<>" implementieren, dass auch die Methoden für den Callback definiert. Außerdem ist dort eine „onCreateLoader“-Methode vorgesehen, über die abhängig von einem Parameter der passende Loader instantiiert werden muss. Die eigentliche Instanz wird nur über den LoaderManager erzeugt.

Der eigentliche Loader muss nur die „loadInBackground“-Methode implementieren. Parameter können der Loader-Klasse über den Konstruktor übergeben werden. Schematisch sieht das dann folgendermaßen aus:


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();
   }
}


Welche der beschriebenen Möglichkeiten gewählt wird, hängt natürlich nicht zuletzt vom jeweiligen Kontext ab. Der Loader-Mechanismus ist in einigen Fällen vielleicht zu unflexibel, da er speziell für das Laden von Daten vorgesehen ist. Da er aber auf einem universellen Konzept von Android aufsetzt, wird er sicherlich zukünftig vermehrt in den Fokus rücken.