java 在没有 OutOfMemoryError 的情况下加载大图像
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11931742/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Loading large images without OutOfMemoryError
提问by Benito Bertoli
I have a 5000 x 4000 px image which I want to draw onto a canvas.
我有一个 5000 x 4000 像素的图像,我想将其绘制到画布上。
First I tried to load it from resources.
I put it in /res/drawable
.
首先,我尝试从资源中加载它。我把它放进去/res/drawable
。
I used the following method:
我使用了以下方法:
InputStream input = getResources().openRawResource(R.drawable.huge_image);
Drawable d = Drawable.createFromStream(input, "image");
d.setBounds(...);
d.draw(canvas);
It worked like a charm.
它就像一个魅力。
In this case the InputStream
is an AssetManager.AssetInputStream
.
在这种情况下,InputStream
是一个AssetManager.AssetInputStream
.
So now I want to load it from the sdcard.
所以现在我想从 sdcard 加载它。
Here's what I tried to do:
这是我尝试做的:
File f = new File(path);
Uri uri = Uri.fromFile(f);
InputStream input = mContext.getContentResolver().openInputStream(uri);
Drawable d = Drawable.createFromStream(input, "image");
In this case the InputStream
is a FileInputStream
and I got an OutOfMemoryError
when creating the Drawable
.
在这种情况下,InputStream
是 a FileInputStream
,我OutOfMemoryError
在创建Drawable
.
So I'm wondering:
所以我想知道:
Is there a way to load the image without getting that error? Or is there a way to convert a FileInputStream
to an AssetInputStream
?
有没有办法加载图像而不会出现该错误?或者有没有办法将 a 转换FileInputStream
为 an AssetInputStream
?
Note:
笔记:
I don't want to resize the image because I'm implementing zoom/pan functionality. Please don't tell me to read Loading Large Bitmaps Efficiently.
我不想调整图像大小,因为我正在实现缩放/平移功能。请不要告诉我阅读有效加载大位图。
You can check out the full class here. The error occurs when using setImageUri()
.
您可以在此处查看完整课程。使用时出现错误setImageUri()
。
Here's my Error Log:
这是我的错误日志:
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:
编辑:
I was testing my code on an HTC Desire A8181. After being told that the first code snippet wasn't working on some other devices, I tested on a Samsung Galaxy S2 and on the Emulator.
我正在 HTC Desire A8181 上测试我的代码。在被告知第一个代码片段在其他一些设备上不起作用后,我在三星 Galaxy S2 和模拟器上进行了测试。
Results:
When loading from resources, the emulator gave an OutOfMemoryError
, the Galaxy S2 didn't throw an exception but the returned Drawable
was null.
结果:从资源加载时,模拟器报错OutOfMemoryError
,Galaxy S2没有抛出异常但返回Drawable
null。
So I guess for the time being the only solution is to downsample the image.
所以我想暂时唯一的解决方案是对图像进行下采样。
采纳答案by n3utrino
Take a look at:
看一眼:
http://developer.android.com/reference/android/graphics/BitmapFactory.html
http://developer.android.com/reference/android/graphics/BitmapFactory.html
decodeStream (InputStream is, Rect outPadding, BitmapFactory.Options opts)
or
或者
decodeFile (String pathName, BitmapFactory.Options opts)
this way you can load your whole bitmap and show it on screen.
这样您就可以加载整个位图并将其显示在屏幕上。
You need to set the correct options.
您需要设置正确的选项。
http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html
http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html
inSampleSize
I tried it with your 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);
works for me.
对我来说有效。
回答by Ceetn
In my opinion, the best option is to split the image into smaller pieces. This will prevent the application to give an OutOfMemoryException. Here is my example code
在我看来,最好的选择是将图像分成更小的部分。这将防止应用程序给出 OutOfMemoryException。这是我的示例代码
First get the image from the SD Card an put it into an Bitmap
首先从 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();
}
Split the bitmap (b) into smaller pieces. In this case SPLIT_HEIGHT is 400.
将位图 (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);
Now you have a collection of smaller images. If you want to do it perfecly, you can recycle this bitmap:
现在您有一组较小的图像。如果你想完美地做到这一点,你可以回收这个位图:
// Not needed anymore, free up some memory
if (b != null)
b.recycle();
From this point you can put the smaller images onto the canvas. In my next code I put the images into an ScrollView. The code for putting the images on a Canvas is I think not very different.
从这一点上,您可以将较小的图像放到画布上。在我的下一个代码中,我将图像放入 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();
}
}
I hope this will help you.
我希望这能帮到您。
NOTE: The maximum resolution for images is 2048*2048 (OpenGL restrictions or something), so you will always have to split images into smaller pieces.
注意:图像的最大分辨率为 2048*2048(OpenGL 限制或其他限制),因此您必须始终将图像分割成更小的部分。
回答by Marek R
If it worked when you loaded from resources and now it doesn't work when you load manually then I suspect that you have some problem with resource management, probably some memory leak.
如果从资源加载时它有效,而现在手动加载时它不起作用,那么我怀疑您在资源管理方面存在一些问题,可能是内存泄漏。
Does OutOfMemoryError
occur always at the start of application or it happens in later time? Is it on configuration change? Do you use static fields (if used incorrectly this often leads to memory leaks in Android application, example with explanationthis fits to your case)?
Try use some profiling tools (googlecan help to find something useful).
不会OutOfMemoryError
总是发生在应用程序启动时或在它以后会发生什么?是配置更改吗?您是否使用静态字段(如果使用不正确,这通常会导致 Android 应用程序中的内存泄漏,示例说明适合您的情况)?
尝试使用一些分析工具(谷歌可以帮助找到有用的东西)。
回答by mudit
I am not sure whether it is the correct of way of doing this but still this can solve your problem.
我不确定这样做是否正确,但这仍然可以解决您的问题。
Try opening your image in a webview. This will provide you in-built zoom-in/out and panning feature and you dont have to worry about opening any input stream or anything else.
尝试在 web 视图中打开您的图像。这将为您提供内置的放大/缩小和平移功能,您不必担心打开任何输入流或其他任何东西。
To open an image in webview from assets:
要从资产在 webview 中打开图像:
WebView wv = (WebView) findViewById(R.id.webView1);
wv.loadUrl("file:///android_asset/abc.png");
You can WebSettingsfor allowing zoom controls.
您可以通过WebSettings来允许缩放控件。
Hope this will help you!!
希望能帮到你!!