I have a list view with a couple of image buttons on each row. When you click the list row, it launches a new activity. I have had to build my own tabs because of an issue with the camera layout. The activity that gets launched for result is a map. If I click on my button to launch the image preview (load an image off the SD card) the application returns from the activity back to the listview
activity to the result handler to relaunch my new activity which is nothing more than an image widget.
我有一个列表视图,每行有几个图像按钮。当单击列表行时,它将启动一个新活动。由于相机布局的问题,我不得不建立自己的标签。为结果而启动的活动是一个映射。如果我点击我的按钮来启动图像预览(从SD卡上加载一个图像),应用程序从活动返回到listview活动到结果处理程序,以重新启动我的新活动,它只不过是一个图像小部件。
The image preview on the list view is being done with the cursor and ListAdapter
. This makes it pretty simple, but I am not sure how I can put a resized image (I.e. Smaller bit size not pixel as the src
for the image button on the fly. So I just resized the image that came off the phone camera.
列表视图上的图像预览是通过游标和ListAdapter完成的。这使得它非常简单,但是我不确定如何设置一个缩放的图像(例如,较小的位元不是作为图像按钮的src)。所以我调整了手机摄像头的图像。
The issue is that I get an out of memory error when it tries to go back and re-launch the 2nd activity.
问题是,当它试图返回并重新启动第2个活动时,我得到了一个内存错误。
- Is there a way I can build the list adapter easily row by row, where I can resize on the fly (bit wise)?
- 是否有一种方法可以让我轻松地逐行构建列表适配器,在那里我可以调整大小(bit wise)?
This would be preferable as I also need to make some changes to the properties of the widgets/elements in each row as I am unable to select a row with touch screen because of focus issue. (I can use roller ball.)
这将是更好的,因为我还需要对每一行中的小部件/元素的属性进行一些更改,因为我不能因为焦点问题选择带有触摸屏的行。(我可以用滚球。)
- I know I can do an out of band resize and save of my image, but that is not really what I want to do, but some sample code for that would be nice.
- 我知道我可以做一个带外缩放和保存我的图像,但这不是我想做的,但是一些样本代码是好的。
As soon as I disabled the image on the list view it worked fine again.
一旦我禁用了列表视图中的图像,它就会再次正常工作。
FYI: This is how I was doing it:
我就是这样做的:
String[] from = new String[] { DBHelper.KEY_BUSINESSNAME,DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY,DBHelper.KEY_GPSLONG,DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME + ""};
int[] to = new int[] {R.id.businessname,R.id.address,R.id.city,R.id.gpslong,R.id.gpslat,R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);
Where R.id.imagefilename
is a ButtonImage
.
R.id的地方。imagefilename ButtonImage。
Here is my LogCat:
这是我LogCat:
01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed
I also have a new error when displaying an image:
在显示图像时,我也有一个新的错误:
01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri:
01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
40 个解决方案
#1
524
The Android Training class, "Displaying Bitmaps Efficiently", offers some great information for understanding and dealing with the exception java.lang.OutOfMemoryError: bitmap size exceeds VM budget
when loading Bitmaps.
Android培训类“高效显示位图”,提供了一些了解和处理异常java.lang的重要信息。OutOfMemoryError:在加载位图时,位图的大小超过了VM预算。
Read Bitmap Dimensions and Type
The BitmapFactory
class provides several decoding methods (decodeByteArray()
, decodeFile()
, decodeResource()
, etc.) for creating a Bitmap
from various sources. Choose the most appropriate decode method based on your image data source. These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in an OutOfMemory
exception. Each type of decode method has additional signatures that let you specify decoding options via the BitmapFactory.Options
class. Setting the inJustDecodeBounds
property to true
while decoding avoids memory allocation, returning null
for the bitmap object but setting outWidth
, outHeight
and outMimeType
. This technique allows you to read the dimensions and type of the image data prior to construction (and memory allocation) of the bitmap.
BitmapFactory类提供了几个解码方法(decodeByteArray()、decodeFile()、decodeResource()等),用于从各种来源创建位图。根据您的图像数据源选择最合适的解码方法。这些方法试图为构造的位图分配内存,因此很容易导致OutOfMemory异常。每种解码方法都有额外的签名,可以通过BitmapFactory指定解码选项。选择类。在解码避免内存分配的情况下,将伤害解码的属性设置为true,返回null的位图对象,但设置输出宽度,outHeight和outMimeType。该技术允许您在构建(和内存分配)位图之前读取图像数据的尺寸和类型。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
To avoid java.lang.OutOfMemory
exceptions, check the dimensions of a bitmap before decoding it, unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory.
避免. lang。OutOfMemory异常,在解码之前检查位图的大小,除非您绝对信任该源,以便为您提供适合于可用内存的可预测大小的图像数据。
Load a scaled down version into Memory
Now that the image dimensions are known, they can be used to decide if the full image should be loaded into memory or if a subsampled version should be loaded instead. Here are some factors to consider:
现在已经知道了图像的大小,可以使用它们来决定是否将完整的图像加载到内存中,或者是否应该加载一个子采样版本。以下是一些需要考虑的因素:
- Estimated memory usage of loading the full image in memory.
- 估计内存的使用,在内存中加载完整的映像。
- The amount of memory you are willing to commit to loading this image given any other memory requirements of your application.
- 在您的应用程序的任何其他内存需求的情况下,您愿意提交该映像的内存数量。
- Dimensions of the target ImageView or UI component that the image is to be loaded into.
- 要加载图像的目标ImageView或UI组件的维度。
- Screen size and density of the current device.
- 屏幕大小和当前设备的密度。
For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x96 pixel thumbnail in an ImageView
.
例如,如果将1024x768像素图像加载到内存中,如果它最终在ImageView中显示在128x96像素的缩略图中,那么它就不值得加载。
To tell the decoder to subsample the image, loading a smaller version into memory, set inSampleSize
to true
in your BitmapFactory.Options
object. For example, an image with resolution 2048x1536 that is decoded with an inSampleSize
of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image (assuming a bitmap configuration of ARGB_8888
). Here’s a method to calculate a sample size value that is a power of two based on a target width and height:
要告诉解码器对图像进行子采样,将较小的版本加载到内存中,在您的BitmapFactory中设置inSampleSize为真。选择对象。例如,有一个分辨率为2048x1536的图像,它被解码为一个inSampleSize 4,生成一个大约512x384的位图。将其加载到内存中使用0.75MB而不是12MB的完整映像(假设ARGB_8888的位图配置)。这里有一种计算样本大小值的方法,它是基于目标宽度和高度的两倍的幂:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Note: A power of two value is calculated because the decoder uses a final value by rounding down to the nearest power of two, as per the
inSampleSize
documentation.注意:两个值的幂是计算出来的,因为解码器使用最后的值,根据inSampleSize的文档,将其舍入到最近的两个幂。
To use this method, first decode with inJustDecodeBounds
set to true
, pass the options through and then decode again using the new inSampleSize
value and inJustDecodeBounds
set to false
:
若要使用此方法,首先要对受伤害的解密程序进行解码,然后将选项通过,然后再使用新的inSampleSize值和被破坏的反译码器进行解码:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
This method makes it easy to load a bitmap of arbitrarily large size into an ImageView
that displays a 100x100 pixel thumbnail, as shown in the following example code:
该方法使得将任意大尺寸的位图加载到显示100x100像素缩略图的ImageView中很容易,如下面的示例代码所示:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
You can follow a similar process to decode bitmaps from other sources, by substituting the appropriate BitmapFactory.decode*
method as needed.
您可以按照类似的过程从其他来源解码位图,并根据需要替换适当的位图。
#2
847
To fix the OutOfMemory error, you should do something like this:
要修复OutOfMemory错误,您应该这样做:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);
This inSampleSize
option reduces memory consumption.
这个inSampleSize选项可以减少内存消耗。
Here's a complete method. First it reads image size without decoding the content itself. Then it finds the best inSampleSize
value, it should be a power of 2, and finally the image is decoded.
这是一个完整的方法。首先,它读取图像大小而不解码内容本身。然后找到最佳的采样值,它应该是2的幂,最后是图像解码。
// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
try {
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
// The new size we want to scale to
final int REQUIRED_SIZE=70;
// Find the correct scale value. It should be the power of 2.
int scale = 1;
while(o.outWidth / scale / 2 >= REQUIRED_SIZE &&
o.outHeight / scale / 2 >= REQUIRED_SIZE) {
scale *= 2;
}
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}
#3
349
I've made a small improvement to Fedor's code. It basically does the same, but without the (in my opinion) ugly while loop and it always results in a power of two. Kudos to Fedor for making the original solution, I was stuck until I found his, and then I was able to make this one :)
我对费多的代码做了一点改进。它基本上是一样的,但是没有(在我看来)丑陋的while循环,它总是会产生两个的力量。在我找到他之前,我一直被困住,直到我找到了他,然后我才得以实现这个目标:)
private Bitmap decodeFile(File f){
Bitmap b = null;
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
int scale = 1;
if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /
(double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
}
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
return b;
}
#4
218
I come from iOS experience and I was frustrated to discover an issue with something so basic as loading and showing an image. After all, everyone that is having this issue is trying to display reasonably sized images. Anyway, here are the two changes that fixed my problem (and made my app very responsive).
我来自iOS的经验,我很沮丧地发现了一个问题,它的基础是加载和显示图像。毕竟,每个有这个问题的人都在试图展示合理大小的图片。无论如何,这是修正了我的问题的两个变化(并且使我的应用程序非常敏感)。
1) Every time you do BitmapFactory.decodeXYZ()
, make sure to pass in a BitmapFactory.Options
with inPurgeable
set to true
(and preferably with inInputShareable
also set to true
).
每次你做BitmapFactory. decodexyz()时,一定要在BitmapFactory中通过。选项为true(最好是inInputShareable也设置为true)。
2) NEVER use Bitmap.createBitmap(width, height, Config.ARGB_8888)
. I mean NEVER! I've never had that thing not raise memory error after few passes. No amount of recycle()
, System.gc()
, whatever helped. It always raised exception. The one other way that actually works is to have a dummy image in your drawables (or another Bitmap that you decoded using step 1 above), rescale that to whatever you want, then manipulate the resulting Bitmap (such as passing it on to a Canvas for more fun). So, what you should use instead is: Bitmap.createScaledBitmap(srcBitmap, width, height, false)
. If for whatever reason you MUST use the brute force create method, then at least pass Config.ARGB_4444
.
2)不要使用位图。createBitmap(宽度、高度,Config.ARGB_8888)。我的意思是永远!我从来没有过这样的事情,在经过几次之后就不会引起内存错误。没有任何回收(),System.gc(),任何帮助。它总是提出例外。另一种方法实际上是在您的drawables(或您使用第1步解码的另一个位图)中创建一个虚拟映像,将其重新扩展到您想要的任何位置,然后操作生成的位图(例如将其传递到画布上以获得更多的乐趣)。因此,您应该使用的是:位图。createScaledBitmap(srcBitmap,宽度,高度,假)。如果出于某种原因,您必须使用蛮力创建方法,那么至少要通过Config.ARGB_4444。
This is almost guaranteed to save you hours if not days. All that talk about scaling the image, etc. does not really work (unless you consider getting wrong size or degraded image a solution).
这几乎可以保证,如果不是几天的话,你可以节省几个小时。所有关于缩放图像的讨论都没有真正起作用(除非您考虑使用错误的大小或降级的图像作为解决方案)。
#5
83
It's a known bug, it's not because of large files. Since Android Caches the Drawables, it's going out of memory after using few images. But I've found an alternate way for it, by skipping the android default cache system.
这是一个已知的错误,不是因为大文件。由于Android缓存了Drawables,在使用了很少的图像之后,它就会从内存中消失。但我找到了另一种方法,跳过android默认的缓存系统。
Solution: Move the images to "assets" folder and use the following function to get BitmapDrawable:
解决方案:将图像移动到“assets”文件夹,并使用以下函数获取位图:
public static Drawable getAssetImage(Context context, String filename) throws IOException {
AssetManager assets = context.getResources().getAssets();
InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
Bitmap bitmap = BitmapFactory.decodeStream(buffer);
return new BitmapDrawable(context.getResources(), bitmap);
}
#6
68
I had this same issue and solved it by avoiding the BitmapFactory.decodeStream or decodeFile functions and instead used BitmapFactory.decodeFileDescriptor
我有同样的问题,并通过避免BitmapFactory.decodeStream或decodeFile函数来解决它,而是使用了bitmapfactory . decodetledescriptor。
decodeFileDescriptor
looks like it calls different native methods than the decodeStream/decodeFile.
与decodeStream/decodeFile相比,解码描述符看起来像是调用了不同的本机方法。
Anyways, what worked was this (note that I added some options as some had above, but that's not what made the difference. What is critical is the call to BitmapFactory.decodeFileDescriptor instead of decodeStream or decodeFile):
不管怎么说,这(注意到我在上面添加了一些选项,但这并不是造成差异的原因)。关键的是对位图因子的调用。decodeledescriptor而不是decodeStream或decodeFile):
private void showImage(String path) {
Log.i("showImage","loading:"+path);
BitmapFactory.Options bfOptions=new BitmapFactory.Options();
bfOptions.inDither=false; //Disable Dithering mode
bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared
bfOptions.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
bfOptions.inTempStorage=new byte[32 * 1024];
File file=new File(path);
FileInputStream fs=null;
try {
fs = new FileInputStream(file);
} catch (FileNotFoundException e) {
//TODO do something intelligent
e.printStackTrace();
}
try {
if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
} catch (IOException e) {
//TODO do something intelligent
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
im.setImageBitmap(bm);
//bm.recycle();
bm=null;
}
I think there is a problem with the native function used in decodeStream/decodeFile. I have confirmed that a different native method is called when using decodeFileDescriptor. Also what I've read is "that Images (Bitmaps) are not allocated in a standard Java way but via native calls; the allocations are done outside of the virtual heap, but are counted against it!"
我认为在decodeStream/decodeFile中使用的本机函数存在一个问题。我已经确认了在使用解码描述符时调用了不同的本机方法。我所读到的是“这些图像(位图)不是按照标准Java方式分配的,而是通过本地调用来分配的;这些分配是在虚拟堆之外完成的,但是被计算出来了!
#7
62
I think best way to avoid the OutOfMemoryError
is to face it and understand it.
我认为避免OutOfMemoryError的最好方法是面对它并理解它。
I made an app to intentionally cause OutOfMemoryError
, and monitor memory usage.
我做了一个应用程序,故意造成OutOfMemoryError错误,并监视内存使用情况。
After I've done a lot of experiments with this App, I've got the following conclusions:
在我做了很多这个应用的实验之后,我得出了以下结论:
I'm gonna talk about SDK versions before Honey Comb first.
我要先讲一下SDK版本。
-
Bitmap is stored in native heap, but it will get garbage collected automatically, calling recycle() is needless.
位图存储在本机堆中,但它会自动收集垃圾,调用回收()是不必要的。
-
If {VM heap size} + {allocated native heap memory} >= {VM heap size limit for the device}, and you are trying to create bitmap, OOM will be thrown.
如果{VM堆大小}+{分配了本机堆内存}>= {VM堆大小限制},并且您正在尝试创建位图,那么OOM将被抛出。
NOTICE: VM HEAP SIZE is counted rather than VM ALLOCATED MEMORY.
注意:VM堆大小被计数,而不是VM分配的内存。
-
VM Heap size will never shrink after grown, even if the allocated VM memory is shrinked.
即使已分配的VM内存被收缩,VM堆大小也不会缩小。
-
So you have to keep the peak VM memory as low as possible to keep VM Heap Size from growing too big to save available memory for Bitmaps.
因此,您必须尽可能地保持最大的VM内存,以保持VM堆大小的增长,从而使其无法保存用于位图的可用内存。
-
Manually call System.gc() is meaningless, the system will call it first before trying to grow the heap size.
手动调用system .gc()是没有意义的,在尝试增加堆大小之前,系统会先调用它。
-
Native Heap Size will never shrink too, but it's not counted for OOM, so no need to worry about it.
本机堆大小也不会缩小,但它不为OOM计数,因此无需担心。
Then, let's talk about SDK Starts from Honey Comb.
然后,让我们讨论一下从蜂巢开始的SDK。
-
Bitmap is stored in VM heap, Native memory is not counted for OOM.
位图存储在VM堆中,本机内存不计入OOM。
-
The condition for OOM is much simpler: {VM heap size} >= {VM heap size limit for the device}.
OOM的条件更简单:{VM堆大小}>= {VM堆大小限制}。
-
So you have more available memory to create bitmap with the same heap size limit, OOM is less likely to be thrown.
因此,您有更多可用内存来创建具有相同堆大小限制的位图,OOM不太可能被抛出。
Here is some of my observations about Garbage Collection and Memory Leak.
下面是我对垃圾收集和内存泄漏的一些观察。
You can see it yourself in the App. If an Activity executed an AsyncTask that was still running after the Activity was destroyed, the Activity will not get garbage collected until the AsyncTask finish.
您可以在应用程序中看到它。如果一个活动执行了一个在活动被破坏后仍在运行的AsyncTask,那么在AsyncTask完成之前,活动将不会被收集到垃圾。
This is because AsyncTask is an instance of an anonymous inner class, it holds a reference of the Activity.
这是因为AsyncTask是匿名内部类的一个实例,它包含活动的引用。
Calling AsyncTask.cancel(true) will not stop the execution if the task is blocked in an IO operation in background thread.
如果任务在后台线程的IO操作中被阻塞,则调用AsyncTask.cancel(true)不会停止执行。
Callbacks are anonymous inner classes too, so if a static instance in your project holds them and do not release them, memory would be leaked.
回调是匿名的内部类,所以如果您的项目中的一个静态实例持有它们并且不释放它们,那么内存将被泄漏。
If you scheduled a repeating or delayed task, for example a Timer, and you do not call cancel() and purge() in onPause(), memory would be leaked.
如果您安排了一个重复或延迟的任务,例如一个计时器,并且在onPause()中不调用cancel()和清除(),内存就会被泄漏。
#8
57
I have seen a lot of questions about OOM exceptions and caching lately. The developer guide has a really good article on this, but some tends to fail on implementing it in a suitable way.
最近我看到了很多关于OOM异常和缓存的问题。开发人员指南上有一篇非常好的文章,但是一些人倾向于以一种合适的方式来实现它。
Because of this I wrote an example application that demonstrates caching in an Android environment. This implementation has not yet gotten an OOM.
因此,我编写了一个示例应用程序,在Android环境中演示缓存。这个实现还没有得到一个OOM。
Look at the end of this answer for a link to the source code.
查看这个答案的末尾,链接到源代码。
Requirements:
- Android API 2.1 or higher (I simply could not manage to get the available memory for an application in API 1.6 - that is the only piece of code that doesn't work in API 1.6)
- Android API 2.1或更高版本(我只是无法获得API 1.6中应用程序的可用内存),这是API 1.6中唯一不能使用的代码。
- Android support package
- Android支持包
Features:
- Retains the cache if there is an orientation change, using a singleton
- 如果有方向变化,则使用singleton来保留缓存。
- Use one eighth of the assigned application memory to the cache (modify if you want)
- 将指定的应用程序内存的八分之一用于缓存(如果需要,可以修改)
- Large bitmaps gets scaled (you can define the maximum pixels that you want to allow)
- 大位图被缩放(你可以定义你想要的最大像素)
- Controls that there is an internet connection available before downloading the bitmaps
- 在下载位图之前,可以使用internet连接。
- Makes sure that you are only instantiating one task per row
- 确保每个行只实例化一个任务。
- If you are flinging the
ListView
away, it simply won't download the bitmaps between - 如果你把ListView去掉,它根本不会下载位图。
This does not include:
- Disk caching. This should be easy to implement anyway - just point to a different task that grabs the bitmaps from the disk
- 磁盘缓存。无论如何,这都应该很容易实现—只需指向从磁盘获取位图的不同任务。
Sample code:
The images that are being downloaded are images (75x75) from Flickr. However, put whatever image urls you want to be processed, and the application will scale it down if it exceeds the maximum. In this application the urls are simply in a String
array.
正在下载的图像是来自Flickr的图像(75x75)。但是,将您想要处理的任何图像url放在一起,如果它超过了最大值,应用程序就会将其缩小。在这个应用程序中,url仅仅是在一个字符串数组中。
The LruCache
has a good way to deal with bitmaps. However, in this application I put an instance of an LruCache
inside another cache class that I created in order to get the application more feasible.
LruCache有一个很好的处理位图的方法。但是,在这个应用程序中,我在另一个缓存类中放置了一个LruCache实例,以便使应用程序更可行。
Cache.java's critical stuff (the loadBitmap()
method is the most important):
缓存。java的关键内容(loadBitmap()方法是最重要的):
public Cache(int size, int maxWidth, int maxHeight) {
// Into the constructor you add the maximum pixels
// that you want to allow in order to not scale images.
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
mBitmapCache = new LruCache<String, Bitmap>(size) {
protected int sizeOf(String key, Bitmap b) {
// Assuming that one pixel contains four bytes.
return b.getHeight() * b.getWidth() * 4;
}
};
mCurrentTasks = new ArrayList<String>();
}
/**
* Gets a bitmap from cache.
* If it is not in cache, this method will:
*
* 1: check if the bitmap url is currently being processed in the
* BitmapLoaderTask and cancel if it is already in a task (a control to see
* if it's inside the currentTasks list).
*
* 2: check if an internet connection is available and continue if so.
*
* 3: download the bitmap, scale the bitmap if necessary and put it into
* the memory cache.
*
* 4: Remove the bitmap url from the currentTasks list.
*
* 5: Notify the ListAdapter.
*
* @param mainActivity - Reference to activity object, in order to
* call notifyDataSetChanged() on the ListAdapter.
* @param imageKey - The bitmap url (will be the key).
* @param imageView - The ImageView that should get an
* available bitmap or a placeholder image.
* @param isScrolling - If set to true, we skip executing more tasks since
* the user probably has flinged away the view.
*/
public void loadBitmap(MainActivity mainActivity,
String imageKey, ImageView imageView,
boolean isScrolling) {
final Bitmap bitmap = getBitmapFromCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.ic_launcher);
if (!isScrolling && !mCurrentTasks.contains(imageKey) &&
mainActivity.internetIsAvailable()) {
BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
mainActivity.getAdapter());
task.execute();
}
}
}
You shouldn't need to edit anything in the Cache.java file unless you want to implement disk caching.
您不需要在缓存中编辑任何东西。java文件,除非您想实现磁盘缓存。
MainActivity.java's critical stuff:
MainActivity。java的关键内容:
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (view.getId() == android.R.id.list) {
// Set scrolling to true only if the user has flinged the
// ListView away, hence we skip downloading a series
// of unnecessary bitmaps that the user probably
// just want to skip anyways. If we scroll slowly it
// will still download bitmaps - that means
// that the application won't wait for the user
// to lift its finger off the screen in order to
// download.
if (scrollState == SCROLL_STATE_FLING) {
mIsScrolling = true;
} else {
mIsScrolling = false;
mListAdapter.notifyDataSetChanged();
}
}
}
// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
final ViewHolder holder;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.main_listview_row, parent, false);
holder = new ViewHolder(row);
row.setTag(holder);
} else {
holder = (ViewHolder) row.getTag();
}
final Row rowObject = getItem(position);
// Look at the loadBitmap() method description...
holder.mTextView.setText(rowObject.mText);
mCache.loadBitmap(MainActivity.this,
rowObject.mBitmapUrl, holder.mImageView,
mIsScrolling);
return row;
}
getView()
gets called very often. It's normally not a good idea to download images there if we haven't implemented a check that ensure us that we won't start an infinite amount of threads per row. Cache.java checks whether the rowObject.mBitmapUrl
already is in a task and if it is, it won't start another. Therefore, we are most likely not exceeding the work queue restriction from the AsyncTask
pool.
getView()经常被调用。如果我们没有实现一个检查,确保我们不会每一行启动一个无限数量的线程,那么在那里下载图像通常不是一个好主意。缓存。java检查行对象。mBitmapUrl已经在一个任务中,如果它是,它不会启动另一个。因此,我们很可能不超过来自AsyncTask池的工作队列限制。
Download:
You can download the source code from https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip.
您可以从https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip下载源代码。
Last words:
I have tested this for a few weeks now, I haven't gotten a single OOM exception yet. I have tested this on the emulator, on my Nexus One and on my Nexus S. I have tested image urls that contain images that were in HD quality. The only bottleneck is that it takes more time to download.
我已经测试了几个星期了,我还没有得到一个OOM例外。我在我的Nexus One和Nexus s上测试了这款产品,我测试了包含高清质量图像的图像url。唯一的瓶颈是需要更多的时间下载。
There is only one possible scenario where I can imagine that the OOM will appear, and that is if we download many, really big images, and before they get scaled and put into cache, will simultaneously take up more memory and cause an OOM. But that isn't even an ideal situation anyway and it most likely won't be possible to solve in a more feasible way.
只有一个可能的场景,我可以想象OOM会出现,那就是如果我们下载了很多非常大的图像,在它们被缩放和放入缓存之前,会同时占用更多的内存并导致一个OOM。但这并不是一个理想的情况,而且很可能无法以更可行的方式解决。
Report errors in the comments! :-)
在评论中报告错误!:-)
#9
36
I did the following to take the image and resize it on the fly. Hope this helps
我做了如下图,并在飞行中调整它的大小。希望这有助于
Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);
#10
31
It seems that this is a very long running problem, with a lot of differing explanations. I took the advice of the two most common presented answers here, but neither one of these solved my problems of the VM claiming it couldn't afford the bytes to perform the decoding part of the process. After some digging I learned that the real problem here is the decoding process taking away from the NATIVE heap.
这似乎是一个长期存在的问题,有许多不同的解释。我在这里听取了两个最常见的答案的建议,但这两个问题都没有解决我的问题,因为VM声称它无法负担执行解码部分的字节。在进行了一些挖掘之后,我了解到真正的问题在于从本机堆中提取的解码过程。
See here: BitmapFactory OOM driving me nuts
看这里:BitmapFactory OOM把我逼疯了。
That lead me to another discussion thread where I found a couple more solutions to this problem. One is to callSystem.gc();
manually after your image is displayed. But that actually makes your app use MORE memory, in an effort to reduce the native heap. The better solution as of the release of 2.0 (Donut) is to use the BitmapFactory option "inPurgeable". So I simply added o2.inPurgeable=true;
just after o2.inSampleSize=scale;
.
这就引出了另一个讨论线程,在这里我找到了更多解决这个问题的方法。一个是callSystem.gc();在您的图像显示之后手动操作。但这实际上使您的应用程序使用更多的内存,以减少本机堆。对于2.0版本(Donut)来说,更好的解决方案是使用“inPurgeable”的BitmapFactory选项。所以我只是添加了o2。inpurgeable =true;刚刚o2.inSampleSize =规模;。
More on that topic here: Is the limit of memory heap only 6M?
更多关于这个主题的内容:内存堆的极限只有6M吗?
Now, having said all of this, I am a complete dunce with Java and Android too. So if you think this is a terrible way to solve this problem, you are probably right. ;-) But this has worked wonders for me, and I have found it impossible to run the VM out of heap cache now. The only drawback I can find is that you are trashing your cached drawn image. Which means if you go RIGHT back to that image, you are redrawing it each and every time. In the case of how my application works, that is not really a problem. Your mileage may vary.
现在,我已经说了所有这些,我是一个完整的dunce和Java和Android。所以如果你认为这是解决这个问题的糟糕方法,你可能是对的。但是这对我来说很有用,我发现现在不可能从堆缓存中运行VM。我能发现的唯一缺点是,您正在对缓存的绘制图像进行破坏。也就是说,如果你回到那个图像,你每次都重新画它。在我的应用程序如何工作的情况下,这并不是一个真正的问题。你的情况可能不同。
#11
29
unfortunately if None of the Above works, then Add this to your Manifest file. Inside application tag
不幸的是,如果以上这些都没有工作,那么将它添加到您的清单文件中。内部应用程序标记
<application
android:largeHeap="true"
#12
26
Use this bitmap.recycle();
This helps without any image quality issue.
使用这个bitmap.recycle();这有助于避免任何图像质量问题。
#13
24
I have a much more effective solution which does not need scaling of any sort. Simply decode your bitmap only once and then cache it in a map against its name. Then simply retrieve the bitmap against the name and set it in the ImageView. There is nothing more that needs to be done.
我有一个更有效的解决方案,不需要任何形式的缩放。只需简单地解码您的位图,然后将其缓存到与它的名称相对应的映射中。然后简单地检索位图并将其设置在ImageView中。没有什么事情需要做了。
This will work because the actual binary data of the decoded bitmap is not stored within the dalvik VM heap. It is stored externally. So every time you decode a bitmap, it allocates memory outside of VM heap which is never reclaimed by GC
这将起作用,因为解码位图的实际二进制数据不存储在dalvik VM堆中。它是存储在外部。因此,每当您解码一个位图时,它就会分配到VM堆之外的内存,而这永远不会被GC回收。
To help you better appreciate this, imagine you have kept ur image in the drawable folder. You just get the image by doing a getResources().getDrwable(R.drawable.). This will NOT decode your image everytime but re-use an already decoded instance everytime you call it. So in essence it is cached.
为了帮助您更好地理解这一点,请想象您将您的图像保存在drawable文件夹中。通过做getResources(). getdrwable (R.drawable.)来获取图像。这不会每次都解码你的图像,但是每次你调用它的时候都要重新使用一个已经解码的实例。本质上它被缓存了。
Now since your image is in a file somewhere (or may even be coming from an external server), it is YOUR responsibility to cache the decoded bitmap instance to be reused any where it is needed.
既然您的映像在某个文件中(或者甚至可能来自外部服务器),那么您就有责任缓存已解码的位图实例,以便在需要的地方重用它。
Hope this helps.
希望这个有帮助。
#14
24
I have resolved the same issue in the following manner.
我以下列方式解决了同样的问题。
Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
b.eraseColor(0xFFFFFFFF);
Rect r = new Rect(0, 0,320 , 424);
Canvas c = new Canvas(b);
Paint p = new Paint();
p.setColor(0xFFC0C0C0);
c.drawRect(r, p);
d = mContext.getResources().getDrawable(mImageIds[position]);
d.setBounds(r);
d.draw(c);
/*
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inTempStorage = new byte[128*1024];
b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
o2.inSampleSize=16;
o2.inPurgeable = true;
*/
} catch (Exception e) {
}
i.setImageBitmap(b);
#15
23
There are two issues here....
这里有两个问题....
- Bitmap memory isn't in the VM heap but rather in the native heap - see BitmapFactory OOM driving me nuts
- 位图内存不是在VM堆中,而是在本机堆中——看到BitmapFactory OOM让我抓狂。
- Garbage collection for the native heap is lazier than the VM heap - so you need to be quite aggressive about doing bitmap.recycle and bitmap =null every time you go through an Activity's onPause or onDestroy
- 本机堆的垃圾收集比VM堆更懒——因此您需要非常积极地处理位图。每次执行活动的onPause或onDestroy时,循环和位图=null。
#16
23
This worked for me!
这为我工作!
public Bitmap readAssetsBitmap(String filename) throws IOException {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
if(bitmap == null) {
throw new IOException("File cannot be opened: It's value is null");
} else {
return bitmap;
}
} catch (IOException e) {
throw new IOException("File cannot be opened: " + e.getMessage());
}
}
#17
19
Great answers here, but I wanted a fully usable class to address this problem.. so I did one.
这里有很好的答案,但我想要一个完全可用的类来解决这个问题。所以我做了一个。
Here is my BitmapHelper class that is OutOfMemoryError proof :-)
这里是我的BitmapHelper类,它是OutOfMemoryError proof:-)
import java.io.File;
import java.io.FileInputStream;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
public class BitmapHelper
{
//decodes image and scales it to reduce memory consumption
public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
{
try
{
//Decode image size
BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
bitmapSizeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);
// load image using inSampleSize adapted to required image size
BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
bitmapDecodeOptions.inPurgeable = true;
bitmapDecodeOptions.inDither = !quickAndDirty;
bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;
Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);
// scale bitmap to mathc required size (and keep aspect ratio)
float srcWidth = (float) bitmapDecodeOptions.outWidth;
float srcHeight = (float) bitmapDecodeOptions.outHeight;
float dstWidth = (float) requiredWidth;
float dstHeight = (float) requiredHeight;
float srcAspectRatio = srcWidth / srcHeight;
float dstAspectRatio = dstWidth / dstHeight;
// recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
// (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
// java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
// I do not excatly understand why, but this way it's OK
boolean recycleDecodedBitmap = false;
Bitmap scaledBitmap = decodedBitmap;
if (srcAspectRatio < dstAspectRatio)
{
scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
// will recycle recycleDecodedBitmap
recycleDecodedBitmap = true;
}
else if (srcAspectRatio > dstAspectRatio)
{
scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
recycleDecodedBitmap = true;
}
// crop image to match required image size
int scaledBitmapWidth = scaledBitmap.getWidth();
int scaledBitmapHeight = scaledBitmap.getHeight();
Bitmap croppedBitmap = scaledBitmap;
if (scaledBitmapWidth > requiredWidth)
{
int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
scaledBitmap.recycle();
}
else if (scaledBitmapHeight > requiredHeight)
{
int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
scaledBitmap.recycle();
}
if (recycleDecodedBitmap)
{
decodedBitmap.recycle();
}
decodedBitmap = null;
scaledBitmap = null;
return croppedBitmap;
}
catch (Exception ex)
{
ex.printStackTrace();
}
return null;
}
/**
* compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
*
* @param requiredWidth
* @param requiredHeight
* @param powerOf2
* weither we want a power of 2 sclae or not
* @return
*/
public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
{
int inSampleSize = 1;
// Raw height and width of image
final int srcHeight = options.outHeight;
final int srcWidth = options.outWidth;
if (powerOf2)
{
//Find the correct scale value. It should be the power of 2.
int tmpWidth = srcWidth, tmpHeight = srcHeight;
while (true)
{
if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
break;
tmpWidth /= 2;
tmpHeight /= 2;
inSampleSize *= 2;
}
}
else
{
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public static Bitmap drawableToBitmap(Drawable drawable)
{
if (drawable instanceof BitmapDrawable)
{
return ((BitmapDrawable) drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
{
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// RECREATE THE NEW BITMAP
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
}
#18
18
None of the answers above worked for me, but I did come up with a horribly ugly workaround that solved the problem. I added a very small, 1x1 pixel image to my project as a resource, and loaded it into my ImageView before calling into garbage collection. I think it might be that the ImageView was not releasing the Bitmap, so GC never picked it up. It's ugly, but it seems to be working for now.
以上的答案都不适合我,但我确实想出了一个非常丑陋的解决方法。我将一个非常小的1x1像素图像添加到我的项目中作为一个资源,并将它加载到我的ImageView中,然后调用垃圾收集。我想可能是ImageView没有释放位图,所以GC没有把它捡起来。它很丑,但它现在似乎在起作用。
if (bitmap != null)
{
bitmap.recycle();
bitmap = null;
}
if (imageView != null)
{
imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();
imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.
#19
18
This works for me.
这适合我。
Bitmap myBitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;
File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);
and this is on C# monodroid. you can easily change the path of the image. what important here is the options to be set.
这是在c#单机器人上。你可以很容易地改变图像的路径。这里重要的是要设置的选项。
#20
14
This seems like the appropriate place to share my utility class for loading and processing images with the community, you are welcome to use it and modify it freely.
这似乎是一个合适的地方,可以共享我的实用程序类来加载和处理与社区的图像,欢迎您使用它并*修改它。
package com.emil;
import java.io.IOException;
import java.io.InputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
/**
* A class to load and process images of various sizes from input streams and file paths.
*
* @author Emil http://*.com/users/220710/emil
*
*/
public class ImageProcessing {
public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
if(ImageProcessing.checkDecode(options)){
return bm;
}else{
throw new IOException("Image decoding failed, using stream.");
}
}
public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
if(ImageProcessing.checkDecode(options)){
return bm;
}else{
throw new IOException("Image decoding failed, using file path.");
}
}
public static Dimensions getDimensions(InputStream stream) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
BitmapFactory.decodeStream(stream,null,options);
if(ImageProcessing.checkDecode(options)){
return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
}else{
throw new IOException("Image decoding failed, using stream.");
}
}
public static Dimensions getDimensions(String imgPath) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
BitmapFactory.decodeFile(imgPath,options);
if(ImageProcessing.checkDecode(options)){
return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
}else{
throw new IOException("Image decoding failed, using file path.");
}
}
private static boolean checkDecode(BitmapFactory.Options options){
// Did decode work?
if( options.outWidth<0 || options.outHeight<0 ){
return false;
}else{
return true;
}
}
/**
* Creates a Bitmap that is of the minimum dimensions necessary
* @param bm
* @param min
* @return
*/
public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
int newWidth, newHeight;
switch(min.type){
case WIDTH:
if(bm.getWidth()>min.minWidth){
newWidth=min.minWidth;
newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
break;
case HEIGHT:
if(bm.getHeight()>min.minHeight){
newHeight=min.minHeight;
newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
break;
case BOTH: // minimize to the maximum dimension
case MAX:
if(bm.getHeight()>bm.getWidth()){
// Height needs to minimized
min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
if(bm.getHeight()>min.minDim){
newHeight=min.minDim;
newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
}else{
// Width needs to be minimized
min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
if(bm.getWidth()>min.minDim){
newWidth=min.minDim;
newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
}
break;
default:
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
}
public static int getScaledWidth(int height, Bitmap bm){
return (int)(((double)bm.getWidth()/bm.getHeight())*height);
}
public static int getScaledHeight(int width, Bitmap bm){
return (int)(((double)bm.getHeight()/bm.getWidth())*width);
}
/**
* Get the proper sample size to meet minimization restraints
* @param dim
* @param min
* @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
* @return
*/
public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
switch(min.type){
case WIDTH:
return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
case HEIGHT:
return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
case BOTH:
int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
// Return the smaller of the two
if(widthMaxSampleSize<heightMaxSampleSize){
return widthMaxSampleSize;
}else{
return heightMaxSampleSize;
}
case MAX:
// Find the larger dimension and go bases on that
if(dim.width>dim.height){
return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
}else{
return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
}
}
return 1;
}
public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
int add=multipleOf2 ? 2 : 1;
int size=0;
while(min<(dim/(size+add))){
size+=add;
}
size = size==0 ? 1 : size;
return size;
}
public static class Dimensions {
int width;
int height;
public Dimensions(int width, int height) {
super();
this.width = width;
this.height = height;
}
@Override
public String toString() {
return width+" x "+height;
}
}
public static class Minimize {
public enum Type {
WIDTH,HEIGHT,BOTH,MAX
}
Integer minWidth;
Integer minHeight;
Integer minDim;
Type type;
public Minimize(int min, Type type) {
super();
this.type = type;
switch(type){
case WIDTH:
this.minWidth=min;
break;
case HEIGHT:
this.minHeight=min;
break;
case BOTH:
this.minWidth=min;
this.minHeight=min;
break;
case MAX:
this.minDim=min;
break;
}
}
public Minimize(int minWidth, int minHeight) {
super();
this.type=Type.BOTH;
this.minWidth = minWidth;
this.minHeight = minHeight;
}
}
/**
* Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
* @param width
* @param height
* @param config
* @return
*/
public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
long pixels=width*height;
switch(config){
case ALPHA_8: // 1 byte per pixel
return pixels;
case ARGB_4444: // 2 bytes per pixel, but depreciated
return pixels*2;
case ARGB_8888: // 4 bytes per pixel
return pixels*4;
case RGB_565: // 2 bytes per pixel
return pixels*2;
default:
return pixels;
}
}
private static BitmapFactory.Options getOptionsForDimensions(){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
return options;
}
private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inDither = false;
options.inSampleSize = sampleSize;
options.inScaled = false;
options.inPreferredConfig = bitmapConfig;
return options;
}
}
#21
14
In one of my application i need to take picture either from Camera/Gallery
. If user click image from Camera(may be 2MP, 5MP or 8MP), image size varies from kB
s to MB
s. If image size is less(or up to 1-2MB) above code working fine but if i have image of size above 4MB or 5MB then OOM
comes in frame :(
在我的一个应用程序中,我需要从相机/画廊中拍照。如果用户从相机上点击图像(可能是2MP, 5MP或8MP),图像的大小会因kBs到MBs而有所不同。如果图像大小小于(或高达1-2MB)以上代码运行良好,但如果我的图像大小超过4MB或5MB,那么OOM就会出现在框架中:(
then i have worked to solve this issue & finally i've made the below improvement to Fedor's(All Credit to Fedor for making such a nice solution) code :)
然后我努力解决这个问题&最后我已经把下面的改进变成了Fedor的(所有的都是Fedor的功劳)代码:)
private Bitmap decodeFile(String fPath) {
// Decode image size
BitmapFactory.Options opts = new BitmapFactory.Options();
/*
* If set to true, the decoder will return null (no bitmap), but the
* out... fields will still be set, allowing the caller to query the
* bitmap without having to allocate the memory for its pixels.
*/
opts.inJustDecodeBounds = true;
opts.inDither = false; // Disable Dithering mode
opts.inPurgeable = true; // Tell to gc that whether it needs free
// memory, the Bitmap can be cleared
opts.inInputShareable = true; // Which kind of reference will be used to
// recover the Bitmap data after being
// clear, when it will be used in the
// future
BitmapFactory.decodeFile(fPath, opts);
// The new size we want to scale to
final int REQUIRED_SIZE = 70;
// Find the correct scale value.
int scale = 1;
if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) opts.outHeight
/ (float) REQUIRED_SIZE);
final int widthRatio = Math.round((float) opts.outWidth
/ (float) REQUIRED_SIZE);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
}
// Decode bitmap with inSampleSize set
opts.inJustDecodeBounds = false;
opts.inSampleSize = scale;
Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
Bitmap.Config.RGB_565, false);
return bm;
}
I hope this will help the buddies facing the same problem!
我希望这能帮助朋友们面对同样的问题!
for more please refer this
更多信息请参考。
#22
13
I just ran into this issue a couple minutes ago. I solved it by doing a better job at managing my listview adapter. I thought it was an issue with the hundreds of 50x50px images I was using, turns out I was trying to inflate my custom view each time the row was being shown. Simply by testing to see if the row had been inflated I eliminated this error, and I am using hundreds of bitmaps. This is actually for a Spinner, but the base adapter works all the same for a ListView. This simple fix also greatly improved the performance of the adapter.
我几分钟前刚刚碰到这个问题。通过更好地管理listview适配器,我解决了这个问题。我认为这是我使用的数百个50x50px图像的问题,结果显示,每次显示这一行时,我都在试图膨胀自定义视图。简单地通过测试看看行是否被夸大了,我消除了这个错误,我使用了数百个位图。这实际上是针对一个Spinner,但是基本适配器对ListView的工作是相同的。这个简单的修复也大大提高了适配器的性能。
@Override
public View getView(final int position, View convertView, final ViewGroup parent) {
if(convertView == null){
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.spinner_row, null);
}
...
#23
12
I've spent the entire day testing these solutions and the only thing that worked for me is the above approaches for getting the image and manually calling the GC, which I know is not supposed to be necessary, but it is the only thing that worked when I put my app under heavy load testing switching between activities. My app has a list of thumbnail images in a listview in (lets say activity A) and when you click on one of those images it takes you to another activity (lets say activity B) that shows a main image for that item. When I would switch back and forth between the two activities, I would eventually get the OOM error and the app would force close.
我花了一整天测试这些解决方案和唯一为我工作上面的方法获取图像并手动调用GC,我知道这不是应该是必需的,但它是唯一工作当我把我的应用程序之间切换负载较重的情况下,测试活动。我的应用程序在listview中有一个缩略图列表(比如activity a),当你点击其中一个图像时,它会带你去另一个活动(比如活动B),它显示了该项目的主要图像。当我在这两种活动之间来回切换时,我最终会得到OOM错误,这款应用程序将会关闭。
When I would get half way down the listview it would crash.
当我走到一半的时候,它会崩溃。
Now when I implement the following in activity B, I can go through the entire listview with no issue and keep going and going and going...and its plenty fast.
现在,当我在活动B中实现以下内容时,我可以遍历整个listview,然后继续前进,继续前进。和它的足够快。
@Override
public void onDestroy()
{
Cleanup();
super.onDestroy();
}
private void Cleanup()
{
bitmap.recycle();
System.gc();
Runtime.getRuntime().gc();
}
#24
12
This issue only happens in Android emulators. I also faced this issue in an emulator but when I checked in a device then it worked fine.
这个问题只发生在Android模拟器中。我在模拟器中也遇到了这个问题,但是当我在一个设备中检查时,它运行得很好。
So please check in a device. It may be run in device.
所以请检查一下设备。它可以在设备中运行。
#25
11
My 2 cents: i solved my OOM errors with bitmaps by:
我的2分:我用位图解决了我的OOM错误:
a) scaling my images by a factor of 2
a)缩放图像的倍数为2。
b) using Picasso library in my custom Adapter for a ListView, with a one-call in getView like this: Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
b)在我的自定义适配器中使用毕加索库作为ListView,在getView中有一个这样的调用:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
#26
10
This code will help to load large bitmap from drawable
此代码将有助于从drawable加载大位图。
public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {
Context context;
public BitmapUtilsTask(Context context) {
this.context = context;
}
/**
* Loads a bitmap from the specified url.
*
* @param url The location of the bitmap asset
* @return The bitmap, or null if it could not be loaded
* @throws IOException
* @throws MalformedURLException
*/
public Bitmap getBitmap() throws MalformedURLException, IOException {
// Get the source image's dimensions
int desiredWidth = 1000;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);
int srcWidth = options.outWidth;
int srcHeight = options.outHeight;
// Only scale if the source is big enough. This code is just trying
// to fit a image into a certain width.
if (desiredWidth > srcWidth)
desiredWidth = srcWidth;
// Calculate the correct inSampleSize/scale value. This helps reduce
// memory use. It should be a power of 2
int inSampleSize = 1;
while (srcWidth / 2 > desiredWidth) {
srcWidth /= 2;
srcHeight /= 2;
inSampleSize *= 2;
}
// Decode with inSampleSize
options.inJustDecodeBounds = false;
options.inDither = false;
options.inSampleSize = inSampleSize;
options.inScaled = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inPurgeable = true;
Bitmap sampledSrcBitmap;
sampledSrcBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);
return sampledSrcBitmap;
}
/**
* The system calls this to perform work in a worker thread and delivers
* it the parameters given to AsyncTask.execute()
*/
@Override
protected Bitmap doInBackground(Object... item) {
try
{
return getBitmap();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
}
#27
10
use these code for every image in select from SdCard or drewable to convert bitmap object.
从SdCard中选择每一个图像,或者drewable来转换位图对象。
Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
System.gc();
}
bitmap = Bitmap.createScaledBitmap(BitmapFactory
.decodeFile(ImageData_Path.get(img_pos).getPath()),
width, height, true);
} catch (OutOfMemoryError e) {
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
System.gc();
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.RGB_565;
options.inSampleSize = 1;
options.inPurgeable = true;
bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
.getPath().toString(), options), width, height,true);
}
return bitmap;
use your image path instend of ImageData_Path.get(img_pos).getPath() .
使用ImageData_Path.get(img_pos). getpath()的图像路径。
#28
9
All the solutions here require setting a IMAGE_MAX_SIZE. This limits devices with more powerful hardware and if the image size is too low it looks ugly on the HD screen.
这里所有的解决方案都需要设置一个IMAGE_MAX_SIZE。这限制了硬件更强大的设备,如果图像尺寸过低,在高清屏幕上看起来很难看。
I came out with a solution that works with my Samsung Galaxy S3 and several other devices including less powerful ones, with better image quality when a more powerful device is used.
我推出了一个解决方案,它与我的三星Galaxy S3和其他一些设备一起工作,包括功能不那么强大的设备,当使用更强大的设备时,它的图像质量更好。
The gist of it is to calculate the maximum memory allocated for the app on a particular device, then set the scale to be lowest possible without exceeding this memory. Here's the code:
它的要点是计算在特定设备上为应用程序分配的最大内存,然后在不超过此内存的情况下将其设置为最低。这是代码:
public static Bitmap decodeFile(File f)
{
Bitmap b = null;
try
{
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
try
{
BitmapFactory.decodeStream(fis, null, o);
}
finally
{
fis.close();
}
// In Samsung Galaxy S3, typically max memory is 64mb
// Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
// If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
// We try use 25% memory which equals to 16mb maximum for one bitmap
long maxMemory = Runtime.getRuntime().maxMemory();
int maxMemoryForImage = (int) (maxMemory / 100 * 25);
// Refer to
// http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
// A full screen GridView filled with images on a device with
// 800x480 resolution would use around 1.5MB (800*480*4 bytes)
// When bitmap option's inSampleSize doubled, pixel height and
// weight both reduce in half
int scale = 1;
while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
scale *= 2;
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
try
{
b = BitmapFactory.decodeStream(fis, null, o2);
}
finally
{
fis.close();
}
}
catch (IOException e)
{
}
return b;
}
I set the maximum memory used by this bitmap to be 25% of maximum allocated memory, you may need to adjust this to your needs and make sure this bitmap is cleaned up and don't stay in memory when you've finished using it. Typically I use this code to perform image rotation (source and destination bitmap) so my app needs to load 2 bitmaps in memory at the same time, and 25% gives me a good buffer without running out of memory when performing image rotation.
我将这个位图所使用的最大内存设置为最大分配内存的25%,您可能需要将其调整到您的需要,并确保该位图已被清理,并且在您使用完它之后,不要停留在内存中。通常,我使用这个代码来执行图像旋转(源和目标位图),因此我的应用程序需要同时加载两个位图,而25%给了我一个很好的缓冲,在执行图像旋转时不会耗尽内存。
Hope this helps someone out there..
希望这能帮助到外面的人。
#29
9
Such OutofMemoryException
cannot be totally resolved by calling the System.gc()
and so on .
这样的OutofMemoryException不能通过调用System.gc()等方式完全解决。
By referring to the
Activity Life Cycle
指的是活动生命周期。
The Activity States are determined by the OS itself subject to the memory usage for each process and the priority of each process.
活动状态由操作系统本身决定,取决于每个进程的内存使用情况和每个进程的优先级。
You may consider the size and the resolution for each of the bitmap pictures used. I recommend to reduce the size ,resample to lower resolution , refer to the design of galleries (one small picture PNG , and one original picture.)
您可以考虑使用的每个位图图片的大小和分辨率。我建议减小尺寸,重新采样,以降低分辨率,参考图库的设计(一张小图片PNG,和一张原始图片)。
#30
8
Generally android device heap size is only 16MB (varies from device/OS see post Heap Sizes), if you are loading the images and it crosses the size of 16MB , it will throw out of memory exception, instead of using the Bitmap for , loading images from SD card or from resources or even from network try to using getImageUri , loading bitmap require more memory , or you can set bitmap to null if your work done with that bitmap.
一般android设备堆大小只有16 mb(不同设备/ OS见帖子堆大小),如果你是加载图片和它穿过16 mb的大小,它将抛出内存不足异常,而不是使用的位图加载图像从SD卡或从网络资源甚至试图使用getImageUri,加载位图需要更多的内存,或者你可以设置位图位图的零如果你的工作完成了。
#1
524
The Android Training class, "Displaying Bitmaps Efficiently", offers some great information for understanding and dealing with the exception java.lang.OutOfMemoryError: bitmap size exceeds VM budget
when loading Bitmaps.
Android培训类“高效显示位图”,提供了一些了解和处理异常java.lang的重要信息。OutOfMemoryError:在加载位图时,位图的大小超过了VM预算。
Read Bitmap Dimensions and Type
The BitmapFactory
class provides several decoding methods (decodeByteArray()
, decodeFile()
, decodeResource()
, etc.) for creating a Bitmap
from various sources. Choose the most appropriate decode method based on your image data source. These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in an OutOfMemory
exception. Each type of decode method has additional signatures that let you specify decoding options via the BitmapFactory.Options
class. Setting the inJustDecodeBounds
property to true
while decoding avoids memory allocation, returning null
for the bitmap object but setting outWidth
, outHeight
and outMimeType
. This technique allows you to read the dimensions and type of the image data prior to construction (and memory allocation) of the bitmap.
BitmapFactory类提供了几个解码方法(decodeByteArray()、decodeFile()、decodeResource()等),用于从各种来源创建位图。根据您的图像数据源选择最合适的解码方法。这些方法试图为构造的位图分配内存,因此很容易导致OutOfMemory异常。每种解码方法都有额外的签名,可以通过BitmapFactory指定解码选项。选择类。在解码避免内存分配的情况下,将伤害解码的属性设置为true,返回null的位图对象,但设置输出宽度,outHeight和outMimeType。该技术允许您在构建(和内存分配)位图之前读取图像数据的尺寸和类型。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
To avoid java.lang.OutOfMemory
exceptions, check the dimensions of a bitmap before decoding it, unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory.
避免. lang。OutOfMemory异常,在解码之前检查位图的大小,除非您绝对信任该源,以便为您提供适合于可用内存的可预测大小的图像数据。
Load a scaled down version into Memory
Now that the image dimensions are known, they can be used to decide if the full image should be loaded into memory or if a subsampled version should be loaded instead. Here are some factors to consider:
现在已经知道了图像的大小,可以使用它们来决定是否将完整的图像加载到内存中,或者是否应该加载一个子采样版本。以下是一些需要考虑的因素:
- Estimated memory usage of loading the full image in memory.
- 估计内存的使用,在内存中加载完整的映像。
- The amount of memory you are willing to commit to loading this image given any other memory requirements of your application.
- 在您的应用程序的任何其他内存需求的情况下,您愿意提交该映像的内存数量。
- Dimensions of the target ImageView or UI component that the image is to be loaded into.
- 要加载图像的目标ImageView或UI组件的维度。
- Screen size and density of the current device.
- 屏幕大小和当前设备的密度。
For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x96 pixel thumbnail in an ImageView
.
例如,如果将1024x768像素图像加载到内存中,如果它最终在ImageView中显示在128x96像素的缩略图中,那么它就不值得加载。
To tell the decoder to subsample the image, loading a smaller version into memory, set inSampleSize
to true
in your BitmapFactory.Options
object. For example, an image with resolution 2048x1536 that is decoded with an inSampleSize
of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image (assuming a bitmap configuration of ARGB_8888
). Here’s a method to calculate a sample size value that is a power of two based on a target width and height:
要告诉解码器对图像进行子采样,将较小的版本加载到内存中,在您的BitmapFactory中设置inSampleSize为真。选择对象。例如,有一个分辨率为2048x1536的图像,它被解码为一个inSampleSize 4,生成一个大约512x384的位图。将其加载到内存中使用0.75MB而不是12MB的完整映像(假设ARGB_8888的位图配置)。这里有一种计算样本大小值的方法,它是基于目标宽度和高度的两倍的幂:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Note: A power of two value is calculated because the decoder uses a final value by rounding down to the nearest power of two, as per the
inSampleSize
documentation.注意:两个值的幂是计算出来的,因为解码器使用最后的值,根据inSampleSize的文档,将其舍入到最近的两个幂。
To use this method, first decode with inJustDecodeBounds
set to true
, pass the options through and then decode again using the new inSampleSize
value and inJustDecodeBounds
set to false
:
若要使用此方法,首先要对受伤害的解密程序进行解码,然后将选项通过,然后再使用新的inSampleSize值和被破坏的反译码器进行解码:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
This method makes it easy to load a bitmap of arbitrarily large size into an ImageView
that displays a 100x100 pixel thumbnail, as shown in the following example code:
该方法使得将任意大尺寸的位图加载到显示100x100像素缩略图的ImageView中很容易,如下面的示例代码所示:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
You can follow a similar process to decode bitmaps from other sources, by substituting the appropriate BitmapFactory.decode*
method as needed.
您可以按照类似的过程从其他来源解码位图,并根据需要替换适当的位图。
#2
847
To fix the OutOfMemory error, you should do something like this:
要修复OutOfMemory错误,您应该这样做:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);
This inSampleSize
option reduces memory consumption.
这个inSampleSize选项可以减少内存消耗。
Here's a complete method. First it reads image size without decoding the content itself. Then it finds the best inSampleSize
value, it should be a power of 2, and finally the image is decoded.
这是一个完整的方法。首先,它读取图像大小而不解码内容本身。然后找到最佳的采样值,它应该是2的幂,最后是图像解码。
// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
try {
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
// The new size we want to scale to
final int REQUIRED_SIZE=70;
// Find the correct scale value. It should be the power of 2.
int scale = 1;
while(o.outWidth / scale / 2 >= REQUIRED_SIZE &&
o.outHeight / scale / 2 >= REQUIRED_SIZE) {
scale *= 2;
}
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}
#3
349
I've made a small improvement to Fedor's code. It basically does the same, but without the (in my opinion) ugly while loop and it always results in a power of two. Kudos to Fedor for making the original solution, I was stuck until I found his, and then I was able to make this one :)
我对费多的代码做了一点改进。它基本上是一样的,但是没有(在我看来)丑陋的while循环,它总是会产生两个的力量。在我找到他之前,我一直被困住,直到我找到了他,然后我才得以实现这个目标:)
private Bitmap decodeFile(File f){
Bitmap b = null;
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
int scale = 1;
if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /
(double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
}
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
return b;
}
#4
218
I come from iOS experience and I was frustrated to discover an issue with something so basic as loading and showing an image. After all, everyone that is having this issue is trying to display reasonably sized images. Anyway, here are the two changes that fixed my problem (and made my app very responsive).
我来自iOS的经验,我很沮丧地发现了一个问题,它的基础是加载和显示图像。毕竟,每个有这个问题的人都在试图展示合理大小的图片。无论如何,这是修正了我的问题的两个变化(并且使我的应用程序非常敏感)。
1) Every time you do BitmapFactory.decodeXYZ()
, make sure to pass in a BitmapFactory.Options
with inPurgeable
set to true
(and preferably with inInputShareable
also set to true
).
每次你做BitmapFactory. decodexyz()时,一定要在BitmapFactory中通过。选项为true(最好是inInputShareable也设置为true)。
2) NEVER use Bitmap.createBitmap(width, height, Config.ARGB_8888)
. I mean NEVER! I've never had that thing not raise memory error after few passes. No amount of recycle()
, System.gc()
, whatever helped. It always raised exception. The one other way that actually works is to have a dummy image in your drawables (or another Bitmap that you decoded using step 1 above), rescale that to whatever you want, then manipulate the resulting Bitmap (such as passing it on to a Canvas for more fun). So, what you should use instead is: Bitmap.createScaledBitmap(srcBitmap, width, height, false)
. If for whatever reason you MUST use the brute force create method, then at least pass Config.ARGB_4444
.
2)不要使用位图。createBitmap(宽度、高度,Config.ARGB_8888)。我的意思是永远!我从来没有过这样的事情,在经过几次之后就不会引起内存错误。没有任何回收(),System.gc(),任何帮助。它总是提出例外。另一种方法实际上是在您的drawables(或您使用第1步解码的另一个位图)中创建一个虚拟映像,将其重新扩展到您想要的任何位置,然后操作生成的位图(例如将其传递到画布上以获得更多的乐趣)。因此,您应该使用的是:位图。createScaledBitmap(srcBitmap,宽度,高度,假)。如果出于某种原因,您必须使用蛮力创建方法,那么至少要通过Config.ARGB_4444。
This is almost guaranteed to save you hours if not days. All that talk about scaling the image, etc. does not really work (unless you consider getting wrong size or degraded image a solution).
这几乎可以保证,如果不是几天的话,你可以节省几个小时。所有关于缩放图像的讨论都没有真正起作用(除非您考虑使用错误的大小或降级的图像作为解决方案)。
#5
83
It's a known bug, it's not because of large files. Since Android Caches the Drawables, it's going out of memory after using few images. But I've found an alternate way for it, by skipping the android default cache system.
这是一个已知的错误,不是因为大文件。由于Android缓存了Drawables,在使用了很少的图像之后,它就会从内存中消失。但我找到了另一种方法,跳过android默认的缓存系统。
Solution: Move the images to "assets" folder and use the following function to get BitmapDrawable:
解决方案:将图像移动到“assets”文件夹,并使用以下函数获取位图:
public static Drawable getAssetImage(Context context, String filename) throws IOException {
AssetManager assets = context.getResources().getAssets();
InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
Bitmap bitmap = BitmapFactory.decodeStream(buffer);
return new BitmapDrawable(context.getResources(), bitmap);
}
#6
68
I had this same issue and solved it by avoiding the BitmapFactory.decodeStream or decodeFile functions and instead used BitmapFactory.decodeFileDescriptor
我有同样的问题,并通过避免BitmapFactory.decodeStream或decodeFile函数来解决它,而是使用了bitmapfactory . decodetledescriptor。
decodeFileDescriptor
looks like it calls different native methods than the decodeStream/decodeFile.
与decodeStream/decodeFile相比,解码描述符看起来像是调用了不同的本机方法。
Anyways, what worked was this (note that I added some options as some had above, but that's not what made the difference. What is critical is the call to BitmapFactory.decodeFileDescriptor instead of decodeStream or decodeFile):
不管怎么说,这(注意到我在上面添加了一些选项,但这并不是造成差异的原因)。关键的是对位图因子的调用。decodeledescriptor而不是decodeStream或decodeFile):
private void showImage(String path) {
Log.i("showImage","loading:"+path);
BitmapFactory.Options bfOptions=new BitmapFactory.Options();
bfOptions.inDither=false; //Disable Dithering mode
bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared
bfOptions.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
bfOptions.inTempStorage=new byte[32 * 1024];
File file=new File(path);
FileInputStream fs=null;
try {
fs = new FileInputStream(file);
} catch (FileNotFoundException e) {
//TODO do something intelligent
e.printStackTrace();
}
try {
if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
} catch (IOException e) {
//TODO do something intelligent
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
im.setImageBitmap(bm);
//bm.recycle();
bm=null;
}
I think there is a problem with the native function used in decodeStream/decodeFile. I have confirmed that a different native method is called when using decodeFileDescriptor. Also what I've read is "that Images (Bitmaps) are not allocated in a standard Java way but via native calls; the allocations are done outside of the virtual heap, but are counted against it!"
我认为在decodeStream/decodeFile中使用的本机函数存在一个问题。我已经确认了在使用解码描述符时调用了不同的本机方法。我所读到的是“这些图像(位图)不是按照标准Java方式分配的,而是通过本地调用来分配的;这些分配是在虚拟堆之外完成的,但是被计算出来了!
#7
62
I think best way to avoid the OutOfMemoryError
is to face it and understand it.
我认为避免OutOfMemoryError的最好方法是面对它并理解它。
I made an app to intentionally cause OutOfMemoryError
, and monitor memory usage.
我做了一个应用程序,故意造成OutOfMemoryError错误,并监视内存使用情况。
After I've done a lot of experiments with this App, I've got the following conclusions:
在我做了很多这个应用的实验之后,我得出了以下结论:
I'm gonna talk about SDK versions before Honey Comb first.
我要先讲一下SDK版本。
-
Bitmap is stored in native heap, but it will get garbage collected automatically, calling recycle() is needless.
位图存储在本机堆中,但它会自动收集垃圾,调用回收()是不必要的。
-
If {VM heap size} + {allocated native heap memory} >= {VM heap size limit for the device}, and you are trying to create bitmap, OOM will be thrown.
如果{VM堆大小}+{分配了本机堆内存}>= {VM堆大小限制},并且您正在尝试创建位图,那么OOM将被抛出。
NOTICE: VM HEAP SIZE is counted rather than VM ALLOCATED MEMORY.
注意:VM堆大小被计数,而不是VM分配的内存。
-
VM Heap size will never shrink after grown, even if the allocated VM memory is shrinked.
即使已分配的VM内存被收缩,VM堆大小也不会缩小。
-
So you have to keep the peak VM memory as low as possible to keep VM Heap Size from growing too big to save available memory for Bitmaps.
因此,您必须尽可能地保持最大的VM内存,以保持VM堆大小的增长,从而使其无法保存用于位图的可用内存。
-
Manually call System.gc() is meaningless, the system will call it first before trying to grow the heap size.
手动调用system .gc()是没有意义的,在尝试增加堆大小之前,系统会先调用它。
-
Native Heap Size will never shrink too, but it's not counted for OOM, so no need to worry about it.
本机堆大小也不会缩小,但它不为OOM计数,因此无需担心。
Then, let's talk about SDK Starts from Honey Comb.
然后,让我们讨论一下从蜂巢开始的SDK。
-
Bitmap is stored in VM heap, Native memory is not counted for OOM.
位图存储在VM堆中,本机内存不计入OOM。
-
The condition for OOM is much simpler: {VM heap size} >= {VM heap size limit for the device}.
OOM的条件更简单:{VM堆大小}>= {VM堆大小限制}。
-
So you have more available memory to create bitmap with the same heap size limit, OOM is less likely to be thrown.
因此,您有更多可用内存来创建具有相同堆大小限制的位图,OOM不太可能被抛出。
Here is some of my observations about Garbage Collection and Memory Leak.
下面是我对垃圾收集和内存泄漏的一些观察。
You can see it yourself in the App. If an Activity executed an AsyncTask that was still running after the Activity was destroyed, the Activity will not get garbage collected until the AsyncTask finish.
您可以在应用程序中看到它。如果一个活动执行了一个在活动被破坏后仍在运行的AsyncTask,那么在AsyncTask完成之前,活动将不会被收集到垃圾。
This is because AsyncTask is an instance of an anonymous inner class, it holds a reference of the Activity.
这是因为AsyncTask是匿名内部类的一个实例,它包含活动的引用。
Calling AsyncTask.cancel(true) will not stop the execution if the task is blocked in an IO operation in background thread.
如果任务在后台线程的IO操作中被阻塞,则调用AsyncTask.cancel(true)不会停止执行。
Callbacks are anonymous inner classes too, so if a static instance in your project holds them and do not release them, memory would be leaked.
回调是匿名的内部类,所以如果您的项目中的一个静态实例持有它们并且不释放它们,那么内存将被泄漏。
If you scheduled a repeating or delayed task, for example a Timer, and you do not call cancel() and purge() in onPause(), memory would be leaked.
如果您安排了一个重复或延迟的任务,例如一个计时器,并且在onPause()中不调用cancel()和清除(),内存就会被泄漏。
#8
57
I have seen a lot of questions about OOM exceptions and caching lately. The developer guide has a really good article on this, but some tends to fail on implementing it in a suitable way.
最近我看到了很多关于OOM异常和缓存的问题。开发人员指南上有一篇非常好的文章,但是一些人倾向于以一种合适的方式来实现它。
Because of this I wrote an example application that demonstrates caching in an Android environment. This implementation has not yet gotten an OOM.
因此,我编写了一个示例应用程序,在Android环境中演示缓存。这个实现还没有得到一个OOM。
Look at the end of this answer for a link to the source code.
查看这个答案的末尾,链接到源代码。
Requirements:
- Android API 2.1 or higher (I simply could not manage to get the available memory for an application in API 1.6 - that is the only piece of code that doesn't work in API 1.6)
- Android API 2.1或更高版本(我只是无法获得API 1.6中应用程序的可用内存),这是API 1.6中唯一不能使用的代码。
- Android support package
- Android支持包
Features:
- Retains the cache if there is an orientation change, using a singleton
- 如果有方向变化,则使用singleton来保留缓存。
- Use one eighth of the assigned application memory to the cache (modify if you want)
- 将指定的应用程序内存的八分之一用于缓存(如果需要,可以修改)
- Large bitmaps gets scaled (you can define the maximum pixels that you want to allow)
- 大位图被缩放(你可以定义你想要的最大像素)
- Controls that there is an internet connection available before downloading the bitmaps
- 在下载位图之前,可以使用internet连接。
- Makes sure that you are only instantiating one task per row
- 确保每个行只实例化一个任务。
- If you are flinging the
ListView
away, it simply won't download the bitmaps between - 如果你把ListView去掉,它根本不会下载位图。
This does not include:
- Disk caching. This should be easy to implement anyway - just point to a different task that grabs the bitmaps from the disk
- 磁盘缓存。无论如何,这都应该很容易实现—只需指向从磁盘获取位图的不同任务。
Sample code:
The images that are being downloaded are images (75x75) from Flickr. However, put whatever image urls you want to be processed, and the application will scale it down if it exceeds the maximum. In this application the urls are simply in a String
array.
正在下载的图像是来自Flickr的图像(75x75)。但是,将您想要处理的任何图像url放在一起,如果它超过了最大值,应用程序就会将其缩小。在这个应用程序中,url仅仅是在一个字符串数组中。
The LruCache
has a good way to deal with bitmaps. However, in this application I put an instance of an LruCache
inside another cache class that I created in order to get the application more feasible.
LruCache有一个很好的处理位图的方法。但是,在这个应用程序中,我在另一个缓存类中放置了一个LruCache实例,以便使应用程序更可行。
Cache.java's critical stuff (the loadBitmap()
method is the most important):
缓存。java的关键内容(loadBitmap()方法是最重要的):
public Cache(int size, int maxWidth, int maxHeight) {
// Into the constructor you add the maximum pixels
// that you want to allow in order to not scale images.
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
mBitmapCache = new LruCache<String, Bitmap>(size) {
protected int sizeOf(String key, Bitmap b) {
// Assuming that one pixel contains four bytes.
return b.getHeight() * b.getWidth() * 4;
}
};
mCurrentTasks = new ArrayList<String>();
}
/**
* Gets a bitmap from cache.
* If it is not in cache, this method will:
*
* 1: check if the bitmap url is currently being processed in the
* BitmapLoaderTask and cancel if it is already in a task (a control to see
* if it's inside the currentTasks list).
*
* 2: check if an internet connection is available and continue if so.
*
* 3: download the bitmap, scale the bitmap if necessary and put it into
* the memory cache.
*
* 4: Remove the bitmap url from the currentTasks list.
*
* 5: Notify the ListAdapter.
*
* @param mainActivity - Reference to activity object, in order to
* call notifyDataSetChanged() on the ListAdapter.
* @param imageKey - The bitmap url (will be the key).
* @param imageView - The ImageView that should get an
* available bitmap or a placeholder image.
* @param isScrolling - If set to true, we skip executing more tasks since
* the user probably has flinged away the view.
*/
public void loadBitmap(MainActivity mainActivity,
String imageKey, ImageView imageView,
boolean isScrolling) {
final Bitmap bitmap = getBitmapFromCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.ic_launcher);
if (!isScrolling && !mCurrentTasks.contains(imageKey) &&
mainActivity.internetIsAvailable()) {
BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
mainActivity.getAdapter());
task.execute();
}
}
}
You shouldn't need to edit anything in the Cache.java file unless you want to implement disk caching.
您不需要在缓存中编辑任何东西。java文件,除非您想实现磁盘缓存。
MainActivity.java's critical stuff:
MainActivity。java的关键内容:
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (view.getId() == android.R.id.list) {
// Set scrolling to true only if the user has flinged the
// ListView away, hence we skip downloading a series
// of unnecessary bitmaps that the user probably
// just want to skip anyways. If we scroll slowly it
// will still download bitmaps - that means
// that the application won't wait for the user
// to lift its finger off the screen in order to
// download.
if (scrollState == SCROLL_STATE_FLING) {
mIsScrolling = true;
} else {
mIsScrolling = false;
mListAdapter.notifyDataSetChanged();
}
}
}
// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
final ViewHolder holder;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.main_listview_row, parent, false);
holder = new ViewHolder(row);
row.setTag(holder);
} else {
holder = (ViewHolder) row.getTag();
}
final Row rowObject = getItem(position);
// Look at the loadBitmap() method description...
holder.mTextView.setText(rowObject.mText);
mCache.loadBitmap(MainActivity.this,
rowObject.mBitmapUrl, holder.mImageView,
mIsScrolling);
return row;
}
getView()
gets called very often. It's normally not a good idea to download images there if we haven't implemented a check that ensure us that we won't start an infinite amount of threads per row. Cache.java checks whether the rowObject.mBitmapUrl
already is in a task and if it is, it won't start another. Therefore, we are most likely not exceeding the work queue restriction from the AsyncTask
pool.
getView()经常被调用。如果我们没有实现一个检查,确保我们不会每一行启动一个无限数量的线程,那么在那里下载图像通常不是一个好主意。缓存。java检查行对象。mBitmapUrl已经在一个任务中,如果它是,它不会启动另一个。因此,我们很可能不超过来自AsyncTask池的工作队列限制。
Download:
You can download the source code from https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip.
您可以从https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip下载源代码。
Last words:
I have tested this for a few weeks now, I haven't gotten a single OOM exception yet. I have tested this on the emulator, on my Nexus One and on my Nexus S. I have tested image urls that contain images that were in HD quality. The only bottleneck is that it takes more time to download.
我已经测试了几个星期了,我还没有得到一个OOM例外。我在我的Nexus One和Nexus s上测试了这款产品,我测试了包含高清质量图像的图像url。唯一的瓶颈是需要更多的时间下载。
There is only one possible scenario where I can imagine that the OOM will appear, and that is if we download many, really big images, and before they get scaled and put into cache, will simultaneously take up more memory and cause an OOM. But that isn't even an ideal situation anyway and it most likely won't be possible to solve in a more feasible way.
只有一个可能的场景,我可以想象OOM会出现,那就是如果我们下载了很多非常大的图像,在它们被缩放和放入缓存之前,会同时占用更多的内存并导致一个OOM。但这并不是一个理想的情况,而且很可能无法以更可行的方式解决。
Report errors in the comments! :-)
在评论中报告错误!:-)
#9
36
I did the following to take the image and resize it on the fly. Hope this helps
我做了如下图,并在飞行中调整它的大小。希望这有助于
Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);
#10
31
It seems that this is a very long running problem, with a lot of differing explanations. I took the advice of the two most common presented answers here, but neither one of these solved my problems of the VM claiming it couldn't afford the bytes to perform the decoding part of the process. After some digging I learned that the real problem here is the decoding process taking away from the NATIVE heap.
这似乎是一个长期存在的问题,有许多不同的解释。我在这里听取了两个最常见的答案的建议,但这两个问题都没有解决我的问题,因为VM声称它无法负担执行解码部分的字节。在进行了一些挖掘之后,我了解到真正的问题在于从本机堆中提取的解码过程。
See here: BitmapFactory OOM driving me nuts
看这里:BitmapFactory OOM把我逼疯了。
That lead me to another discussion thread where I found a couple more solutions to this problem. One is to callSystem.gc();
manually after your image is displayed. But that actually makes your app use MORE memory, in an effort to reduce the native heap. The better solution as of the release of 2.0 (Donut) is to use the BitmapFactory option "inPurgeable". So I simply added o2.inPurgeable=true;
just after o2.inSampleSize=scale;
.
这就引出了另一个讨论线程,在这里我找到了更多解决这个问题的方法。一个是callSystem.gc();在您的图像显示之后手动操作。但这实际上使您的应用程序使用更多的内存,以减少本机堆。对于2.0版本(Donut)来说,更好的解决方案是使用“inPurgeable”的BitmapFactory选项。所以我只是添加了o2。inpurgeable =true;刚刚o2.inSampleSize =规模;。
More on that topic here: Is the limit of memory heap only 6M?
更多关于这个主题的内容:内存堆的极限只有6M吗?
Now, having said all of this, I am a complete dunce with Java and Android too. So if you think this is a terrible way to solve this problem, you are probably right. ;-) But this has worked wonders for me, and I have found it impossible to run the VM out of heap cache now. The only drawback I can find is that you are trashing your cached drawn image. Which means if you go RIGHT back to that image, you are redrawing it each and every time. In the case of how my application works, that is not really a problem. Your mileage may vary.
现在,我已经说了所有这些,我是一个完整的dunce和Java和Android。所以如果你认为这是解决这个问题的糟糕方法,你可能是对的。但是这对我来说很有用,我发现现在不可能从堆缓存中运行VM。我能发现的唯一缺点是,您正在对缓存的绘制图像进行破坏。也就是说,如果你回到那个图像,你每次都重新画它。在我的应用程序如何工作的情况下,这并不是一个真正的问题。你的情况可能不同。
#11
29
unfortunately if None of the Above works, then Add this to your Manifest file. Inside application tag
不幸的是,如果以上这些都没有工作,那么将它添加到您的清单文件中。内部应用程序标记
<application
android:largeHeap="true"
#12
26
Use this bitmap.recycle();
This helps without any image quality issue.
使用这个bitmap.recycle();这有助于避免任何图像质量问题。
#13
24
I have a much more effective solution which does not need scaling of any sort. Simply decode your bitmap only once and then cache it in a map against its name. Then simply retrieve the bitmap against the name and set it in the ImageView. There is nothing more that needs to be done.
我有一个更有效的解决方案,不需要任何形式的缩放。只需简单地解码您的位图,然后将其缓存到与它的名称相对应的映射中。然后简单地检索位图并将其设置在ImageView中。没有什么事情需要做了。
This will work because the actual binary data of the decoded bitmap is not stored within the dalvik VM heap. It is stored externally. So every time you decode a bitmap, it allocates memory outside of VM heap which is never reclaimed by GC
这将起作用,因为解码位图的实际二进制数据不存储在dalvik VM堆中。它是存储在外部。因此,每当您解码一个位图时,它就会分配到VM堆之外的内存,而这永远不会被GC回收。
To help you better appreciate this, imagine you have kept ur image in the drawable folder. You just get the image by doing a getResources().getDrwable(R.drawable.). This will NOT decode your image everytime but re-use an already decoded instance everytime you call it. So in essence it is cached.
为了帮助您更好地理解这一点,请想象您将您的图像保存在drawable文件夹中。通过做getResources(). getdrwable (R.drawable.)来获取图像。这不会每次都解码你的图像,但是每次你调用它的时候都要重新使用一个已经解码的实例。本质上它被缓存了。
Now since your image is in a file somewhere (or may even be coming from an external server), it is YOUR responsibility to cache the decoded bitmap instance to be reused any where it is needed.
既然您的映像在某个文件中(或者甚至可能来自外部服务器),那么您就有责任缓存已解码的位图实例,以便在需要的地方重用它。
Hope this helps.
希望这个有帮助。
#14
24
I have resolved the same issue in the following manner.
我以下列方式解决了同样的问题。
Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
b.eraseColor(0xFFFFFFFF);
Rect r = new Rect(0, 0,320 , 424);
Canvas c = new Canvas(b);
Paint p = new Paint();
p.setColor(0xFFC0C0C0);
c.drawRect(r, p);
d = mContext.getResources().getDrawable(mImageIds[position]);
d.setBounds(r);
d.draw(c);
/*
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inTempStorage = new byte[128*1024];
b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
o2.inSampleSize=16;
o2.inPurgeable = true;
*/
} catch (Exception e) {
}
i.setImageBitmap(b);
#15
23
There are two issues here....
这里有两个问题....
- Bitmap memory isn't in the VM heap but rather in the native heap - see BitmapFactory OOM driving me nuts
- 位图内存不是在VM堆中,而是在本机堆中——看到BitmapFactory OOM让我抓狂。
- Garbage collection for the native heap is lazier than the VM heap - so you need to be quite aggressive about doing bitmap.recycle and bitmap =null every time you go through an Activity's onPause or onDestroy
- 本机堆的垃圾收集比VM堆更懒——因此您需要非常积极地处理位图。每次执行活动的onPause或onDestroy时,循环和位图=null。
#16
23
This worked for me!
这为我工作!
public Bitmap readAssetsBitmap(String filename) throws IOException {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
if(bitmap == null) {
throw new IOException("File cannot be opened: It's value is null");
} else {
return bitmap;
}
} catch (IOException e) {
throw new IOException("File cannot be opened: " + e.getMessage());
}
}
#17
19
Great answers here, but I wanted a fully usable class to address this problem.. so I did one.
这里有很好的答案,但我想要一个完全可用的类来解决这个问题。所以我做了一个。
Here is my BitmapHelper class that is OutOfMemoryError proof :-)
这里是我的BitmapHelper类,它是OutOfMemoryError proof:-)
import java.io.File;
import java.io.FileInputStream;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
public class BitmapHelper
{
//decodes image and scales it to reduce memory consumption
public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
{
try
{
//Decode image size
BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
bitmapSizeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);
// load image using inSampleSize adapted to required image size
BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
bitmapDecodeOptions.inPurgeable = true;
bitmapDecodeOptions.inDither = !quickAndDirty;
bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;
Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);
// scale bitmap to mathc required size (and keep aspect ratio)
float srcWidth = (float) bitmapDecodeOptions.outWidth;
float srcHeight = (float) bitmapDecodeOptions.outHeight;
float dstWidth = (float) requiredWidth;
float dstHeight = (float) requiredHeight;
float srcAspectRatio = srcWidth / srcHeight;
float dstAspectRatio = dstWidth / dstHeight;
// recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
// (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
// java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
// I do not excatly understand why, but this way it's OK
boolean recycleDecodedBitmap = false;
Bitmap scaledBitmap = decodedBitmap;
if (srcAspectRatio < dstAspectRatio)
{
scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
// will recycle recycleDecodedBitmap
recycleDecodedBitmap = true;
}
else if (srcAspectRatio > dstAspectRatio)
{
scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
recycleDecodedBitmap = true;
}
// crop image to match required image size
int scaledBitmapWidth = scaledBitmap.getWidth();
int scaledBitmapHeight = scaledBitmap.getHeight();
Bitmap croppedBitmap = scaledBitmap;
if (scaledBitmapWidth > requiredWidth)
{
int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
scaledBitmap.recycle();
}
else if (scaledBitmapHeight > requiredHeight)
{
int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
scaledBitmap.recycle();
}
if (recycleDecodedBitmap)
{
decodedBitmap.recycle();
}
decodedBitmap = null;
scaledBitmap = null;
return croppedBitmap;
}
catch (Exception ex)
{
ex.printStackTrace();
}
return null;
}
/**
* compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
*
* @param requiredWidth
* @param requiredHeight
* @param powerOf2
* weither we want a power of 2 sclae or not
* @return
*/
public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
{
int inSampleSize = 1;
// Raw height and width of image
final int srcHeight = options.outHeight;
final int srcWidth = options.outWidth;
if (powerOf2)
{
//Find the correct scale value. It should be the power of 2.
int tmpWidth = srcWidth, tmpHeight = srcHeight;
while (true)
{
if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
break;
tmpWidth /= 2;
tmpHeight /= 2;
inSampleSize *= 2;
}
}
else
{
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public static Bitmap drawableToBitmap(Drawable drawable)
{
if (drawable instanceof BitmapDrawable)
{
return ((BitmapDrawable) drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
{
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// RECREATE THE NEW BITMAP
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
}
#18
18
None of the answers above worked for me, but I did come up with a horribly ugly workaround that solved the problem. I added a very small, 1x1 pixel image to my project as a resource, and loaded it into my ImageView before calling into garbage collection. I think it might be that the ImageView was not releasing the Bitmap, so GC never picked it up. It's ugly, but it seems to be working for now.
以上的答案都不适合我,但我确实想出了一个非常丑陋的解决方法。我将一个非常小的1x1像素图像添加到我的项目中作为一个资源,并将它加载到我的ImageView中,然后调用垃圾收集。我想可能是ImageView没有释放位图,所以GC没有把它捡起来。它很丑,但它现在似乎在起作用。
if (bitmap != null)
{
bitmap.recycle();
bitmap = null;
}
if (imageView != null)
{
imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();
imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.
#19
18
This works for me.
这适合我。
Bitmap myBitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;
File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);
and this is on C# monodroid. you can easily change the path of the image. what important here is the options to be set.
这是在c#单机器人上。你可以很容易地改变图像的路径。这里重要的是要设置的选项。
#20
14
This seems like the appropriate place to share my utility class for loading and processing images with the community, you are welcome to use it and modify it freely.
这似乎是一个合适的地方,可以共享我的实用程序类来加载和处理与社区的图像,欢迎您使用它并*修改它。
package com.emil;
import java.io.IOException;
import java.io.InputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
/**
* A class to load and process images of various sizes from input streams and file paths.
*
* @author Emil http://*.com/users/220710/emil
*
*/
public class ImageProcessing {
public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
if(ImageProcessing.checkDecode(options)){
return bm;
}else{
throw new IOException("Image decoding failed, using stream.");
}
}
public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
if(ImageProcessing.checkDecode(options)){
return bm;
}else{
throw new IOException("Image decoding failed, using file path.");
}
}
public static Dimensions getDimensions(InputStream stream) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
BitmapFactory.decodeStream(stream,null,options);
if(ImageProcessing.checkDecode(options)){
return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
}else{
throw new IOException("Image decoding failed, using stream.");
}
}
public static Dimensions getDimensions(String imgPath) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
BitmapFactory.decodeFile(imgPath,options);
if(ImageProcessing.checkDecode(options)){
return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
}else{
throw new IOException("Image decoding failed, using file path.");
}
}
private static boolean checkDecode(BitmapFactory.Options options){
// Did decode work?
if( options.outWidth<0 || options.outHeight<0 ){
return false;
}else{
return true;
}
}
/**
* Creates a Bitmap that is of the minimum dimensions necessary
* @param bm
* @param min
* @return
*/
public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
int newWidth, newHeight;
switch(min.type){
case WIDTH:
if(bm.getWidth()>min.minWidth){
newWidth=min.minWidth;
newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
break;
case HEIGHT:
if(bm.getHeight()>min.minHeight){
newHeight=min.minHeight;
newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
break;
case BOTH: // minimize to the maximum dimension
case MAX:
if(bm.getHeight()>bm.getWidth()){
// Height needs to minimized
min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
if(bm.getHeight()>min.minDim){
newHeight=min.minDim;
newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
}else{
// Width needs to be minimized
min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
if(bm.getWidth()>min.minDim){
newWidth=min.minDim;
newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
}
break;
default:
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
}
public static int getScaledWidth(int height, Bitmap bm){
return (int)(((double)bm.getWidth()/bm.getHeight())*height);
}
public static int getScaledHeight(int width, Bitmap bm){
return (int)(((double)bm.getHeight()/bm.getWidth())*width);
}
/**
* Get the proper sample size to meet minimization restraints
* @param dim
* @param min
* @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
* @return
*/
public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
switch(min.type){
case WIDTH:
return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
case HEIGHT:
return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
case BOTH:
int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
// Return the smaller of the two
if(widthMaxSampleSize<heightMaxSampleSize){
return widthMaxSampleSize;
}else{
return heightMaxSampleSize;
}
case MAX:
// Find the larger dimension and go bases on that
if(dim.width>dim.height){
return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
}else{
return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
}
}
return 1;
}
public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
int add=multipleOf2 ? 2 : 1;
int size=0;
while(min<(dim/(size+add))){
size+=add;
}
size = size==0 ? 1 : size;
return size;
}
public static class Dimensions {
int width;
int height;
public Dimensions(int width, int height) {
super();
this.width = width;
this.height = height;
}
@Override
public String toString() {
return width+" x "+height;
}
}
public static class Minimize {
public enum Type {
WIDTH,HEIGHT,BOTH,MAX
}
Integer minWidth;
Integer minHeight;
Integer minDim;
Type type;
public Minimize(int min, Type type) {
super();
this.type = type;
switch(type){
case WIDTH:
this.minWidth=min;
break;
case HEIGHT:
this.minHeight=min;
break;
case BOTH:
this.minWidth=min;
this.minHeight=min;
break;
case MAX:
this.minDim=min;
break;
}
}
public Minimize(int minWidth, int minHeight) {
super();
this.type=Type.BOTH;
this.minWidth = minWidth;
this.minHeight = minHeight;
}
}
/**
* Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
* @param width
* @param height
* @param config
* @return
*/
public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
long pixels=width*height;
switch(config){
case ALPHA_8: // 1 byte per pixel
return pixels;
case ARGB_4444: // 2 bytes per pixel, but depreciated
return pixels*2;
case ARGB_8888: // 4 bytes per pixel
return pixels*4;
case RGB_565: // 2 bytes per pixel
return pixels*2;
default:
return pixels;
}
}
private static BitmapFactory.Options getOptionsForDimensions(){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
return options;
}
private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inDither = false;
options.inSampleSize = sampleSize;
options.inScaled = false;
options.inPreferredConfig = bitmapConfig;
return options;
}
}
#21
14
In one of my application i need to take picture either from Camera/Gallery
. If user click image from Camera(may be 2MP, 5MP or 8MP), image size varies from kB
s to MB
s. If image size is less(or up to 1-2MB) above code working fine but if i have image of size above 4MB or 5MB then OOM
comes in frame :(
在我的一个应用程序中,我需要从相机/画廊中拍照。如果用户从相机上点击图像(可能是2MP, 5MP或8MP),图像的大小会因kBs到MBs而有所不同。如果图像大小小于(或高达1-2MB)以上代码运行良好,但如果我的图像大小超过4MB或5MB,那么OOM就会出现在框架中:(
then i have worked to solve this issue & finally i've made the below improvement to Fedor's(All Credit to Fedor for making such a nice solution) code :)
然后我努力解决这个问题&最后我已经把下面的改进变成了Fedor的(所有的都是Fedor的功劳)代码:)
private Bitmap decodeFile(String fPath) {
// Decode image size
BitmapFactory.Options opts = new BitmapFactory.Options();
/*
* If set to true, the decoder will return null (no bitmap), but the
* out... fields will still be set, allowing the caller to query the
* bitmap without having to allocate the memory for its pixels.
*/
opts.inJustDecodeBounds = true;
opts.inDither = false; // Disable Dithering mode
opts.inPurgeable = true; // Tell to gc that whether it needs free
// memory, the Bitmap can be cleared
opts.inInputShareable = true; // Which kind of reference will be used to
// recover the Bitmap data after being
// clear, when it will be used in the
// future
BitmapFactory.decodeFile(fPath, opts);
// The new size we want to scale to
final int REQUIRED_SIZE = 70;
// Find the correct scale value.
int scale = 1;
if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) opts.outHeight
/ (float) REQUIRED_SIZE);
final int widthRatio = Math.round((float) opts.outWidth
/ (float) REQUIRED_SIZE);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
}
// Decode bitmap with inSampleSize set
opts.inJustDecodeBounds = false;
opts.inSampleSize = scale;
Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
Bitmap.Config.RGB_565, false);
return bm;
}
I hope this will help the buddies facing the same problem!
我希望这能帮助朋友们面对同样的问题!
for more please refer this
更多信息请参考。
#22
13
I just ran into this issue a couple minutes ago. I solved it by doing a better job at managing my listview adapter. I thought it was an issue with the hundreds of 50x50px images I was using, turns out I was trying to inflate my custom view each time the row was being shown. Simply by testing to see if the row had been inflated I eliminated this error, and I am using hundreds of bitmaps. This is actually for a Spinner, but the base adapter works all the same for a ListView. This simple fix also greatly improved the performance of the adapter.
我几分钟前刚刚碰到这个问题。通过更好地管理listview适配器,我解决了这个问题。我认为这是我使用的数百个50x50px图像的问题,结果显示,每次显示这一行时,我都在试图膨胀自定义视图。简单地通过测试看看行是否被夸大了,我消除了这个错误,我使用了数百个位图。这实际上是针对一个Spinner,但是基本适配器对ListView的工作是相同的。这个简单的修复也大大提高了适配器的性能。
@Override
public View getView(final int position, View convertView, final ViewGroup parent) {
if(convertView == null){
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.spinner_row, null);
}
...
#23
12
I've spent the entire day testing these solutions and the only thing that worked for me is the above approaches for getting the image and manually calling the GC, which I know is not supposed to be necessary, but it is the only thing that worked when I put my app under heavy load testing switching between activities. My app has a list of thumbnail images in a listview in (lets say activity A) and when you click on one of those images it takes you to another activity (lets say activity B) that shows a main image for that item. When I would switch back and forth between the two activities, I would eventually get the OOM error and the app would force close.
我花了一整天测试这些解决方案和唯一为我工作上面的方法获取图像并手动调用GC,我知道这不是应该是必需的,但它是唯一工作当我把我的应用程序之间切换负载较重的情况下,测试活动。我的应用程序在listview中有一个缩略图列表(比如activity a),当你点击其中一个图像时,它会带你去另一个活动(比如活动B),它显示了该项目的主要图像。当我在这两种活动之间来回切换时,我最终会得到OOM错误,这款应用程序将会关闭。
When I would get half way down the listview it would crash.
当我走到一半的时候,它会崩溃。
Now when I implement the following in activity B, I can go through the entire listview with no issue and keep going and going and going...and its plenty fast.
现在,当我在活动B中实现以下内容时,我可以遍历整个listview,然后继续前进,继续前进。和它的足够快。
@Override
public void onDestroy()
{
Cleanup();
super.onDestroy();
}
private void Cleanup()
{
bitmap.recycle();
System.gc();
Runtime.getRuntime().gc();
}
#24
12
This issue only happens in Android emulators. I also faced this issue in an emulator but when I checked in a device then it worked fine.
这个问题只发生在Android模拟器中。我在模拟器中也遇到了这个问题,但是当我在一个设备中检查时,它运行得很好。
So please check in a device. It may be run in device.
所以请检查一下设备。它可以在设备中运行。
#25
11
My 2 cents: i solved my OOM errors with bitmaps by:
我的2分:我用位图解决了我的OOM错误:
a) scaling my images by a factor of 2
a)缩放图像的倍数为2。
b) using Picasso library in my custom Adapter for a ListView, with a one-call in getView like this: Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
b)在我的自定义适配器中使用毕加索库作为ListView,在getView中有一个这样的调用:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
#26
10
This code will help to load large bitmap from drawable
此代码将有助于从drawable加载大位图。
public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {
Context context;
public BitmapUtilsTask(Context context) {
this.context = context;
}
/**
* Loads a bitmap from the specified url.
*
* @param url The location of the bitmap asset
* @return The bitmap, or null if it could not be loaded
* @throws IOException
* @throws MalformedURLException
*/
public Bitmap getBitmap() throws MalformedURLException, IOException {
// Get the source image's dimensions
int desiredWidth = 1000;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);
int srcWidth = options.outWidth;
int srcHeight = options.outHeight;
// Only scale if the source is big enough. This code is just trying
// to fit a image into a certain width.
if (desiredWidth > srcWidth)
desiredWidth = srcWidth;
// Calculate the correct inSampleSize/scale value. This helps reduce
// memory use. It should be a power of 2
int inSampleSize = 1;
while (srcWidth / 2 > desiredWidth) {
srcWidth /= 2;
srcHeight /= 2;
inSampleSize *= 2;
}
// Decode with inSampleSize
options.inJustDecodeBounds = false;
options.inDither = false;
options.inSampleSize = inSampleSize;
options.inScaled = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inPurgeable = true;
Bitmap sampledSrcBitmap;
sampledSrcBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);
return sampledSrcBitmap;
}
/**
* The system calls this to perform work in a worker thread and delivers
* it the parameters given to AsyncTask.execute()
*/
@Override
protected Bitmap doInBackground(Object... item) {
try
{
return getBitmap();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
}
#27
10
use these code for every image in select from SdCard or drewable to convert bitmap object.
从SdCard中选择每一个图像,或者drewable来转换位图对象。
Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
System.gc();
}
bitmap = Bitmap.createScaledBitmap(BitmapFactory
.decodeFile(ImageData_Path.get(img_pos).getPath()),
width, height, true);
} catch (OutOfMemoryError e) {
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
System.gc();
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.RGB_565;
options.inSampleSize = 1;
options.inPurgeable = true;
bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
.getPath().toString(), options), width, height,true);
}
return bitmap;
use your image path instend of ImageData_Path.get(img_pos).getPath() .
使用ImageData_Path.get(img_pos). getpath()的图像路径。
#28
9
All the solutions here require setting a IMAGE_MAX_SIZE. This limits devices with more powerful hardware and if the image size is too low it looks ugly on the HD screen.
这里所有的解决方案都需要设置一个IMAGE_MAX_SIZE。这限制了硬件更强大的设备,如果图像尺寸过低,在高清屏幕上看起来很难看。
I came out with a solution that works with my Samsung Galaxy S3 and several other devices including less powerful ones, with better image quality when a more powerful device is used.
我推出了一个解决方案,它与我的三星Galaxy S3和其他一些设备一起工作,包括功能不那么强大的设备,当使用更强大的设备时,它的图像质量更好。
The gist of it is to calculate the maximum memory allocated for the app on a particular device, then set the scale to be lowest possible without exceeding this memory. Here's the code:
它的要点是计算在特定设备上为应用程序分配的最大内存,然后在不超过此内存的情况下将其设置为最低。这是代码:
public static Bitmap decodeFile(File f)
{
Bitmap b = null;
try
{
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
try
{
BitmapFactory.decodeStream(fis, null, o);
}
finally
{
fis.close();
}
// In Samsung Galaxy S3, typically max memory is 64mb
// Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
// If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
// We try use 25% memory which equals to 16mb maximum for one bitmap
long maxMemory = Runtime.getRuntime().maxMemory();
int maxMemoryForImage = (int) (maxMemory / 100 * 25);
// Refer to
// http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
// A full screen GridView filled with images on a device with
// 800x480 resolution would use around 1.5MB (800*480*4 bytes)
// When bitmap option's inSampleSize doubled, pixel height and
// weight both reduce in half
int scale = 1;
while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
scale *= 2;
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
try
{
b = BitmapFactory.decodeStream(fis, null, o2);
}
finally
{
fis.close();
}
}
catch (IOException e)
{
}
return b;
}
I set the maximum memory used by this bitmap to be 25% of maximum allocated memory, you may need to adjust this to your needs and make sure this bitmap is cleaned up and don't stay in memory when you've finished using it. Typically I use this code to perform image rotation (source and destination bitmap) so my app needs to load 2 bitmaps in memory at the same time, and 25% gives me a good buffer without running out of memory when performing image rotation.
我将这个位图所使用的最大内存设置为最大分配内存的25%,您可能需要将其调整到您的需要,并确保该位图已被清理,并且在您使用完它之后,不要停留在内存中。通常,我使用这个代码来执行图像旋转(源和目标位图),因此我的应用程序需要同时加载两个位图,而25%给了我一个很好的缓冲,在执行图像旋转时不会耗尽内存。
Hope this helps someone out there..
希望这能帮助到外面的人。
#29
9
Such OutofMemoryException
cannot be totally resolved by calling the System.gc()
and so on .
这样的OutofMemoryException不能通过调用System.gc()等方式完全解决。
By referring to the
Activity Life Cycle
指的是活动生命周期。
The Activity States are determined by the OS itself subject to the memory usage for each process and the priority of each process.
活动状态由操作系统本身决定,取决于每个进程的内存使用情况和每个进程的优先级。
You may consider the size and the resolution for each of the bitmap pictures used. I recommend to reduce the size ,resample to lower resolution , refer to the design of galleries (one small picture PNG , and one original picture.)
您可以考虑使用的每个位图图片的大小和分辨率。我建议减小尺寸,重新采样,以降低分辨率,参考图库的设计(一张小图片PNG,和一张原始图片)。
#30
8
Generally android device heap size is only 16MB (varies from device/OS see post Heap Sizes), if you are loading the images and it crosses the size of 16MB , it will throw out of memory exception, instead of using the Bitmap for , loading images from SD card or from resources or even from network try to using getImageUri , loading bitmap require more memory , or you can set bitmap to null if your work done with that bitmap.
一般android设备堆大小只有16 mb(不同设备/ OS见帖子堆大小),如果你是加载图片和它穿过16 mb的大小,它将抛出内存不足异常,而不是使用的位图加载图像从SD卡或从网络资源甚至试图使用getImageUri,加载位图需要更多的内存,或者你可以设置位图位图的零如果你的工作完成了。