Es gibt bei der Entwicklung von Android Apps wohl kaum ein zweites Szenario, das so häufig Anwendung findet, so trivial zu sein scheint – zeitgleich jedoch so unglaublich herausfordernd zu implementieren ist, wie das effiziente und asynchrone Laden von Bildern in eine ListView. Unzählige Threads auf Stackoverflow, sowie ein unlängst von Google veröffentlichter Ratgeber über Best-Practices für den Umgang mit Bitmaps, unterstreichen diese Aussage. Gründe hierfür sind zum einen, dass das Android SDK zu diesem Zweck kaum vorgefertigte Hilfsklassen bereitstellt, die einem mit vertretbarem Entwicklungsaufwand eine brauchbare Lösung ermöglichen. Aber auch einige architekturelle Gegebenheiten, wie beispielsweise die grundlegende Funktionsweise eines Adapters, oder das Single-Thread-Modell (hier insbesondere die Tatsache, dass Views nur aus dem Main-Thread heraus manipuliert werden dürfen) sind einer einfachen Lösung wenig förderlich. Dabei könnte für den Entwickler doch alles so einfach sein, hätte er nur eine Methode setImageFromWeb(URI) – oder nicht? In diesem Artikel möchte ich darauf eingehen, wieso uns diese brillante Methode bislang vorenthalten wurde. Ich werde den Versuch unternehmen, die Komplexität des Problems etwas zu veranschaulichen und Gründe nennen, wieso das asynchrone Laden von Bildern in eine ListView alles andere als trivial ist.

Eine Vielzahl unterschiedlicher Sachverhalte erschwert das Erstellen einer wiederverwendbaren Komponente

Zunächst einmal muss man sich vor Augen führen, welche einzelnen Schritte für eine effiziente Implementierung notwendig wären. Hier wird schnell deutlich, dass Komponenten aus unterschiedlichsten Bereichen und Abstraktionsebenen benötigt werden. Dies möchte ich einmal skizzieren:

  1. Eine Liste (oder generell gesagt eine AdapterView) wird über einen Adapter befüllt. Wir benötigen also zunächst eine eigene Implementierung eines ListAdapters. Sinnvollerweise verwenden wir auch das View-Holder Pattern, um das Durchsuchen der View-Hierarchy auf ein absolutes Minimum zu reduzieren und machen von der Wiederverwendung recycleder Views gebrauch. Die Implementierung eines entsprechenden Callbacks, welcher bei erfolgreich geladenem Bild aufgerufen wird, sei der Vollständigkeit halber auch noch erwähnt.
  2. Außerdem benötigen wir ein- oder mehrere Working-Threads, die aus einer Queue heraus den eigentlichen Ladevorgang durchführen und im Anschluss, beispielsweise über einen Handler, den Callback benachrichtigen. Das Synchronisieren aller beteiligten Threads sei an dieser Stelle als Herausforderung noch einmal explizit genannt.
  3. Da das Laden von Daten über das mobile Netz in puncto Akkulaufzeit und Datenvolumen teuer sein kann, empfiehlt sich außerdem die Implementierung einer Caching-Strategie. Hier kann man einen in-memory-cache implementieren und/ oder über ein Caching auf einem externen Datenträger (SD-Card) nachdenken.
  4. Auch das eigentliche Laden der Daten über das Netz muss via HttpClient bzw. URLConnection realisiert werden. Letzteres ist laut Google seit Gingerbread übrigens die bessere Wahl. Möchte man das ganze dann auch noch robust und fehlertolerant implementieren, kann es sehr schnell komplex werden. Hierzu später mehr.
  5. Zu guter Letzt muss man aus den geladenen Daten noch entsprechende Bitmaps bzw. Drawables erstellen, welche dann in einer View gerendert werden können.

Zusammenfassend kann man sagen, dass die Vielzahl an beteiligten Komponenten unterschiedlichster Bereiche und Abstraktionsebenen, das Zusammenfassen in eine Komponente (was den Grundstein für die Wiederverwendung in anderen Projekten darstellt) erschwert. Diese Komponente müsste in vielerlei Hinsicht sehr flexibel und hochgradig konfigurierbar sein. Dies betrifft insbesondere das Caching, sowie das Laden der Bilder. Der Preis für diese Konfigurierbarkeit wäre dann eine Menge boilerplate code, der für den Setup Aufwand notwendig wäre. Dies könnte mitunter ein Grund sein, wieso dem Entwickler seitens SDK nicht bereits eine funktionsfähige Allround-Lösung bereitgestellt wird.

Äußere Faktoren und Anforderungen bestimmen die Wahl der Implementierung

