在没有OutOfMemoryError的情况下加载较大的图像

问题描述:

我想要在画布上绘制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

看看:

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

我不确定这是否是正确的做法,但仍然可以解决您的问题。

尝试在网页查看中打开您的图像。这将为您提供内置的放大/缩小和平移功能,您不必担心打开任何输入流或其他任何内容。

要在网页视图从资产中打开图像:

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

您可以WebSettings允许缩放控件。

希望这会帮助你!

+0

谢谢你的回答。我正在开发具有手势检测功能的自定义视图,而仅仅使用WebView是不够的。 – 2012-08-13 09:57:19

在我看来,最好的选择是将图像分成小块。这将阻止应用程序发出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

如果它在从资源加载时起作用,并且现在在手动加载时不起作用,那么我怀疑是否有资源管理问题,可能是内存泄漏。

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