Große Bitmaps effizient laden

Hinweis:Es gibt mehrere Bibliotheken, die Best Practices für das Laden von Bildern. Sie können diese Bibliotheken in Ihrer App für Folgendes verwenden: Bilder so optimiert, dass sie optimal geladen werden. Wir empfehlen die Glide in der Bilder so schnell und reibungslos wie möglich geladen und angezeigt werden. Weitere beliebte Bibliotheken zum Laden von Bildern sind Picasso vom Square, Coil von Instacart und Fresco von Facebook. Diese Bibliotheken vereinfachen die meisten mit Bitmaps und anderen Bildern auf Android.

Bilder gibt es in allen möglichen Formen und Größen. In vielen Fällen sind sie größer als für eine typische Benutzeroberfläche (UI) der Anwendung. In der Systemgalerie-Anwendung werden z. B. Fotos angezeigt, Android-Geräte verwenden, deren Auflösung meist viel höher ist als die Bildschirmauflösung die Anzeigedichte von 50 Pixeln.

Da Sie mit begrenztem Speicher arbeiten, sollten Sie idealerweise nur eine niedrigere Auflösung laden. Version im Arbeitsspeicher. Die Version mit geringerer Auflösung sollte der Größe der UI-Komponente entsprechen, die wird es angezeigt. Ein Bild mit einer höheren Auflösung hat keinen sichtbaren Vorteil, nimmt aber dennoch und verursacht zusätzlichen Leistungs-Overhead aufgrund von zusätzlichem spontanem Skalierung.

In dieser Lektion erfahren Sie, wie Sie große Bitmaps decodieren, ohne das indem Sie eine kleinere Teilstichprobenversion in den Arbeitsspeicher laden.

Bitmapabmessungen und -typ lesen

Die Klasse BitmapFactory bietet mehrere Decodierungsmethoden (decodeByteArray(), decodeFile(), decodeResource() usw.) zum Erstellen einer Bitmap aus verschiedenen Quellen. Auswählen die sich am besten für Ihre Bilddatenquelle eignet. Mit diesen Methoden wird versucht, Speicher für die erstellte Bitmap zuzuteilen und kann daher leicht zu einem OutOfMemory führen. Ausnahme. Jede Art von Decodierungsmethode verfügt über zusätzliche Signaturen, mit denen Sie die Decodierung festlegen können Optionen über die Klasse BitmapFactory.Options. Das Attribut inJustDecodeBounds beim Decodieren auf true festlegen vermeidet Arbeitsspeicherzuweisung und gibt null für das Bitmapobjekt zurück, legt aber outWidth, outHeight und outMimeType fest. Mit dieser Technik können Sie die Dimensionen und Typ der Bilddaten vor Erstellung (und Speicherzuweisung) der Bitmap.

Kotlin

val options = BitmapFactory.Options().apply {
    inJustDecodeBounds = true
}
BitmapFactory.decodeResource(resources, R.id.myimage, options)
val imageHeight: Int = options.outHeight
val imageWidth: Int = options.outWidth
val imageType: String = options.outMimeType

Java

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

Um java.lang.OutOfMemory-Ausnahmen zu vermeiden, prüfen Sie die Abmessungen einer Bitmap vor dem es nicht decodiert werden kann, es sei denn, Sie vertrauen der Quelle absolut hundertprozentig, um Bilddaten in vorhersehbarer Größe bereitzustellen das bequem in den verfügbaren Speicher passt.

Herunterskalierte Version in den Arbeitsspeicher laden

Jetzt, da die Bildabmessungen bekannt sind, können sie verwendet werden, um zu entscheiden, ob das vollständige Bild in den Arbeitsspeicher geladen werden soll oder ob stattdessen eine Subsampling-Version geladen werden soll. Hier sind einige Faktoren, sollten Sie Folgendes in Betracht ziehen:

  • Geschätzte Arbeitsspeichernutzung beim Laden des vollständigen Bildes in den Arbeitsspeicher.
  • Arbeitsspeichermenge, die Sie für das Laden dieses Images bei einem anderen Arbeitsspeicher zur Verfügung stellen möchten Anforderungen Ihrer Anwendung.
  • Abmessungen des Ziel-ImageView oder der UI-Komponente, die das Bild enthält in das die Daten geladen werden sollen.
  • Bildschirmgröße und -dichte des aktuellen Geräts.

Es lohnt sich beispielsweise nicht, ein Bild mit 1024 x 768 Pixeln in den Speicher zu laden, wird in einer Miniaturansicht mit 128 × 96 Pixeln in einem ImageView angezeigt.

Um den Decodierer anzuweisen, das Bild durch das Laden einer kleineren Version in den Arbeitsspeicher zu übertragen, setzen Sie inSampleSize in Ihrem BitmapFactory.Options-Objekt auf true. Beispiel: Ein Bild mit einer Auflösung von 2048 x 1536, das mit einem inSampleSize von 4 decodiert wird, erzeugt ein Bitmap von etwa 512 x 384. Wenn diese Datei in den Arbeitsspeicher geladen wird, werden für die gesamte (Bitmapkonfiguration von ARGB_8888 vorausgesetzt). Hier finden Sie eine Methode zur Berechnung eines Stichprobengrößenwerts mit einer Potenz von zwei basierend auf einer Zielbreite und Höhe:

Kotlin

fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    // Raw height and width of image
    val (height: Int, width: Int) = options.run { outHeight to outWidth }
    var inSampleSize = 1

    if (height > reqHeight || width > reqWidth) {

        val halfHeight: Int = height / 2
        val halfWidth: Int = width / 2

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }

    return inSampleSize
}

Java

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) >= reqHeight
                && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

Hinweis:Die Potenz von zwei wird berechnet, weil der Decoder einen endgültigen Wert durch Abrunden auf die nächste Potenz von zwei, wie in der inSampleSize-Dokumentation angegeben.

Um diese Methode zu verwenden, decodieren Sie zuerst, wobei inJustDecodeBounds auf true gesetzt ist, übergeben Sie die Optionen und dann noch einmal mit dem neuen Wert inSampleSize und inJustDecodeBounds auf false decodieren:

Kotlin

fun decodeSampledBitmapFromResource(
        res: Resources,
        resId: Int,
        reqWidth: Int,
        reqHeight: Int
): Bitmap {
    // First decode with inJustDecodeBounds=true to check dimensions
    return BitmapFactory.Options().run {
        inJustDecodeBounds = true
        BitmapFactory.decodeResource(res, resId, this)

        // Calculate inSampleSize
        inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)

        // Decode bitmap with inSampleSize set
        inJustDecodeBounds = false

        BitmapFactory.decodeResource(res, resId, this)
    }
}

Java

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

Mit dieser Methode wird es einfach, eine Bitmap von beliebig großer Größe in eine ImageView zu laden, die eine Miniaturansicht von 100 × 100 Pixel anzeigt, wie im folgenden Beispiel gezeigt. Code:

Kotlin

imageView.setImageBitmap(
        decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100)
)

Java

imageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

Sie können ähnlich vorgehen, um Bitmaps aus anderen Quellen zu decodieren, indem Sie Entsprechende BitmapFactory.decode*-Methode.