2015-08-20 4 views
107

Ich erstelle eine imageintensive soziale App, bei der Bilder vom Server an das Gerät gesendet werden. Wenn das Gerät kleinere Bildschirmauflösungen hat, muss ich die Größe der Bitmaps auf dem Gerät anpassen, damit sie den beabsichtigten Bildschirmgrößen entsprechen.Die meisten Speicher-effiziente Möglichkeit zur Größenanpassung von Bitmaps auf Android?

Das Problem ist, dass die Verwendung von createScaledBitmap verursacht, dass ich in eine Menge von nicht genügend Arbeitsspeicher Fehler nach dem Ändern der Größe einer Horde von Miniaturbildern.

Was ist die speicherfreundlichste Möglichkeit, Bitmaps auf Android zu skalieren?

+7

Kann Ihr Server nicht die richtige Größe senden, so sparen Sie RAM und Bandbreite Ihres Kunden !? – James

+2

Das ist nur dann gültig, wenn ich die Serverressource besaß, eine Compute-Komponente zur Verfügung hatte und in jedem Fall die genauen Abmessungen von Bildern für noch nicht gesehene Seitenverhältnisse vorhersagen konnte. Also, wenn Sie Ressourceninhalt von einer CDN von Drittanbietern laden (wie ich bin) funktioniert es nicht :( –

Antwort

154

Diese Antwort wird von Loading large bitmaps Efficiently zusammengefasst, die erklären, wie inSampleSize verwenden, um eine abwärtsskalierten Bitmap Version zu laden.

Insbesondere Pre-scaling bitmaps erklärt die Details der verschiedenen Methoden, wie sie zu kombinieren, und welche die meisten Speicher effizient sind.

Es gibt drei dominante Möglichkeiten, um eine Bitmap auf Android, um die Größe, die unterschiedlichen Speichereigenschaften haben:

createScaledBitmap API

Diese API in einer vorhandenen Bitmap nehmen, und erstellen Sie eine neue Bitmap mit der genaue Abmessungen, die Sie ausgewählt haben.

Auf der positiven Seite können Sie genau die Bildgröße erhalten, die Sie suchen (unabhängig davon, wie es aussieht). Aber der Nachteil, ist, dass diese API eine bestehende Bitmap benötigt, um zu arbeiten. Das bedeutet, dass das Bild geladen, dekodiert und eine Bitmap erstellt werden muss, bevor eine neue, kleinere Version erstellt werden kann. Dies ist ideal in Bezug auf die genauen Abmessungen, aber schrecklich in Bezug auf zusätzliche Speicher Overhead. Als solches ist diese Art-eines Deal Breaker für die meisten App-Entwickler, die Speicher in der Regel bewusst

inSampleSize flag

BitmapFactory.Options eine Eigenschaft festgestellt, wie inSampleSize hat, dass Ihr Bild die Größe neu, während es Decodierung, um zu verhindern die Notwendigkeit, zu einer temporären Bitmap zu dekodieren. Dieser ganzzahlige Wert, der hier verwendet wird, lädt ein Bild mit einer 1/x-reduzierten Größe. Wenn Sie beispielsweise auf 2 setzen, wird ein Bild zurückgegeben, das halb so groß ist, und wenn Sie es auf 4 setzen, wird ein Bild zurückgegeben, das 1/4 der Größe entspricht. Grundsätzlich sind die Bildgrößen immer etwas kleiner als die Quellgröße.

Aus einer Speicherperspektive ist die Verwendung von inSampleSize eine sehr schnelle Operation. Effektiv wird nur jedes X-te Pixel Ihres Bildes in die resultierende Bitmap decodiert. Es gibt zwei Hauptprobleme mit inSampleSize aber:

  • Es gibt nicht Sie genaue Auflösungen. Es verringert nur die Größe Ihrer Bitmap um etwas 2.

  • Es produziert nicht die beste Qualität Größe ändern. Die meisten Größenänderungsfilter erzeugen gut aussehende Bilder durch Lesen von Pixelblöcken und dann Gewichten dieser, um das in der Größe veränderte Pixel zu erzeugen.inSampleSize vermeidet all dies, indem nur alle paar Pixel gelesen wird. Das Ergebnis ist ziemlich performant und wenig Speicher, aber die Qualität leidet darunter.

Wenn Sie nur mit schrumpf Ihr Bild von einigen POW2 Größe zu tun, und Filterung ist kein Problem, dann können Sie keine mehr Speicher effizient (oder Leistung effizient) Methode als inSampleSize finden.

inScaled, inDensity, inTargetDensity flags

Wenn Sie ein Bild auf eine Dimension müssen maßstabs, die eine Potenz von zwei nicht gleich ist, dann müssen Sie die inScaled, inDensity und inTargetDensity Flaggen von BitmapOptions. Wenn das Flag inScaled gesetzt wurde, leitet das System den Skalierungswert ab, der auf Ihre Bitmap angewendet werden soll, indem der Wert inTargetDensity durch die Werte inDensity dividiert wird.

mBitmapOptions.inScaled = true; 
mBitmapOptions.inDensity = srcWidth; 
mBitmapOptions.inTargetDensity = dstWidth; 

// will load & resize the image to be 1/inSampleSize dimensions 
mCurrentBitmap = BitmapFactory.decodeResources(getResources(), 
     mImageIDs, mBitmapOptions); 

Mit dieser Methode wird die Größe neu Ihr Bild, und auch ein ‚Ändern der Größe Filter‘, um es anzuwenden, das heißt, wird das Endergebnis besser aussehen, weil einige zusätzliche Mathematik berücksichtigt hat, während der Umskalierungsschritt genommen worden . Aber seien Sie gewarnt: dieser zusätzliche Filterschritt, benötigt zusätzliche Verarbeitungszeit, und kann schnell für große Bilder summieren, was zu langsamen Größenanpassungen und zusätzlichen Speicherzuweisungen für den Filter selbst führt.

Es ist generell keine gute Idee, diese Technik auf ein Bild anzuwenden, das aufgrund des zusätzlichen Filteraufwands deutlich größer ist als Ihre gewünschte Größe.

magische Kombination

aus einem Speicher und Performance-Perspektive, können Sie diese Optionen für die besten Ergebnisse kombinieren. (Einstellung der inSampleSize, inScaled, inDensity und inTargetDensity flags)

inSampleSize wird zuerst auf das Bild angewendet werden, es auf die nächste mit Strom versorgt von zwei Kindern größer als Zielgröße. Dann werden inDensity & inTargetDensity verwendet, um das Ergebnis auf die gewünschten exakten Dimensionen zu skalieren, wobei eine Filteroperation angewendet wird, um das Bild zu bereinigen.

Die Kombination dieser beiden Verfahren ist viel schneller, da der Schritt inSampleSize die Anzahl der Pixel verringert, die der resultierende dichtebasierte Schritt für die Anwendung des Größenänderungsfilters benötigt.

mBitmapOptions.inScaled = true; 
mBitmapOptions.inSampleSize = 4; 
mBitmapOptions.inDensity = srcWidth; 
mBitmapOptions.inTargetDensity = dstWidth * mBitmapOptions.inSampleSize; 

// will load & resize the image to be 1/inSampleSize dimensions 
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions); 

