2012-08-13 4 views
16

Ich habe ein 5000 x 4000 px Bild, das ich auf eine Leinwand zeichnen möchte.Loading große Bilder ohne OutOfMemoryError

Zuerst habe ich versucht, es von Ressourcen zu laden. Ich lege es in /res/drawable.

habe ich die folgende Methode:

InputStream input = getResources().openRawResource(R.drawable.huge_image); 
Drawable d = Drawable.createFromStream(input, "image"); 
d.setBounds(...); 
d.draw(canvas); 

Es arbeitete wie ein Charme. In diesem Fall ist ein AssetManager.AssetInputStream.

So jetzt möchte ich es von der SD-Karte laden.

Hier ist, was ich versucht zu tun:

File f = new File(path); 
Uri uri = Uri.fromFile(f); 
InputStream input = mContext.getContentResolver().openInputStream(uri); 
Drawable d = Drawable.createFromStream(input, "image"); 

In diesem Fall wird die InputStream ein FileInputStream ist, und ich habe eine OutOfMemoryError wenn die Drawable zu schaffen.

Also ich frage mich:

Gibt es eine Möglichkeit, das Bild zu laden, ohne dass Fehler zu bekommen? Oder gibt es eine Möglichkeit, eine FileInputStream in eine AssetInputStream umzuwandeln?

Hinweis:

Ich möchte nicht das Bild, um die Größe, weil ich Zoom/Pan-Funktionalität bin zu implementieren. Bitte sagen Sie mir nicht zu lesen Loading Large Bitmaps Efficiently.

Sie können die gesamte Klasse here überprüfen. Der Fehler tritt auf, wenn setImageUri() verwendet wird.

Hier ist mein Fehlerprotokoll:

08-13 11:57:54.180: E/AndroidRuntime(23763): FATAL EXCEPTION: main 
08-13 11:57:54.180: E/AndroidRuntime(23763): java.lang.OutOfMemoryError: bitmap size exceeds VM budget 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:468) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:332) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.graphics.drawable.Drawable.createFromStream(Drawable.java:657) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.benitobertoli.largeimagezoom.ZoomImageView.setDrawablefromUri(ZoomImageView.java:187) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.benitobertoli.largeimagezoom.ZoomImageView.setImageUri(ZoomImageView.java:588) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.benitobertoli.largeimagezoom.TestActivity.onKeyDown(TestActivity.java:30) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.view.KeyEvent.dispatch(KeyEvent.java:1257) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.app.Activity.dispatchKeyEvent(Activity.java:2075) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1673) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.view.ViewRoot.deliverKeyEventToViewHierarchy(ViewRoot.java:2493) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2463) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.view.ViewRoot.handleMessage(ViewRoot.java:1752) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.os.Handler.dispatchMessage(Handler.java:99) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.os.Looper.loop(Looper.java:144) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at android.app.ActivityThread.main(ActivityThread.java:4937) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at java.lang.reflect.Method.invokeNative(Native Method) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at java.lang.reflect.Method.invoke(Method.java:521) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626) 
08-13 11:57:54.180: E/AndroidRuntime(23763): at dalvik.system.NativeStart.main(Native Method) 

EDIT:

Ich teste meinen Code auf einem HTC Desire A8181. Nachdem mir gesagt wurde, dass das erste Code-Snippet auf einigen anderen Geräten nicht funktionierte, habe ich auf einem Samsung Galaxy S2 und auf dem Emulator getestet.

Ergebnisse: Wenn von Ressourcen Laden, gab den Emulator ein OutOfMemoryError, nicht das Galaxy S2 eine Ausnahme aus, aber die zurück Drawable war null.

Also ich denke, vorläufig ist die einzige Lösung, das Bild zu verkleinern.

+0

Ich habe versucht, Ihren Code auf m Emulator laufen und bekomme den OutOfMemory Fehler sogar auf der "From Res" -Methode :) Welche Geräte und/oder Emulator-Einstellungen, die Sie verwenden, konnte es ohne Fehler ausführen? – Joe

