Android image loaders

Es ist fast unmöglich, eine kommerzielle Android-Anwendung zu finden, die keine Bilder in ihrer Schnittstelle verwendet. Das Herunterladen eines Bildes in die entsprechende Ansicht kann trotz der scheinbaren Einfachheit des Vorgangs große Probleme für den Entwickler verursachen. Was ist zum Beispiel, wenn das Bild auf dem Server gespeichert ist? Um es auf dem Bildschirm anzuzeigen, müssen Sie es zunächst in den lokalen Speicher hochladen, es dann in das gewünschte Format konvertieren und es als Attribut in die Ansicht einfügen. Die Liste der Aufgaben wird erheblich länger, wenn wir die bereits hochgeladenen Bilder wiederverwenden oder den Platzhalter anzeigen wollen, während das gewünschte Bild heruntergeladen wird. Daher verwenden die meisten Android-Projekte spezielle Bibliotheken – Bild-Downloader, die es Ihnen ermöglichen, diesen endlosen Kreislauf zu durchbrechen. Die gängigsten für 2020 sind Picasso, Glide und Fresco.

Wurde von Square entwickelt, das in der Android-Community für so beliebte Bibliotheken wie Retrofit, OkHttp und Leak Canary bekannt ist. Die Schöpfer dieser Bibliothek legten den Schwerpunkt auf die Einfachheit und Verständlichkeit der Arbeit. Der minimalistische Ansatz macht es möglich, die Größe der apk-Datei nicht aufzublähen – Picasso benötigt nur 121 Kb. Die Bibliothek spart auch die Anzahl der Methoden in der Anwendung und fügt nur 849 Methoden hinzu. Dadurch wird der Moment der Umwandlung der Anwendung in eine Multidex-Datei aufgeschoben.

ist ein Produkt von Bump Tech. Eine der herausragenden Eigenschaften der Bibliothek ist ihre leistungsstarke Funktionalität, die es Ihnen ermöglicht, komplexe Bildtransformationen zu implementieren. Diese haben jedoch ihren Preis – eine größere Anwendung: Glide kommt auf 440 Kb und 2678 Methoden. Daher ist die Nische von Glide bei der Entwicklung von Android-Anwendungen ein Programm mit komplexer Bilddarstellungslogik. Es ist jedoch auch durchaus möglich, einfachere Lösungen mit seiner Hilfe zu implementieren.

Wurde von Facebook-Ingenieuren entwickelt. Beim Laden von Bildern konzentriert sich diese Bibliothek auf den effizienten Umgang mit Speicher und Produktivität. Besonders signifikant war der Vorsprung gegenüber anderen Bild-Downloadern auf Geräten mit Android 4. Inzwischen hat sich der Abstand verringert, aber Fresco ist in diesem Bereich immer noch führend. Die zweite wichtige Eigenschaft von Fresco ist die Verwendung der eigenen SimpleDraweeView anstelle der traditionellen ImageView.

Ein Vergleich der einfachen Funktionen dieser Bibliotheken (Laden von Ressourcen, Fehlerbehandlung, Platzhalter) wird dem Android-Entwickler nicht viel bringen, da diese Funktionen intuitiv sind und in nur einer Stunde erlernt werden können. Interessanter wäre es, die fortgeschrittenen Funktionen von Picasso, Glide und Fresco zu vergleichen: Zwischenspeicherung und Bildtransformation sowie die einzigartigen Werkzeuge der einzelnen Bibliotheken.

Bild-Caching

Picasso unterstützt standardmäßig 2 Arten von Cache: LRU-Cache mit einer Größe von 15 % des der Anwendung zur Verfügung stehenden Arbeitsspeichers und Festplatten-Cache mit einer Größe von 5 bis 50 MB (abhängig von der verfügbaren Größe des Nur-Lese-Speichers). Die Funktionen memoryPolicy() und networkPolicy() werden zur Steuerung dieser Caches verwendet. Mit der ersten können Sie den Zugriff auf den Online-Cache während des Bildladens verweigern (Attribut MemoryPolicy.NO_CACHE) oder das heruntergeladene Bild nicht im Cache speichern (Attribut MemoryPolicy.NO_STORE). Mit Hilfe des zweiten Attributs wird der Festplatten-Cache gesteuert. Die Attribute NetworkPolicy.NO_CACHE und NetworkPolicy.NO_STORE funktionieren auf die gleiche Weise wie bei MemoryPolicy. Ein weiteres Attribut – NetworkPolicy.OFFLINE – weist Picasso an, Daten nur aus dem Cache herunterzuladen, ohne auf das Netzwerk zuzugreifen.

Eine solche Anforderung zwingt Picasso beispielsweise dazu, ständig Bilder aus dem Netzwerk herunterzuladen.

 Picasso.get()