Wenn Sie benötigen ein Bild auf bestimmte Dimensionen passen, und einiger schöner Filterung, dann ist diese Technik ist die beste Brücke die richtige Größe zu bekommen, aber in einem schnellen, low-memory footprint getan Betrieb.

Erste Bildabmessungen

gibt es die Bildgröße, ohne das gesamte Bilddekodierungs Um Ihre Bitmap, um die Größe, benötigen Sie die eingehenden Dimensionen kennen. Mit dem Flag können Sie die Abmessungen des Bilds ermitteln, ohne die Pixeldaten dekodieren zu müssen.

Sie können dieses Flag verwenden, um zuerst die Größe zu dekodieren und dann die richtigen Werte für die Skalierung auf Ihre Zielauflösung zu berechnen.

+1

es war toll, wenn Sie uns sagen könnten, was dstWidth ist? – k0sh

+0

@ k0sh dstWIdth ist die Breite des ImageView für wohin es geht, zB 'Zielweite' oder dstWidth für – tyczj

+0

@Tyczj danke für die Antwort, ich weiß, was es ist, aber es gibt einige, die es vielleicht nicht wissen und seit Colt, der diese Frage tatsächlich beantwortet hat, könnte er es vielleicht erklären so Leute werden nicht verwirrt. – k0sh

11

So schön (und genau) wie diese Antwort ist, ist es auch sehr kompliziert. Anstatt das Rad neu zu erfinden, erwägen Sie Bibliotheken wie Glide, Picasso, UIL, Ion oder eine beliebige Anzahl anderer, die diese komplexe und fehleranfällige Logik für Sie implementieren.

Colt selbst empfiehlt sogar einen Blick auf Glide und Picasso im Pre-scaling Bitmaps Performance Patterns Video.

Durch die Verwendung von Bibliotheken können Sie in Colts Antwort jede nur erdenkliche Effizienz erreichen, jedoch mit weitaus einfacheren APIs, die konsistent für jede Android-Version funktionieren.

Verwandte Themen