Häufig hat man mehrere Möglichkeiten, ein Problem zu lösen. Die Wahl der richtigen Implementierung bzw. Konfiguration wird häufig sehr stark von den äußeren Faktoren und Anforderungen bestimmt. Anders ausgedrückt können das Umstände und Zustände sein, die wir als gegeben ansehen und entsprechende Abweichungen ausschließen können. Um dies klarer zu machen, hierzu einige Beispiele*:

  • Caching von Daten: Daten 1) können auf einen externen Speicher geschrieben werden. Dieser ist 2) immer verfügbar und stellt 3) praktisch unbegrenzt Speicherplatz zur Verfügung. Ein 5) Aufräumen alter Daten ist demnach nicht erforderlich. Daten können 5) jederzeit von diesem Speicher gelesen werden. Wenn eine Datei vorhanden ist, so ist sie 6) noch aktuell und muss nicht erneut geladen werden. Die Daten sind 7) nicht sensitiv und müssen demnach nicht verschlüsselt werden. Der Zugriff auf den externen Speicher 8 ) ist vergleichsweise schnell und muss nicht in einem gesonderten Thread erfolgen. Generell sollte man 9) Caching wann immer möglich verwenden. Cachen würde man dann natürlich 10) die Daten, die man geladen hat und nicht etwa Teile/ Derivate davon (z.B: Thumbnail).
  • Laden von Bildern: Wir können davon ausgehen,dass wir eine 1) schnelle und 2) stabile Verbindung ins Internet haben. URIs zu den Bildern, die geladen werden sollen, 3) zeigen auf eine vorhandene Ressource, genauer gesagt 4) auf ein Bild, 5) das wir dekodieren können. Dieses ist bereits 6) entsprechend skaliert, sodass uns beim Einlesen der Daten nicht der Speicher voll läuft. Für den Zugriff auf die URL benötigen wir 7) weder Credentials, 8 ) noch ein gültiges Server-Zertifikat, oder gar 9) ein Client-Zertifikat.

* Jede dieser gertroffenen Annahmen kann in einem realen System durchaus falsch sein! Wir haben damit ein einfaches, um nicht zu sagen ein ideales System beschrieben. Wir haben dazu eine Menge Annahmen getroffen, die uns die Implementierung sehr einfach machen würde. In einem realen System würde unsere Komponente jedoch kaum Einsatz finden, da sie nicht über die notwendige Fehler-Toleranz verfügen würde und einige Annahmen teilweise auch zu speziell wären, als dass sie universell einsetzbar wären. Die Liste von Annahmen ließe sich dabei noch beliebig erweitern: Von „sehr wahrscheinlichen“ bis hin zu „eher unwahrscheinlichen“ Annahmen – und das nicht nur in den oben aufgeführten Modulen. Je weniger Annahmen man als gegeben akzeptiert und diese damit entsprechend programmatisch behandeln muss, umso aufwendiger wird die Implementierung, bzw. der Konfigurationsaufwand eine generischen Komponente. Nicht selten leidet auch die Performance des gesamten Moduls darunter: Nehmen wir als Beispiel einmal an, wir verzichten auf die Annahme, ein gecachtes Bild sei immer aktuell, da es sich auf dem Server nie ändern wird. Dann müssten wir beim Laden aus dem Cache immer auch eine Prüfung auf Aktualität (via Http Request) durchführen. Aber auch die Annahme, ein Bild würde immer richtig skaliert vom Server kommen, würde in den meisten Fällen unnötig viel Zeit für eine notwendige Skalierung kosten.

Fazit

Ich bin der Meinung, dass sowohl die Anforderungen an ein System als auch die äußeren Faktoren teilweise zu stark variieren, als dass man eine funktionsfähige, out-of-the-box Komponente für das Laden von Bildern in einer ListView einsetzen könnte. Ich denke zwar, dass dies prinzipiell schon möglich ist – diese würde jedoch imho nicht die oben beschriebene Konfigurations-Vielfalt erlauben und man müsste in vielerlei Hinsicht Kompromisse eingehen. Daher wäre eine generische Komponente vielleicht nicht immer die beste Lösung. Für viel wichtiger halte ich, dass man sich als Entwickler der Komplexität der Thematik bewusst ist, etwaige Stolpersteine, sowie unterschiedliche Lösungsansätze kennt und diese je nach äußeren Faktoren richtig einzusetzen weiß. Ich hoffe, mit diesem Post nun den ersten Bereich hinreichend abgedeckt zu haben und freue mich bereits darauf, in einem weiteren Artikel über verschiedene Techniken, Kniffe und Best-Practices im Rahmen dieser Thematik berichten zu können.

0 Kommentare

Dein Kommentar

An Diskussion beteiligen?
Hinterlasse uns Deinen Kommentar!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.