.load(imageUrl)
.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
.networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE)
.into(test_imageview)

Das Cache-Schema in Glide ist komplexer. Es hat 4 Ebenen:

  1. aktive Ressourcen (werden jetzt in jeder Ansicht angezeigt);
  2. Speicher-Cache (Daten im RAM);
  3. Ressource (konvertiertes und dekodiertes Bild);
  4. Daten (auf der Festplatte gespeichertes Rohbild).

Standardmäßig sucht Glide vor dem Zugriff auf eine externe Ressource nach einem geeigneten Bild auf diesen Ebenen. Mit diesem Ansatz kann ein und dasselbe Bild mehrere Versionen haben. Wir verwenden zum Beispiel dasselbe Bild sowohl in der Liste als auch in der Detailansicht. Im ersten Fall leiten wir jedoch einen Teil des Bildes ab, indem wir den mittleren Ausschnitt verwenden, im zweiten Fall zeigen wir es vollständig. Der Cache wird 2 Versionen dieses Bildes enthalten. Um sie zu unterscheiden, verwendet Glide Schlüssel, die Daten über die Breite und Höhe des Bildes, die Transformationen, die hinzugefügten Optionen, den Datentyp usw. enthalten. Um den Cache zu verwalten, verwenden Sie die Funktion diskCacheStrategy(), die die enum-Argumente von DiskCacheStrategy. ALL (verwendet alle Cache-Ebenen und eine externe Ressource), DATA (schreibt Rohdaten in den Nur-Lese-Speicher), RESOURCE (schreibt dekodierte Daten in den Nur-Lese-Speicher), NONE (cached Daten nicht im permanenten Speicher).

Das vorherige Beispiel aus Picasso sieht dann so aus:

 Glide.with(this)
.load(imageUrl)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.into(test_imageview)

Wie Picasso erlaubt auch Glide, den Cache komplett oder für ein bestimmtes Bild zu löschen. Aber es ermöglicht auch eine genauere Steuerung durch die Verwendung der Funktion signature() und benutzerdefinierter Schlüssel.

Der Fresco-Cache ist ebenfalls hierarchisch aufgebaut und hat 3 Ebenen:

  1. Bitmap (dekodierte Bilder, bereit zur Anzeige oder Nachbearbeitung);
  2. Kodierter Speicher-Cache (speichert komprimierte Bilder im Originalzustand im Speicher);
  3. Disk (speichert komprimierte Bilder im Originalzustand im lokalen Speicher).

Die Bibliothek verwendet die ImagePipeline-Klasse, um den Cache zu verwalten. Sie bietet die Möglichkeit, das Vorhandensein von Bildern im Cache zu überprüfen, zwischengespeicherte Bilder abzurufen oder sie zu löschen.

 val imagePipeline = Fresco.getImagePipeline()

// Check if image is in cache
val inMemoryCache = imagePipeline.isInBitmapMemoryCache(uri)

// Get cached image
imagePipeline.evictFromMemoryCache(uri)
imagePipeline.evictFromDiskCache(uri)

// Clear cache
imagePipeline.clearMemoryCaches()
imagePipeline.clearDiskCaches()

Eine interessante Funktion von Fresco ist die Möglichkeit, einen separaten Cache für die Speicherung kleiner Bilder einzurichten. Dies kann zur Steigerung der Anwendungsleistung nützlich sein.

Bildumwandlung

Einer der Hauptvorteile der Verwendung von Downloadern ist die Möglichkeit, Bilder vor der Ausgabe an den Benutzer zu bearbeiten. Picasso bietet eine Reihe von eingebauten einfachen Transformationen: resize, centerCrop, centerInside, rotate. Für komplexere Änderungen können Sie fertige Lösungen aus freien Bibliotheken verwenden. Zum Beispiel eine Lösung von Daichi Furiya (Wasabeef). Mit ihr können Sie ganz einfach die komplexe Form des Bildes festlegen, weichzeichnen oder Masken und Filter anwenden. Es gibt auch die Möglichkeit, eine eigene benutzerdefinierte Transformation zu erstellen. Dazu müssen Sie von der Klasse Transformation erben und die Logik für die Veränderung des Bildes vorgeben. Es ist auch notwendig, einen eindeutigen Schlüsselstring für diese Transformation festzulegen, der es uns ermöglicht, zwischen den verarbeiteten Bildern im Cache zu unterscheiden.

Beispiel einer Kreistransformation

 class CircleTransformation : Transformation {
   override fun transform(source: Bitmap): Bitmap {
       val paint = Paint()
       paint.setAntiAlias(true)
       paint.setShader(BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP))

       val output = Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888)
       val canvas = Canvas(output)
       canvas.drawCircle(source.width / 2, source.height / 2, source.width / 2, paint)

       if (source != output)
           source.recycle()
       return output
   }

   override fun key(): String {
       return "circle"
   }
}

