我有一个列表视图,每行有几个图像按钮.单击列表行时,将启动新活动.由于相机布局有问题,我不得不建立自己的标签.为结果启动的活动是地图.如果我单击我的按钮启动图像预览(从SD卡加载图像),应用程序将从活动返回到活动返回到listview
结果处理程序以重新启动我的新活动,这只是一个图像小部件.
列表视图上的图像预览正在使用光标和ListAdapter
.这使得它非常简单,但我不确定如何放置一个经过调整大小的图像(即小的像素大小不像动态src
图像按钮那样.所以我只是调整了从手机摄像头下来的图像.
问题是当我试图返回并重新启动第二个活动时,我收到内存不足错误.
有没有办法我可以轻松地逐行构建列表适配器,我可以在运行中调整大小(有点明智)?
这是更好的,因为我还需要对每行中的小部件/元素的属性进行一些更改,因为焦点问题我无法选择带触摸屏的行.(我可以用滚球.)
我知道我可以做一个带外调整大小并保存我的图像,但这不是我想要做的,但是一些示例代码会很好.
一旦我在列表视图上禁用了图像,它再次正常工作.
仅供参考:这就是我的做法:
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);
哪里R.id.imagefilename
是ButtonImage
.
这是我的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
显示图像时出现新错误:
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
Fedor.. 886
要修复OutOfMemory错误,您应该执行以下操作:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 8; Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);
此inSampleSize
选项可减少内存消耗.
这是一个完整的方法.首先,它读取图像大小而不解码内容本身.然后它找到最佳inSampleSize
值,它应该是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; }
我面临和Chrispix一样的问题,但我不认为这里的解决方案能够真正解决问题,而是回避它.更改样本大小会减少使用的内存量(以图像质量为代价,这对于图像预览来说可能没什么用),但如果解码的图像流足够大,如果有多个图像流则不会阻止异常解码.如果我找到一个更好的解决方案(并且可能没有),我会在这里发布一个答案. (68认同)
请注意,10可能不是inSampleSize的最佳值,文档建议使用2的幂. (31认同)
这个解决方案帮助了我,但图像质量很糟糕.我正在使用viewfilpper来显示图像的任何建议? (8认同)
您只需要一个合适的尺寸来匹配屏幕的像素密度,以便放大,这样您就可以以更高的密度拍摄图像样本. (4认同)
REQUIRED_SIZE是您要缩放到的新大小. (4认同)
@Dopyiii FYI*= 2相当于>> 1,而不是>> 2 (2认同)
AdamK.. 628
在Android的训练课," 显示位图高效 ",提供了一些伟大的信息,理解和处理异常java.lang.OutOfMemoryError: bitmap size exceeds VM budget
装载位图时.
的BitmapFactory
类提供了几种解码方法(decodeByteArray()
,decodeFile()
,decodeResource()
,等等),用于创建Bitmap
来自各种来源.根据图像数据源选择最合适的解码方法.这些方法尝试为构造的位图分配内存,因此很容易导致OutOfMemory
异常.每种类型的解码方法都有其他签名,可让您通过BitmapFactory.Options
类指定解码选项.在解码时将inJustDecodeBounds
属性设置为true
避免内存分配,返回null
位图对象但设置outWidth
,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;
为避免java.lang.OutOfMemory
异常,请在解码之前检查位图的尺寸,除非您完全信任该源为您提供可预测大小的图像数据,这些图像数据可以轻松地放入可用内存中.
既然图像尺寸已知,它们可用于决定是否应将完整图像加载到内存中,或者是否应加载子采样版本.以下是需要考虑的一些因素:
估计在内存中加载完整映像的内存使用情况.
在给定应用程序的任何其他内存要求的情况下,您愿意承诺加载此映像的内存量.
要加载图像的目标ImageView或UI组件的尺寸.
屏幕尺寸和当前设备的密度.
例如,如果将最终显示在128x96像素的缩略图中,则不值得将1024x768像素图像加载到内存中ImageView
.
告诉解码器子样本图像,加载一个较小的版本到内存中,设置inSampleSize
到true
你的BitmapFactory.Options
对象.例如,分辨率为2048x1536且用inSampleSize
4 解码的图像产生大约512x384的位图.将其加载到内存中对于完整图像使用0.75MB而不是12MB(假设位图配置为ARGB_8888
).这是一种根据目标宽度和高度计算样本大小值的方法,该值为2的幂:
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; }
注意:计算两个幂的幂是因为解码器使用最终值,通过向下舍入到最接近的2的幂,根据
inSampleSize
文档.
要使用此方法,首先使用inJustDecodeBounds
set to 解码true
,传递选项,然后使用新inSampleSize
值再次解码并inJustDecodeBounds
设置为false
:
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); }
此方法可以轻松地将任意大尺寸的位图加载到ImageView
显示100x100像素缩略图的位图中,如以下示例代码所示:
mImageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
您可以按照类似的过程解码来自其他来源的位图,BitmapFactory.decode*
方法是根据需要替换相应的方法.
要修复OutOfMemory错误,您应该执行以下操作:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 8; Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);
此inSampleSize
选项可减少内存消耗.
这是一个完整的方法.首先,它读取图像大小而不解码内容本身.然后它找到最佳inSampleSize
值,它应该是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; }
在Android的训练课," 显示位图高效 ",提供了一些伟大的信息,理解和处理异常java.lang.OutOfMemoryError: bitmap size exceeds VM budget
装载位图时.
的BitmapFactory
类提供了几种解码方法(decodeByteArray()
,decodeFile()
,decodeResource()
,等等),用于创建Bitmap
来自各种来源.根据图像数据源选择最合适的解码方法.这些方法尝试为构造的位图分配内存,因此很容易导致OutOfMemory
异常.每种类型的解码方法都有其他签名,可让您通过BitmapFactory.Options
类指定解码选项.在解码时将inJustDecodeBounds
属性设置为true
避免内存分配,返回null
位图对象但设置outWidth
,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;
为避免java.lang.OutOfMemory
异常,请在解码之前检查位图的尺寸,除非您完全信任该源为您提供可预测大小的图像数据,这些图像数据可以轻松地放入可用内存中.
既然图像尺寸已知,它们可用于决定是否应将完整图像加载到内存中,或者是否应加载子采样版本.以下是需要考虑的一些因素:
估计在内存中加载完整映像的内存使用情况.
在给定应用程序的任何其他内存要求的情况下,您愿意承诺加载此映像的内存量.
要加载图像的目标ImageView或UI组件的尺寸.
屏幕尺寸和当前设备的密度.
例如,如果将最终显示在128x96像素的缩略图中,则不值得将1024x768像素图像加载到内存中ImageView
.
告诉解码器子样本图像,加载一个较小的版本到内存中,设置inSampleSize
到true
你的BitmapFactory.Options
对象.例如,分辨率为2048x1536且用inSampleSize
4 解码的图像产生大约512x384的位图.将其加载到内存中对于完整图像使用0.75MB而不是12MB(假设位图配置为ARGB_8888
).这是一种根据目标宽度和高度计算样本大小值的方法,该值为2的幂:
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; }
注意:计算两个幂的幂是因为解码器使用最终值,通过向下舍入到最接近的2的幂,根据
inSampleSize
文档.
要使用此方法,首先使用inJustDecodeBounds
set to 解码true
,传递选项,然后使用新inSampleSize
值再次解码并inJustDecodeBounds
设置为false
:
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); }
此方法可以轻松地将任意大尺寸的位图加载到ImageView
显示100x100像素缩略图的位图中,如以下示例代码所示:
mImageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
您可以按照类似的过程解码来自其他来源的位图,BitmapFactory.decode*
方法是根据需要替换相应的方法.
我对Fedor的代码做了一点改进.它基本上是一样的,但没有(在我看来)丑陋的while循环,它总是导致2的幂.感谢Fedor制作原始解决方案,我被困住直到找到了他,然后我才能做到这一点:)
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; }
我来自iOS经验,我很沮丧地发现了一个像加载和显示图像这样基本的问题.毕竟,遇到此问题的每个人都试图显示合理大小的图像.无论如何,这里有两个修改我的问题的变化(并使我的应用程序非常敏感).
1)你做的每一次BitmapFactory.decodeXYZ()
,请务必在一传BitmapFactory.Options
有inPurgeable
集于true
(并优选inInputShareable
也设置为true
).
2)永远不要使用Bitmap.createBitmap(width, height, Config.ARGB_8888)
.我的意思是从来没有!我几乎没有通过这件事就没有引起内存错误.再多recycle()
,System.gc()
,任何帮助.它总是引发异常.实际工作的另一种方法是在drawables中使用虚拟图像(或者使用上面的步骤1解码的另一个Bitmap),将其重新缩放到您想要的任何位置,然后操纵生成的Bitmap(例如将其传递给Canvas)为了更多的乐趣).那么,你应该使用的是:Bitmap.createScaledBitmap(srcBitmap, width, height, false)
.如果由于某种原因你必须使用暴力创造方法,那么至少通过Config.ARGB_4444
.
这几乎可以保证,如果不是几天,你可以节省数小时.所有关于缩放图像等的讨论都没有真正起作用(除非您考虑将错误的大小或降级的图像作为解决方案).
这是一个已知的错误,它不是因为大文件.由于Android缓存了Drawables,因此在使用少量图像后内存不足.但是我通过跳过android默认缓存系统找到了另一种方法.
解决方案:将图像移动到"assets"文件夹并使用以下函数获取BitmapDrawable:
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); }
我有同样的问题,通过避免使用BitmapFactory.decodeStream或decodeFile函数来解决它 BitmapFactory.decodeFileDescriptor
decodeFileDescriptor
看起来它比decodeStream/decodeFile调用不同的本机方法.
无论如何,这是有效的(请注意,我添加了一些选项,如上所述,但这不是产生差异的原因.重要的是调用BitmapFactory.decodeFileDescriptor而不是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; }
我认为decodeStream/decodeFile中使用的本机函数存在问题.我已经确认在使用decodeFileDescriptor时会调用不同的本机方法.我还读到的是"图像(位图)不是以标准Java方式分配,而是通过本机调用分配;分配是在虚拟堆之外完成的,但是要对它进行 计数! "
我认为避免这种OutOfMemoryError
情况的最佳方法是面对它并理解它.
我制作了一个有意识地引起并监控内存使用情况的应用程序OutOfMemoryError
.
在我使用此应用程序完成了大量实验后,我得出以下结论:
我将在Honey Comb之前讨论SDK版本.
位图存储在本机堆中,但它会自动收集垃圾,调用recycle()是不必要的.
如果{VM堆大小} + {已分配的本机堆内存}> = {设备的VM堆大小限制},并且您尝试创建位图,则将抛出OOM.
注意:计算VM HEAP SIZE而不是VM ALLOCATED MEMORY.
即使分配的VM内存缩小,VM堆大小也不会在增长后缩小.
因此,您必须尽可能降低峰值VM内存,以防止VM堆大小过大而无法为Bitmaps节省可用内存.
手动调用System.gc()是没有意义的,系统会在尝试增加堆大小之前先调用它.
原生堆大小永远不会缩小,但它不计入OOM,因此无需担心它.
然后,我们来谈谈从Honey Comb开始的SDK.
位图存储在VM堆中,本机内存不计入OOM.
OOM的条件要简单得多:{VM heap size}> = {设备的VM堆大小限制}.
因此,您有更多可用内存来创建具有相同堆大小限制的位图,因此不太可能抛出OOM.
以下是我对垃圾收集和内存泄漏的一些观察.
您可以在App中自己查看.如果一个Activity执行了一个在销毁Activity之后仍在运行的AsyncTask,那么在AsyncTask完成之前,Activity不会被垃圾收集.
这是因为AsyncTask是匿名内部类的一个实例,它拥有Activity的引用.
如果在后台线程中的IO操作中阻止任务,则调用AsyncTask.cancel(true)将不会停止执行.
回调也是匿名内部类,因此如果项目中的静态实例保留它们并且不释放它们,则内存将被泄露.
如果您安排了重复或延迟的任务,例如Timer,并且您没有在onPause()中调用cancel()和purge(),则内存将被泄露.
我最近看到了很多关于OOM异常和缓存的问题.开发人员指南有一篇非常好的文章,但有些人往往以合适的方式实现它.
因此,我编写了一个示例应用程序,演示了Android环境中的缓存.这个实现还没有得到OOM.
查看此答案的结尾以获取源代码的链接.
Android API 2.1或更高版本(我根本无法为API 1.6中的应用程序获取可用内存 - 这是在API 1.6中不起作用的唯一代码段)
Android支持包
如果使用单例进行方向更改,则保留缓存
使用八分之一指定的应用程序内存来缓存(如果你想修改)
大位图得到缩放(您可以定义要允许的最大像素)
在下载位图之前控制可用的Internet连接
确保您每行只实例化一个任务
如果你丢的ListView
了,它根本不会下载的位图
磁盘缓存.无论如何,这应该很容易实现 - 只需指向从磁盘抓取位图的不同任务
正在下载的图像是来自Flickr的图像(75x75).但是,放置您想要处理的任何图像URL,如果超过最大值,应用程序将缩小它.在这个应用程序中,网址只是一个String
数组.
该LruCache
有一个很好的方式来处理位图.但是,在这个应用程序中,我将一个LruCache
内部实例放在我创建的另一个缓存类中,以使应用程序更加可行.
Cache.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(size) { protected int sizeOf(String key, Bitmap b) { // Assuming that one pixel contains four bytes. return b.getHeight() * b.getWidth() * 4; } }; mCurrentTasks = new ArrayList (); } /** * 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(); } } }
除非要实现磁盘缓存,否则您不需要编辑Cache.java文件中的任何内容.
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()
经常被召唤.如果我们没有实现检查以确保我们不会每行启动无限量的线程,那么在那里下载图像通常不是一个好主意.Cache.java检查是否rowObject.mBitmapUrl
已经在任务中,如果是,则不会启动另一个任务.因此,我们很可能不会超出AsyncTask
池中的工作队列限制.
您可以从https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip下载源代码.
我已经测试了几个星期了,我还没有得到一个OOM例外.我已经在模拟器,Nexus One和Nexus S上对此进行了测试.我测试的图像网址包含高清质量的图像.唯一的瓶颈是下载需要更多时间.
只有一种可能的情况,我可以想象OOM会出现,也就是说,如果我们下载许多非常大的图像,并且在它们被缩放并放入缓存之前,将会同时占用更多内存并导致OOM.但这无论如何都不是理想的情况,很可能无法以更可行的方式解决.
在评论中报告错误!:-)
我做了以下操作来拍摄图像并在运行中调整大小.希望这可以帮助
Bitmap bm; bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true); mPicture = new ImageView(context); mPicture.setImageBitmap(bm);
这似乎是一个非常长期存在的问题,有很多不同的解释.我在这里接受了两个最常见的答案的建议,但这些都没有解决我的VM问题,声称它无法承担字节来执行过程的解码部分.经过一番挖掘后,我了解到这里的真正问题是解除了NATIVE堆的过程.
看到这里:BitmapFactory OOM让我疯狂
这引导我进入另一个讨论主题,在那里我找到了更多解决这个问题的方法.一种是System.gc();
在显示图像后手动调用.但这实际上使您的应用程序使用更多内存,以减少本机堆.2.0(Donut)发布时更好的解决方案是使用BitmapFactory选项"inPurgeable".所以,我只是增加o2.inPurgeable=true;
刚过o2.inSampleSize=scale;
.
有关该主题的更多信息:内存堆的限制是否只有6M?
现在,说完所有这些之后,我对Java和Android也是一个完整的过程.因此,如果您认为这是解决此问题的可怕方法,那么您可能是对的.;-)但这对我有用,我发现现在无法从堆缓存中运行VM.我能找到的唯一缺点就是你在捣毁你的缓存绘制图像.这意味着如果你回到那个图像,你每次都会重新绘制它.在我的应用程序如何工作的情况下,这不是一个真正的问题.你的旅费可能会改变.
不幸的是,如果以上都不起作用,那么将其添加到您的清单文件中.内部应用标签
12> 小智..:使用
bitmap.recycle();
此功能可以避免任何图像质量问题.
根据API,不需要调用recycle().
13> 小智..:我有一个更有效的解决方案,不需要任何类型的扩展.只需解码您的位图一次,然后将其缓存在地图中,与其名称相对应.然后只需根据名称检索位图并在ImageView中设置它.没有什么需要做的.
这将起作用,因为解码位图的实际二进制数据不存储在dalvik VM堆中.它存储在外部.因此,每次解码位图时,它都会在VM堆外部分配内存,而这些内存永远不会被GC回收
为了帮助您更好地理解这一点,想象一下您将图像保留在可绘制文件夹中.您只需通过执行getResources().getDrwable(R.drawable.)来获取图像.这不会每次解码您的图像,但每次调用时都会重新使用已解码的实例.所以从本质上说它是缓存的.
现在,因为你的形象是在一个文件中的某个地方(或者甚至可以从外部服务器来),这是你的责任缓存解码位图实例可以重复使用任何需要的地方.
希望这可以帮助.
"然后根据其名称将其缓存在地图中." 你如何缓存你的图像?
你真的试过这个吗?即使像素数据实际上并未存储在Dalvik堆中,也会将其在本机内存中的大小报告给VM并根据其可用内存进行计数.
@Vincent我认为将它们存储在Map中并不困难.我会建议像HashMapmap这样的东西,其中Key可以是源的字符串或任何对你有意义的东西.让我们假设您将路径作为KEY,将其存储为map.put(Path,Bitmap)并通过map.get(Path)接收它
你想要使用HashMap>如果你正在实现一个图像缓存,否则你可能会耗尽内存 - 我也不认为"它分配了VM堆之外的内存,它永远不会被GC回收"是的,内存被收回,因为我知道这可能是一个延迟,这就是bitmap.recycle()的用途,作为早期回收mem的提示......
14> 小智..:我已按以下方式解决了同样的问题.
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> Torid..:这里有两个问题....
位图内存不在VM堆中,而是在本机堆中 - 请参阅BitmapFactory OOM让我疯狂
本机堆的垃圾收集比VM堆更懒 - 所以每次通过Activity的onPause或onDestroy时,你需要非常积极地做bitmap.recycle和bitmap = null
16> Luke Taylor..:这对我有用!
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> Mike..:上面的答案都没有对我有用,但我确实想出了一个解决问题的可怕丑陋的解决方法.我在项目中添加了一个非常小的1x1像素图像作为资源,并在调用垃圾收集之前将其加载到我的ImageView中.我认为可能是ImageView没有发布Bitmap,所以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.
18> Pascal..:这里有很好的答案,但我想要一个完全可用的类来解决这个问题..所以我做了一个.
这是我的BitmapHelper类,它是OutOfMemoryError证明:-)
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; } }
19> 小智..:这适合我.
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);这是在C#monodroid上.您可以轻松更改图像的路径.这里重要的是要设置的选项.
20> Emil Davtyan..:这似乎是共享我的实用程序类以便与社区一起加载和处理图像的合适位置,欢迎您使用它并自由地修改它.
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://stackoverflow.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(widthMaxSampleSizedim.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> Rupesh Yadav..:在我的一个应用程序中,我需要拍照
Camera/Gallery
.如果用户单击来自Camera的图像(可能是2MP,5MP或8MP),图像大小会因kB
s 而异MB
.如果图像大小较小(或高达1-2MB)以上代码工作正常,但如果我有大小超过4MB或5MB的图像然后OOM
进入框架:(然后我一直在努力解决这个问题,最后我已经对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; }我希望这能帮助那些面临同样问题的伙伴们!
更多请参考这个
22> BajaBob..:我刚刚在几分钟前遇到过这个问题.我通过更好地管理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); } ...
我不能为此感谢你!在看到这个之前,我正在追逐错误的问题.但问题是:由于每个或我的列表行都有一个唯一的名称和照片,我必须使用convertView数组来保留每个行的值.我看不出使用单个变量如何允许你这样做.我错过了什么吗?
23> Jesse..:我花了整整一天来测试这些解决方案,唯一对我有用的是上面获取图像和手动调用GC的方法,我知道这不是必需的,但它是唯一有效的方法当我把我的应用程序置于重负载测试之间切换活动.我的应用程序在列表视图中有一个缩略图列表(比如活动A),当您点击其中一个图像时,它会带您进入另一个显示该项目主图像的活动(比如活动B).当我在两个活动之间来回切换时,我最终会得到OOM错误,应用程序会强行关闭.
当我在列表视图中间一半时,它会崩溃.
现在,当我在活动B中实现以下内容时,我可以浏览整个列表视图,没有任何问题,继续前进和继续......并且它的速度很快.
@Override public void onDestroy() { Cleanup(); super.onDestroy(); } private void Cleanup() { bitmap.recycle(); System.gc(); Runtime.getRuntime().gc(); }
24> Android Dev..:此问题仅发生在Android模拟器中.我也在模拟器中遇到过这个问题但是当我检查了一个设备然后它工作正常.
所以请检查设备.它可以在设备中运行.
25> Mahesh..:一般android设备的堆大小只有16MB(因设备/操作系统而不同,看看后大小),如果你加载的是图像并且它的大小超过16MB,它会抛出内存异常,而不是使用Bitmap,加载来自SD卡或资源甚至网络的图像尝试使用getImageUri,加载位图需要更多内存,或者如果您使用该位图完成工作,则可以将位图设置为null.
26> matsoftware..:我的2美分:我用位图解决了我的OOM错误:
a)将我的图像缩放2倍
b)在我的自定义适配器中使用Picasso库进行ListView,在getView中进行一次调用,如下所示:
Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
27> Gaurav Pansh..:将这些代码用于从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;使用ImageData_Path.get(img_pos).getPath()的图像路径.
28> Raju yourPep..:这样
OutofMemoryException
就不能通过调用等来完全解决System.gc()
.
通过参考活动生命周期活动状态由操作系统本身决定,取决于每个进程的内存使用情况和每个进程的优先级.
您可以考虑使用的每个位图图片的大小和分辨率.我建议缩小尺寸,重新采样以降低分辨率,参考画廊的设计(一张小图片PNG和一张原始图片.)
29> Bruce..:此处的所有解决方案都需要设置IMAGE_MAX_SIZE.这限制了具有更强大硬件的设备,如果图像尺寸太小,则在高清屏幕上看起来很难看.
我推出的解决方案适用于我的三星Galaxy S3和其他几种设备,包括功能较弱的设备,当使用功能更强大的设备时,可以获得更好的图像质量.
它的要点是计算在特定设备上为应用程序分配的最大内存,然后将该比例设置为尽可能低而不超过此内存.这是代码:
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; }我将此位图使用的最大内存设置为最大分配内存的25%,您可能需要根据需要进行调整,并确保清理此位图,并在使用完后不留在内存中.通常我使用此代码执行图像旋转(源和目标位图),因此我的应用程序需要同时在内存中加载2个位图,25%给我一个良好的缓冲区,而不会在执行图像旋转时耗尽内存.
希望这有助于那里的人..
30> vineet..:此代码将有助于从drawable加载大位图
public class BitmapUtilsTask extends AsyncTask
31> 小智..:BitmapFactory.Options options = new Options(); options.inSampleSize = 32; //img = BitmapFactory.decodeFile(imageids[position], options); Bitmap theImage = BitmapFactory.decodeStream(imageStream,null, options); Bitmap img=theImage.copy(Bitmap.Config.RGB_565,true); theImage.recycle(); theImage = null; System.gc(); //ivlogdp.setImageBitmap(img); Runtime.getRuntime().gc();
32> 小智..:您使用的图像看起来非常大.因为堆内存已满,会发生一些旧设备崩溃.在较旧的设备(蜂窝梳或ICS或任何低端型号设备)中尝试
android:largeHeap="true"
在应用程序标签下的清单文件中使用或使用下面的代码减小位图的大小.Bitmap bMap; BitmapFactory.Options options = new BitmapFactory.Options(); options.InSampleSize = 8; bMap= BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);你也可以给4或12或16来减少位图大小