Antwort

13

Werfen Sie einen Blick auf:

http://developer.android.com/reference/android/graphics/BitmapFactory.html

decodeStream (InputStream is, Rect outPadding, BitmapFactory.Options opts) 

oder

decodeFile (String pathName, BitmapFactory.Options opts) 

diese Weise können Sie Ihre ganze Bitmap laden und es auf dem Bildschirm zeigen.

Sie müssen die richtigen Optionen einstellen.

http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html

inSampleSize 

Ich versuchte es mit dem Code:

BitmapFactory.Options options = new BitmapFactory.Options(); 

options.inSampleSize = 4; 

Bitmap myBitmap = BitmapFactory.decodeFile(mUri.getPath(),options); 
Drawable d = new BitmapDrawable(Resources.getSystem(),myBitmap); 
updateDrawable(d); 

Arbeiten für mich.

+0

Wie ich in meiner Frage gesagt habe, habe ich kein Problem, ein Bild von Ressourcen zu laden. Außerdem möchte ich das Bild nicht verkleinern. –

+0

Entschuldigung ich verpasste es dann versuchen Sie die decodeStream (InputStream ist, Rect outPadding, BitmapFactory.Options opts) Methode – n3utrino

+0

Ich wiederhole * Ich möchte nicht die Größe des Bildes *. –

0

Ich bin mir nicht sicher, ob es der richtige Weg ist, dies zu tun, aber immer noch kann dies Ihr Problem lösen.

Versuchen Sie, Ihr Bild in einem Webview zu öffnen. Dadurch erhalten Sie eine eingebaute Zoom-In/Out- und Panning-Funktion und Sie müssen sich keine Gedanken darüber machen, irgendeinen Eingangsstrom oder etwas anderes zu öffnen.

Um ein Bild in Webansicht von Vermögenswerten zu öffnen:

WebView wv = (WebView) findViewById(R.id.webView1); 
    wv.loadUrl("file:///android_asset/abc.png"); 

können Sie WebSettings für Zoom-Steuerungen ermöglicht.

Hoffe, das wird dir helfen !!

+0

Vielen Dank für Ihre Antwort. Ich entwickle eine benutzerdefinierte Ansicht mit Gestenerkennung und ein WebView ist einfach nicht genug. –

3

Meiner Meinung nach ist die beste Option, das Bild in kleinere Teile zu teilen. Dies verhindert, dass die Anwendung eine OutOfMemoryException gibt. Hier ist mein Beispielcode

Zuerst das Bild von der SD-Karte erhält so legt es in eine Bitmap

Bitmap b = null; 
     try { 
      File sd = new File(Environment.getExternalStorageDirectory() + link_to_image.jpg); 
      FileInputStream fis; 

      fis = new FileInputStream(sd); 

      BitmapFactory.Options options = new BitmapFactory.Options(); 
      options.inPurgeable = true; 
      options.inScaled = false; 

      b = BitmapFactory.decodeStream(fis, null, options); 
      fis.close(); 

     } catch (FileNotFoundException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

Split das Bitmap (b) in kleinere Stücke. In diesem Fall ist SPLIT_HEIGHT 400.

double rest = (image_height + SPLIT_HEIGHT); 
     int i = 0; 
     Bitmap[] imgs = new Bitmap[50]; 

     do { 
      rest -= SPLIT_HEIGHT; 
      if (rest < 0) { 
       // Do nothing 
      } else if (rest < SPLIT_HEIGHT) { 
       imgs[i] = Bitmap.createBitmap(b, 0, (i * SPLIT_HEIGHT), image_width, (int) rest); 
      } else { 
       imgs[i] = Bitmap.createBitmap(b, 0, i * SPLIT_HEIGHT, image_width, SPLIT_HEIGHT); 
      } 

      i++; 

     } while (rest > SPLIT_HEIGHT); 

Jetzt haben Sie eine Sammlung von kleineren Bildern. Wenn Sie es perfecly tun möchten, können Sie diese Bitmap recyceln:

// Not needed anymore, free up some memory 
     if (b != null) 
      b.recycle(); 

Von diesem Punkt Sie die kleineren Bilder auf die Leinwand bringen kann. In meinem nächsten Code lege ich die Bilder in eine ScrollView. Der Code zum Setzen der Bilder auf einen Canvas ist meiner Meinung nach nicht sehr unterschiedlich.

// Add images to scrollview 
     for (int j = 0; j < imgs.length; j++) { 
      Bitmap tmp = imgs[j]; 
      if (tmp != null) { 
       // Add to scrollview 
       ImageView iv = new ImageView(activity); 

       String id = j + "" + articlenumber; 
       iv.setId(Integer.parseInt(id)); 
       iv.setTag(i); 

       iv.setImageBitmap(tmp); 

       RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(globalwidth, tmp.getHeight()); 
       lp.addRule(RelativeLayout.BELOW, previousLongPageImageId); 
       lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT); 

       container.addView(iv, lp); 
       previousLongPageImageId = iv.getId(); 
      } 
     } 