Die Arbeit mit Transformationen in Glide ist der in Picasso sehr ähnlich. Sie können die eingebauten Optionen zum Ändern von Bildern verwenden (die gleichen wie in Picasso plus circleCrop, roundedCorners, granularRoundedCorners). Für komplexere Fälle gibt es freie Bibliotheken: von dem bereits erwähnten Daichi Furiya (Wasabeef) oder von Werbhelius. Falls erforderlich, können Sie jederzeit Ihre eigene Transformation definieren. Die Implementierung ist etwas anders als bei Picasso. Zunächst einmal wird die Transformationsschnittstelle meist nicht direkt implementiert, sondern mit Hilfe von Utility-Klassen, von denen jede für die Transformation eines bestimmten Ressourcentyps zuständig ist: DrawableTransformation, BitmapTransformation, GifDrawableTransformation, MultiTransformation, usw. Um Ihre Transformation zu erstellen, müssen Sie 4 Methoden neu definieren: equals() und hashcode() – um zwischen Transformationsobjekten zu unterscheiden, transform() – beschreibt die tatsächliche Veränderung des Bildes, updateDiskCacheKey() – ermöglicht es Ihnen, die veränderten Bilder im Cache zu identifizieren.

Beispiel für eine benutzerdefinierte Transformation in Glide

 class GreyscaleTransformation : BitmapTransformation() {
   override fun transform(
           pool: BitmapPool,
           source: Bitmap,
           outWidth: Int,
           outHeight: Int
   ): Bitmap {
       val bitmap = Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888)
       val paint = Paint()
       val greyMatrix = ColorMatrix()
       greyMatrix.setSaturation(0.0f)
       paint.colorFilter = ColorMatrixColorFilter(greyMatrix)
       val canvas = Canvas(bitmap)
       canvas.drawBitmap(source, 0f, 0f, paint)
       return bitmap
   }

   override fun equals(other: Any?): Boolean {
       return other is GreyscaleTransformation
   }

   override fun hashCode(): Int {
       return Util.hashCode(ID.hashCode())
   }

   override fun updateDiskCacheKey(messageDigest: MessageDigest) {
       messageDigest.update(ID_BYTES)
   }

   companion object {
       private val ID = "GreyscaleTransformation"
       private val ID_BYTES = ID.toByteArray()
   }
}

Von den 3 in Frage kommenden Downloadern verfügt Fresco über die ausgefeiltesten Bildumwandlungswerkzeuge. Die meisten von ihnen können im XML-Layout unter Angabe der entsprechenden Attribute angewendet werden. Aus der Perspektive der Codierung ist die Anwendung schwieriger. So sieht zum Beispiel die Abrundung von Ecken bei der Verwendung von Fresco aus.

 val roundingParams = RoundingParams.fromCornersRadius(7f)
fresco_imageview.setHierarchy(
    GenericDraweeHierarchyBuilder(resources)
    .setRoundingParams(roundingParams)
    .build()
)

Darüber hinaus haben einige Standardwerkzeuge Einschränkungen. Zum Beispiel kann „Größe ändern“ nur auf das JPEG-Format angewendet werden, es kann ein Bild nicht vergrößern und nur auf ⅛ der ursprünglichen Größe verkleinern.

Wie andere Lader kann Fresco mit Bibliotheken vorgefertigter Transformationen (aus demselben Wasabeef ) verwendet werden.

Die Erstellung einer eigenen Transformation erfordert eine eigene Implementierung der Postprozessorklasse. Lassen Sie uns das Beispiel der Grauskala mit Glide für Fresco neu schreiben.

 class GrayScaleProcessor : BasePostprocessor() {
   override fun process(bitmap: Bitmap) {
       val w = bitmap.width
       val h = bitmap.height
       val pixels = IntArray(w * h)

       bitmap.getPixels(pixels, 0, w, 0, 0, w, h)

       for (x in 0 until w) {
           for (y in 0 until h) {
               val offset = y * w + x
               pixels[offset] = getGreyColor(pixels[offset])
           }
       }

       bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
   }

   override fun getName(): String {
       return "GrayScaleProcessor"
   }

   override fun getPostprocessorCacheKey(): CacheKey? {
       return SimpleCacheKey("grayScale")
   }

   companion object {
       internal fun getGreyColor(color: Int): Int {
           val alpha = color and -0x1000000
           val r = color shr 16 and 0xFF
           val g = color shr 8 and 0xFF
           val b = color and 0xFF
           val luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b).toInt()
           return alpha or (luminance shl 16) or (luminance shl 8) or luminance
       }
   }
}

Einzigartige Funktionen

