2012-08-13 80 views
16

我想要在畫布上繪製5000 x 4000像素圖像。在沒有OutOfMemoryError的情況下加載較大的圖像

首先我試着從資源中加載它。 我把它放在/res/drawable

我用下面的方法:

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

它的工作就像一個魅力。

在這種情況下,InputStreamAssetManager.AssetInputStream

所以現在我想從SD卡加載它。

這裏就是我試圖做的:

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

在這種情況下,InputStreamFileInputStream和創建Drawable時,我得到了一個OutOfMemoryError

所以我想知道:

有沒有辦法來加載圖像沒有得到這個錯誤?或者有沒有辦法將FileInputStream轉換爲AssetInputStream

注:

我不想調整圖片的大小,因爲我實現縮放/平移功能。 請不要讓我讀Loading Large Bitmaps Efficiently

您可以查看完整的課here。使用setImageUri()時會發生錯誤。

這裏是我的錯誤日誌:

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) 

編輯:

我上的HTC Desire A8181測試我的代碼。 在被告知第一個代碼片段在某些其他設備上不起作用之後,我在Samsung Galaxy S2和仿真器上進行了測試。

結果: 從資源加載時,模擬器給出了OutOfMemoryError,Galaxy S2沒有拋出異常,但返回的Drawable爲空。

因此,我認爲目前唯一的解決方案是縮小圖像的樣本。

+0

我試着在m模擬器上運行你的代碼,即使在「From Res」方法下也會出現OutOfMemory錯誤:)你使用的設備和/或仿真器設置是否能夠正常運行? – Joe 2012-08-14 20:15:09

回答

13

看看:

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

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

decodeFile (String pathName, BitmapFactory.Options opts) 

這種方式,您可以加載你的整個的位圖,並顯示在屏幕上。

您需要設置正確的選項。

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

inSampleSize 

我與您的代碼試了一下:

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); 

爲我工作。

+0

正如我在我的問題中所說,從資源加載圖像沒有問題。另外我不想調整圖像大小。 – 2012-08-14 09:57:30

+0

抱歉,我錯過了,然後嘗試decodeStream(InputStream是,Rect outPadding,BitmapFactory.Options opts)方法 – n3utrino 2012-08-14 10:58:22

+0

我重複*我不想調整圖像*。 – 2012-08-14 11:57:48

0

我不確定這是否是正確的做法,但仍然可以解決您的問題。

嘗試在網頁查看中打開您的圖像。這將爲您提供內置的放大/縮小和平移功能,您不必擔心打開任何輸入流或其他任何內容。

要在網頁視圖從資產中打開圖像:

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

您可以WebSettings允許縮放控件。

希望這會幫助你!

+0

謝謝你的回答。我正在開發具有手勢檢測功能的自定義視圖,而僅僅使用WebView是不夠的。 – 2012-08-13 09:57:19

3

在我看來,最好的選擇是將圖像分成小塊。這將阻止應用程序發出OutOfMemoryException。這裏是我的示例代碼

首先從SD卡獲取圖像的把它放到一個位圖

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(); 
     } 

分割位圖(B)成小塊。在這種情況下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); 

現在你有一個較小的圖像集合。如果你想做到這一點,你可以回收這個位圖:

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

從這裏,你可以把更小的圖像放在畫布上。在我的下一個代碼中,我將這些圖像放入了一個ScrollView。將圖像放在Canvas上的代碼是我認爲沒有什麼不同。

// 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(); 
      } 
     } 

我希望這會幫助你。

注意:圖像的最大分辨率是2048 * 2048(OpenGL限制或某物),所以您將永遠不得不將圖像分割成更小的部分。

+1

謝謝你的回答。甚至在嘗試分割位圖b = BitmapFactory.decodeStream(fis,null,options)之前;'已經給出了一個OutOfMemoryError。另外,你將位圖分割成50個較小的位圖,但是你仍然將所有的位圖同時加載到內存中,這會得到相同的結果。 – 2012-08-14 07:45:46

+0

如果你設置android:largeHeap =「true」,然後執行上面的代碼?就像我在筆記中所說的那樣,由於最大分辨率是2048 * 2048,您將不得不將圖像分割成幾部分。當然,在我的解決方案中,您還必須將圖像分成垂直部分(寬度高於2048)。 – Ceetn 2012-08-14 12:16:14

0

如果它在從資源加載時起作用,並且現在在手動加載時不起作用,那麼我懷疑是否有資源管理問題,可能是內存泄漏。

OutOfMemoryError是否總是出現在應用程序的開始或它發生在以後的時間?它是否在配置更改?你使用靜態字段(如果使用不正確,這往往會導致Android應用程序中的內存泄漏,example with explanation這適合您的情況)?
嘗試使用一些分析工具(google可以幫助找到有用的東西)。

+0

'OutOfMemoryError'出現在這一行'Drawable d = Drawable.createFromStream(input,「image」);'當我從Uri加載時。我沒有使用任何靜態字段。你可以查看完整的課程[這裏](https://github.com/BenitoBertoli/ZoomImageView/blob/master/src/com/benitobertoli/largeimagezoom/ZoomImageView.java)。我打電話給'setImageUri()'。 – 2012-08-14 07:58:55

+0

此鏈接適用於工作代碼(從資源加載的圖像)。顯然你沒有推動有問題的變化(新分支?)。 – 2012-08-14 08:11:49

+0

請再次確認。我添加了一個菜單。 「來自Res」和「來自文件」。使用「從文件」發生錯誤。你必須從'res/drawable'拷貝'huge_image.png'到你的SD卡並替換你的代碼中的路徑。 – 2012-08-14 08:33:30

相關問題