Ich hoffe, das wird Ihnen helfen.

HINWEIS: Die maximale Auflösung für Bilder ist 2048 * 2048 (OpenGL Einschränkungen oder etwas), so dass Sie Bilder immer in kleinere Teile teilen müssen.

+1

Vielen Dank für Ihre Antwort. Bevor Sie sogar versuchen, die Bitmap 'b = BitmapFactory.decodeStream (fis, null, options) zu trennen;' gibt bereits einen OutOfMemoryError. Außerdem teilen Sie die Bitmap in 50 kleinere auf, aber Sie laden immer noch alle gleichzeitig im Speicher, was zu demselben Ergebnis führt. –

+0

Wenn Sie android: largeHeap = "true" und dann den obigen Code tun? Wie ich in meiner Notiz sagte, müssen Sie das Bild in Stücke teilen, da die maximale Auflösung 2048 * 2048 ist. Natürlich müssen Sie in meiner Lösung auch das Bild in vertikale Teile teilen (die Breite ist höher als 2048). – Ceetn

0

Wenn es funktionierte, wenn Sie von Ressourcen geladen haben und es jetzt nicht funktioniert, wenn Sie manuell laden, dann vermute ich, dass Sie ein Problem mit der Ressourcenverwaltung haben, wahrscheinlich ein Speicherleck.

Erfolgt OutOfMemoryError immer beim Start der Anwendung oder passiert es in späterer Zeit? Ist es auf Konfigurationsänderung? Verwenden Sie statische Felder (bei falscher Verwendung führt dies oft zu Speicherlecks in der Android-Anwendung, example with explanation passt das zu Ihrem Fall)?
Verwenden Sie einige Profiling-Tools (google kann helfen, etwas Nützliches zu finden).

+0

"OutOfMemoryError" tritt in dieser Zeile auf Drawable d = Drawable.createFromStream (input, "image"); 'wenn ich von Uri lade. Ich verwende keine statischen Felder. Sie können die gesamte Klasse [hier] (https://github.com/BenitoBertoli/ZoomImageView/blob/master/src/com/benitobertoli/largeimagezoom/ZoomImageView.java) besuchen. Ich rufe 'setImageUri()' auf. –

+0

Dieser Link ist für Arbeitscode (Bild von Ressourcen geladen). Anscheinend hast du keine problematischen Änderungen (neuer Zweig?) Gedrückt. –

+0

Bitte überprüfen Sie es noch einmal. Ich habe ein Menü hinzugefügt. "Von Res" und "Von Datei". Der Fehler tritt auf, wenn Sie "Von Datei" verwenden. Sie müssen 'huge_image.png' von' res/drawable' auf Ihre SD-Karte kopieren und den Pfad in Ihrem Code ersetzen. –