Da Picasso auf dem Prinzip „nicht mehr“ basiert, wurden die meisten neuen Funktionen später hinzugefügt als bei der Konkurrenz, vor allem bei Glide.

Glide ist seit langem ein Pionier, wenn es um das Hinzufügen von Funktionen zu Bild-Downloadern auf Android geht. Im Moment kann man seine nützlichsten Funktionen hervorheben:

  1. das Laden eines Frames aus einem Video als Bild;
  2. Verwendung eines beliebigen Modelltyps anstelle von Uri / String für benutzerdefinierte Lader;
  3. GIF-Download (auch in Fresco verfügbar);
  4. flexible API mit der Möglichkeit, jeden Netzwerk-Stack (z.B. Volley oder OkHttp) zu verbinden.

Fresco wurde als alternativer Ansatz zum Hochladen von Bildern auf Android mit einem Auge auf Effizienz entwickelt. Daher sind seine Hauptfunktionen unter der Haube versteckt:

  1. Speichern von Bildern nicht im Java Heap, sondern im Ashmem Heap, was Speicher für Anwendungen spart, das Risiko von OutOfMemoryError reduziert und die Leistung durch seltenere Aufrufe des Garbage Collectors erhöht;
  2. Beschneiden von Bildern um einen beliebigen Punkt, nicht nur in der Mitte;
  3. Größenänderung von JPEG unter Verwendung nativer Ressourcen, was ebenfalls das Risiko von OutOfMemoryError verringert;

  4. Unterstützung für progressive JPEG-Bilder.

Schlussfolgerung

Kriterium
Größe 121 Kb 440 Kb 500 kb
Bequemlichkeit der Nutzung hoch hoch Mitte
Web-Download-Geschwindigkeit hoch geringfügig niedriger aufgrund der langwierigen Caching-Verarbeitung hoch
Cache-Download-Geschwindigkeit Durchschnitt hoch hoch
Integrierte Transformationsfunktionen Grundfunktionen basic set plus rounding eine breite Palette von Umwandlungsmöglichkeiten, jedoch mit Einschränkungen
Zusätzliche Tools Abwesend Laden eines Frames aus einem Video als Bild und GIF, unter Verwendung eines beliebigen Modells, einer flexiblen API mit der Möglichkeit, jeden Netzwerk-Stack zu verbinden. Speichern von Bildern nicht im Java Heap, sondern im Ashmem Heap, die Möglichkeit, Bilder um einen beliebigen Punkt herum zu beschneiden, Größenänderung von JPEGs mit Hilfe von nativen Ressourcen, Unterstützung für Progressive JPEG-Bilder.
Größe 121 Kb
Bequemlichkeit der Nutzung hoch
Web-Download-Geschwindigkeit hoch
Cache-Download-Geschwindigkeit Durchschnitt
Integrierte Transformationsfunktionen Grundfunktionen
Zusätzliche Tools Abwesend
Größe 440 Kb
Bequemlichkeit der Nutzung hoch
Web-Download-Geschwindigkeit geringfügig niedriger aufgrund der langwierigen Caching-Verarbeitung
Cache-Download-Geschwindigkeit hoch
Integrierte Transformationsfunktionen basic set plus rounding
Zusätzliche Tools Laden eines Frames aus einem Video als Bild und GIF, unter Verwendung eines beliebigen Modells, einer flexiblen API mit der Möglichkeit, jeden Netzwerk-Stack zu verbinden.
Größe 500 kb
Bequemlichkeit der Nutzung Mitte
Web-Download-Geschwindigkeit hoch
Cache-Download-Geschwindigkeit hoch
Integrierte Transformationsfunktionen eine breite Palette von Umwandlungsmöglichkeiten, jedoch mit Einschränkungen
Zusätzliche Tools Speichern von Bildern nicht im Java Heap, sondern im Ashmem Heap, die Möglichkeit, Bilder um einen beliebigen Punkt herum zu beschneiden, Größenänderung von JPEGs mit Hilfe von nativen Ressourcen, Unterstützung für Progressive JPEG-Bilder.

Für 2020 sind Picasso, Glide und Fresco die beliebtesten Bibliotheken für das Herunterladen von Bildern in Android-Anwendungen. Jede von ihnen hat ihre eigenen Besonderheiten bei der Verwendung von fortgeschrittenen Werkzeugen, Caching und Bildtransformation im Besonderen. Picasso mit seinem minimalistischen Ansatz eignet sich gut für einfache Bildoperationen, bietet aber nur begrenzte Anpassungsmöglichkeiten. Glide ist der unangefochtene Marktführer unter den Android-Downloadern und bietet leistungsstarke Funktionen und eine intuitive Schnittstelle für die Implementierung. Der innovative Ansatz von Fresco sorgt für Leistungssteigerungen, erschwert aber die Implementierung von Caching und Bildumwandlung erheblich.