提问人: 提问时间:1/25/2009 最后编辑:24 revs, 17 users 20%Chrispix 更新时间:5/20/2021 访问量:650236
将图像加载到 Bitmap 对象时出现奇怪的 OutOfMemory 问题
Strange OutOfMemory issue while loading an image to a Bitmap object
问:
我每行都有几个图像按钮。当用户单击列表行时,它将启动一个新活动。由于相机布局的问题,我不得不构建自己的选项卡。为结果启动的活动是地图。如果我单击我的按钮启动图像预览(从 SD 卡加载图像),应用程序会从活动返回到活动,以重新启动我的新活动,它只不过是一个图像小部件。ListView
ListView
上的图像预览是使用光标和 完成的。这使得它非常简单,但我不确定如何放置调整大小的图像(即较小的位大小而不是像素作为图像按钮的动态。所以我只是调整了从手机摄像头上下来的图像的大小。ListView
ListAdapter
src
问题是当它尝试返回并重新启动第二个活动时,我得到了一个。OutOfMemoryError
- 有没有办法轻松地逐行构建列表适配器,我可以在其中动态调整大小(按位)?
这将是可取的,因为我还需要对每行中的小部件/元素的属性进行一些更改,因为由于焦点问题,我无法使用触摸屏选择一行。(能用滚珠。)
- 我知道我可以进行带外调整大小并保存我的图像,但这并不是我真正想做的,但一些示例代码会很好。
一旦我禁用了图像,它又可以正常工作了。ListView
仅供参考:我是这样做的:
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
显示图像时,我还遇到了一个新错误:
22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri:
22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
答:
我执行了以下操作来获取图像并即时调整其大小。希望这会有所帮助
Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);
评论
要修复 OutOfMemory 错误,您应该执行如下操作:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);
此选项可减少内存消耗。inSampleSize
这是一个完整的方法。首先,它读取图像大小而不解码内容本身。然后它找到最佳值,应该是 2 的幂,最后对图像进行解码。inSampleSize
// 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;
}
评论
我对 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;
}
评论
BitmapFactory.decodeStream()
finally
使用这个 这有助于没有任何图像质量问题。bitmap.recycle();
评论
我有一个更有效的解决方案,不需要任何形式的扩展。只需对位图进行一次解码,然后根据其名称将其缓存在映射中即可。然后,只需根据名称检索位图,并在 ImageView 中设置它。没有什么需要做的了。
这将起作用,因为解码位图的实际二进制数据不存储在 dalvik VM 堆中。它存储在外部。因此,每次解码位图时,它都会在 VM 堆之外分配内存,GC 永远不会回收内存
为了帮助您更好地理解这一点,假设您已将图像保存在可绘制文件夹中。你只需通过执行 getResources().getDrwable(R.drawable.) 来获取图像。这不会每次都对图像进行解码,而是在每次调用时都重复使用已解码的实例。所以从本质上讲,它是缓存的。
现在,由于您的图像位于某个位置的文件中(甚至可能来自外部服务器),因此您有责任缓存解码的位图实例,以便在任何需要的地方重复使用。
希望这会有所帮助。
评论
这里有两个问题......
- 位图内存不在 VM 堆中,而是在本机堆中 - 请参阅 BitmapFactory OOM 让我发疯
- 本机堆的垃圾回收比 VM 堆更懒惰 - 因此每次执行 Activity 的 onPause 或 onDestroy 时,都需要非常积极地执行 bitmap.recycle 和 bitmap =null
这似乎是一个长期存在的问题,有很多不同的解释。我采纳了这里最常见的两个答案的建议,但这些都没有解决我的问题,即 VM 声称它负担不起字节来执行该过程的解码部分。经过一番挖掘,我了解到这里真正的问题是解码过程从NATIVE堆中夺走了。
这让我进入了另一个讨论线程,在那里我找到了这个问题的更多解决方案。一种是在显示图像后手动调用。但这实际上会使你的应用使用更多的内存,以减少本机堆。自 2.0 (Donut) 发布以来,更好的解决方案是使用 BitmapFactory 选项“inPurgeable”。所以我只是在 .System.gc();
o2.inPurgeable=true;
o2.inSampleSize=scale;
有关该主题的更多信息,请参阅:内存堆的限制只有 6M 吗?
现在,说了这么多,我也完全迷上了 Java 和 Android。因此,如果您认为这是解决这个问题的可怕方法,那么您可能是对的。;-)但这为我创造了奇迹,我发现现在不可能从堆缓存中运行 VM。我能找到的唯一缺点是您正在破坏缓存的绘制图像。这意味着如果你回到那个图像,你每次都会重新绘制它。就我的应用程序如何工作而言,这并不是真正的问题。您的里程可能会有所不同。
评论
这是一个已知的错误,不是因为大文件。由于 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);
}
我已通过以下方式解决了相同的问题。
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);
我遇到了同样的问题,并通过避免使用 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 方式分配的,而是通过本机调用分配的;分配是在虚拟堆之外完成的,但会计入虚拟堆!"
评论
上面的答案都不适合我,但我确实想出了一个非常丑陋的解决方法来解决这个问题。我将一个非常小的 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.
我来自iOS经验,我感到沮丧的是,我发现加载和显示图像这样基本的东西存在问题。毕竟,每个遇到此问题的人都在尝试显示合理大小的图像。无论如何,这里有两个更改解决了我的问题(并使我的应用程序非常灵敏)。
1)每次这样做时,请确保传入一个 with set to(最好是 with also set to )。BitmapFactory.decodeXYZ()
BitmapFactory.Options
inPurgeable
true
inInputShareable
true
2) 切勿使用 .我的意思是永远不要!我从来没有遇到过这样的事情,在几次通过后没有引起内存错误。再多的 , , 也无济于事。它总是引发异常。另一种实际可行的方法是在可绘制对象(或使用上述步骤 1 解码的另一个位图)中有一个虚拟图像,将其重新缩放到所需的任何位置,然后操作生成的位图(例如将其传递给 Canvas 以获得更多乐趣)。因此,您应该改用的是:.如果出于某种原因必须使用暴力创建方法,则至少通过 .Bitmap.createBitmap(width, height, Config.ARGB_8888)
recycle()
System.gc()
Bitmap.createScaledBitmap(srcBitmap, width, height, false)
Config.ARGB_4444
这几乎可以保证为您节省数小时甚至数天。所有关于缩放图像等的讨论都不起作用(除非您认为获得错误的大小或降级的图像是一种解决方案)。
评论
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;
并解决了我在 android 4.0.0 上出现内存不足异常的问题。谢谢伙计!Bitmap.createScaledBitmap(srcBitmap, width, height, false);
BitmapFactory.Options.inPurgeable
BitmapFactory.Options.inInputShareable
几分钟前我刚刚遇到了这个问题。我通过更好地管理我的列表视图适配器来解决这个问题。我以为这是我使用的数百张 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);
}
...
评论
我花了一整天的时间测试这些解决方案,唯一对我有用的是上述获取图像和手动调用 GC 的方法,我知道这不应该是必要的,但当我将我的应用程序置于繁重的负载测试下时,它是唯一有效的活动切换。我的应用在列表视图中有一个缩略图列表(比如活动 A),当您单击其中一个图像时,它会将您带到另一个活动(比如活动 B),该活动显示该项目的主图像。当我在两个活动之间来回切换时,我最终会得到 OOM 错误,应用程序会强制关闭。
当我在列表视图的一半时,它会崩溃。
现在,当我在活动 B 中实现以下内容时,我可以毫无问题地浏览整个列表视图,并继续前进......而且速度很快。
@Override
public void onDestroy()
{
Cleanup();
super.onDestroy();
}
private void Cleanup()
{
bitmap.recycle();
System.gc();
Runtime.getRuntime().gc();
}
Android 培训课程“高效显示位图”提供了一些有用的信息,用于理解和处理异常“java.lang.OutOfMemoryError:加载位图时位图大小超出虚拟机预算”。
读取位图尺寸和类型
该类提供了几种解码方法(、、等),用于从各种来源创建。根据您的图像数据源选择最合适的解码方法。这些方法尝试为构造的位图分配内存,因此很容易导致异常。每种类型的解码方法都有额外的签名,允许您通过类指定解码选项。将属性设置为 while decoding 可避免内存分配,返回位图对象,但设置 和 。此技术允许您在构建(和内存分配)位图之前读取图像数据的尺寸和类型。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 组件的尺寸。
- 当前设备的屏幕尺寸和密度。
例如,如果 1024x768 像素的图像最终将以 128x96 像素的缩略图显示,则不值得将该图像加载到内存中。ImageView
要告诉解码器对图像进行子采样,请将较小的版本加载到内存中,在对象中设置为。例如,分辨率为 2048x1536 的图像使用 4 解码,将生成大约 512x384 的位图。将其加载到内存中时,完整图像使用 0.75MB 而不是 12MB(假设位图配置为 )。下面是一种根据目标宽度和高度计算样本数量值的方法,该值是 2 的幂:inSampleSize
true
BitmapFactory.Options
inSampleSize
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;
}
注意:计算 2 的幂值是因为解码器使用 根据文档,四舍五入到最接近的 2 的幂的最终值。
inSampleSize
若要使用此方法,请首先将 set 设置为 inSampleSizeinJustDecodeBoundsfalse 进行解码:inJustDecodeBounds
true, pass the options through and then decode again using the new
value and
set to
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);
}
使用此方法可以轻松地将任意大尺寸的位图加载到显示 100x100 像素缩略图的位图中,如以下示例代码所示:ImageView
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
您可以按照类似的过程对来自其他源的位图进行解码,方法是根据需要替换适当的方法。BitmapFactory.decode*
评论
这对我有用!
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());
}
}
这不能通过调用 等等 来完全解决。OutofMemoryException
System.gc()
通过参考活动生命周期
活动状态由操作系统本身确定,具体取决于每个进程的内存使用情况和每个进程的优先级。
您可以考虑所使用的每个位图图片的大小和分辨率。我建议减小尺寸,重新采样到较低的分辨率,参考图库的设计(一张小图片PNG,一张原始图片。
此问题仅发生在 Android 模拟器中。我在模拟器中也遇到了这个问题,但是当我签入设备时,它工作正常。
因此,请检查设备。它可能在设备中运行。
我最近看到了很多关于 OOM 异常和缓存的问题。开发人员指南中有一篇关于此的非常好的文章,但有些文章往往无法以合适的方式实现它。
因此,我编写了一个示例应用程序来演示 Android 环境中的缓存。此实现尚未获得 OOM。
查看此答案的末尾以获取指向源代码的链接。
要求:
- Android API 2.1 或更高版本(我根本无法在 API 1.6 中获取应用程序的可用内存 - 这是 API 1.6 中唯一不起作用的代码)
- Android 支持包
特征:
- 如果方向发生更改,则使用单例保留缓存
- 将分配的应用程序内存的八分之一用于缓存(如果需要,可以修改)
- 缩放大位图(可以定义要允许的最大像素)
- 控制在下载位图之前是否有可用的 Internet 连接
- 确保每行只实例化一个任务
- 如果你要扔掉它,它根本不会下载之间的位图
ListView
这不包括:
- 磁盘缓存。无论如何,这应该很容易实现 - 只需指向从磁盘中获取位图的不同任务
示例代码:
正在下载的图像是来自 Flickr 的图像 (75x75)。但是,输入您要处理的任何图像 URL,如果超过最大值,应用程序将缩小它。在此应用程序中,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<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();
}
}
}
除非要实现磁盘缓存,否则不需要编辑 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 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 上。 您可以轻松更改图像的路径。这里重要的是要设置的选项。
此代码将有助于从可绘制对象加载大型位图
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;
}
}
这似乎是与社区分享我用于加载和处理图像的实用程序类的合适地方,欢迎您使用它并自由修改它。
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(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;
}
}
通常android设备堆大小仅为16MB(因设备/操作系统而异,请参阅堆大小),如果您正在加载图像并且它的大小超过16MB,它将抛出内存不足异常,而不是使用位图,从SD卡或资源甚至网络加载图像尝试使用getImageUri, 加载位图需要更多内存,或者,如果使用该位图完成工作,则可以将位图设置为 null。
评论
我认为避免这种情况的最好方法是面对它并理解它。OutOfMemoryError
我制作了一个应用程序来故意导致和监控内存使用情况。OutOfMemoryError
在我用这个应用程序做了很多实验之后,我得出了以下结论:
我先谈谈 Honey Comb 之前的 SDK 版本。
位图存储在本机堆中,但它会自动收集垃圾,调用 recycle() 是不必要的。
如果 {VM 堆大小} + {分配的本机堆内存} >= {设备的 VM 堆大小限制},并且您正在尝试创建位图,则将引发 OOM。
注意:计算的是虚拟机堆大小,而不是虚拟机分配的内存。
虚拟机堆大小在增长后永远不会缩小,即使分配的虚拟机内存被压缩也是如此。
因此,您必须将峰值 VM 内存保持在尽可能低的水平,以防止 VM 堆大小增长得太大而无法为位图节省可用内存。
手动调用 System.gc() 是没有意义的,系统会先调用它,然后再尝试增加堆大小。
本机堆大小也永远不会缩小,但它不计入 OOM,因此无需担心。
那么,我们来谈谈SDK Starts from Honey Comb。
位图存储在 VM 堆中,本机内存不计入 OOM。
OOM 的条件要简单得多:{VM 堆大小} >= {设备的 VM 堆大小限制}。
因此,您有更多的可用内存来创建具有相同堆大小限制的位图,因此不太可能抛出 OOM。
以下是我对垃圾回收和内存泄漏的一些观察。
您可以在应用程序中自己看到它。如果某个 Activity 执行的 AsyncTask 在 Activity 被销毁后仍在运行,则在 AsyncTask 完成之前,该活动不会被垃圾回收。
这是因为 AsyncTask 是匿名内部类的实例,它包含 Activity 的引用。
如果任务在后台线程的 IO 操作中被阻止,则调用 AsyncTask.cancel(true) 不会停止执行。
回调也是匿名的内部类,因此如果项目中的静态实例持有它们并且不释放它们,则内存将泄漏。
如果您计划了一个重复或延迟的任务,例如一个 Timer,并且您没有在 onPause() 中调用 cancel() 和 purge(),则内存将泄漏。
我尝试了 Thomas Vervest 的方法,但当图像尺寸为 2592x1944 时,它返回 1 IMAGE_MAX_SIZE比例为 2048。
根据其他人提供的所有其他评论,这个版本对我有用:
private 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 ();
}
int scale = 1;
for (int size = Math.max (o.outHeight, o.outWidth);
(size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);
// 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;
}
这里的答案很好,但我想要一个完全可用的类来解决这个问题。所以我做了一个。
这是我的 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;
}
}
在我的一个应用程序中,我需要从 .如果用户从相机点击图像(可能是 2MP、5MP 或 8MP),图像大小从 s 到 s 不等。如果图像大小小于(或最大 1-2MB)高于代码工作正常,但如果图像大小大于 4MB 或 5MB,则会出现帧:(Camera/Gallery
kB
MB
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;
}
我希望这能帮助面临同样问题的伙伴们!
有关更多信息,请参阅此处
将位图设置为 imageview 后,按如下方式回收它:
bitmap.recycle();
bitmap=null;
我的 2 美分:我通过以下方式解决了带有位图的 OOM 错误:
a) 将我的图像缩放 2 倍
b) 在我的自定义 ListView 适配器中使用 Picasso 库,在 getView 中进行一次调用,如下所示:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
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();
使用这个概念这对您有所帮助,之后在图像视图上设置 imagebitmap
public static Bitmap convertBitmap(String path) {
Bitmap bitmap=null;
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) {
e.printStackTrace();
}
try {
if(fs!=null)
{
bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
}
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
如果您想从高度和宽度(如 60 和 60)的大图像制作小图像并快速滚动列表视图,请使用此概念
public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth,
int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(path, options);
return bmp;
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float) height / (float) reqHeight);
} else {
inSampleSize = Math.round((float) width / (float) reqWidth);
}
}
return inSampleSize;
}
我希望它能对你有所帮助。
您可以从此处的开发人员网站获得帮助
这里的所有解决方案都需要设置一个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% 为我提供了良好的缓冲区,而不会在执行图像旋转时耗尽内存。
希望这对那里的某人有所帮助。
嗨,请访问链接 http://developer.android.com/training/displaying-bitmaps/index.html
或者只是尝试使用给定函数检索位图
private Bitmap decodeBitmapFile (File f) {
Bitmap bitmap = 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 ();
}
int scale = 1;
for (int size = Math.max (o.outHeight, o.outWidth);
(size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);
// Decode with input-stram SampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options ();
o2.inSampleSize = scale;
fis = new FileInputStream (f);
try {
bitmap = BitmapFactory.decodeStream (fis, null, o2);
} finally {
fis.close ();
}
} catch (IOException e) {
}
return bitmap ;
}
要修复 OutOfMemory,您应该执行类似操作,请尝试此代码
public Bitmap loadBitmap(String URL, BitmapFactory.Options options) {
Bitmap bitmap = null;
InputStream in = null;
options.inSampleSize=4;
try {
in = OpenHttpConnection(URL);
Log.e("In====>", in+"");
bitmap = BitmapFactory.decodeStream(in, null, options);
Log.e("URL====>", bitmap+"");
in.close();
} catch (IOException e1) {
}
return bitmap;
}
和
try {
BitmapFactory.Options bmOptions;
bmOptions = new BitmapFactory.Options();
bmOptions.inSampleSize = 1;
if(studentImage != null){
galleryThumbnail= loadBitmap(IMAGE_URL+studentImage, bmOptions);
}
galleryThumbnail=getResizedBitmap(galleryThumbnail, imgEditStudentPhoto.getHeight(), imgEditStudentPhoto.getWidth());
Log.e("Image_Url==>",IMAGE_URL+studentImage+"");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
将这些代码用于 select from 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;
使用图像路径 instend of ImageData_Path.get(img_pos).getPath() 。
您使用的图像似乎非常大,size.so 某些较旧的设备由于堆内存已满而崩溃。在较旧的设备(蜂窝或 ICS 或任何低端型号设备)中,尝试在应用程序标记下的清单文件中使用,或使用以下代码减小位图的大小。android:largeHeap="true"
Bitmap bMap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.InSampleSize = 8;
bMap= BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);
您也可以给出 4 或 12 或 16 来减小位图大小
不幸的是,如果以上都不起作用,请将其添加到您的清单文件中。内部应用程序标记
<application
android:largeHeap="true"
评论
如果你像我一样懒惰,你可以开始使用毕加索库来加载图像。
Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);
Picasso.with(context).load(new File(...)).into(imageView3);
我使用了对我有用的解码文件描述符:
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
FileDescriptor fd = fileInputStream.getFD();
Bitmap imageBitmap = decodeSampledBitmapFromDescriptor(fd , 612,
816);
imageView.setImageBitmap(imageBitmap);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
从文件描述符解码采样位图的代码:
/**
* Decode and sample down a bitmap from a file input stream to the requested width and height.
*
* @param fileDescriptor The file descriptor to read from
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public static Bitmap decodeSampledBitmapFromDescriptor(
FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
}
/**
* Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding
* bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates
* the closest inSampleSize that will result in the final decoded bitmap having a width and
* height equal to or larger than the requested width and height. This implementation does not
* ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
* results in a larger bitmap which isn't as useful for caching purposes.
*
* @param options An options object with out* params already populated (run through a decode*
* method with inJustDecodeBounds==true
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @return The value to be used for inSampleSize
*/
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) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 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;
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger inSampleSize).
final float totalPixels = width * height;
// Anything more than 2x the requested pixels we'll sample down further
final float totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++;
}
}
return inSampleSize;
}
在浏览了所有答案后,我惊讶地发现没有人提到用于处理图像的 Glide API。很棒的库,并抽象出位图管理的所有复杂性。您可以使用此库和一行代码快速加载图像并调整图像大小。
Glide.with(this).load(yourImageResource).into(imageview);
您可以在此处获取存储库:https://github.com/bumptech/glide
评论
将以下行添加到清单.xml文件:
<application
android:hardwareAccelerated="false"
android:largeHeap="true">
<activity>
</activity>
</application>
评论
这将获得适当的位图并减少内存消耗
爪哇岛
Bitmap bm = null;
BitmapFactory.Options bmpOption = new BitmapFactory.Options();
bmpOption.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(file);
BitmapFactory.decodeStream(fis, null, bmpOption);
fis.close();
int scale = 1;
if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE) {
scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /
(double) Math.max(bmpOption.outHeight, bmpOption.outWidth)) / Math.log(0.5)));
}
BitmapFactory.Options bmpOption2 = new BitmapFactory.Options();
bmpOption2.inSampleSize = scale;
fis = new FileInputStream(file);
bm = BitmapFactory.decodeStream(fis, null, bmpOption2);
fis.close();
Kotlin
val bm:Bitmap = null
val bmpOption = BitmapFactory.Options()
bmpOption.inJustDecodeBounds = true
val fis = FileInputStream(file)
BitmapFactory.decodeStream(fis, null, bmpOption)
fis.close()
val scale = 1
if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE)
{
scale = Math.pow(2.0, Math.ceil((Math.log((IMAGE_MAX_SIZE / Math.max(bmpOption.outHeight, bmpOption.outWidth) as Double)) / Math.log(0.5))).toInt().toDouble()).toInt()
}
val bmpOption2 = BitmapFactory.Options()
bmpOption2.inSampleSize = scale
fis = FileInputStream(file)
bm = BitmapFactory.decodeStream(fis, null, bmpOption2)
fis.close()
避免内存泄漏或位图OOM的最佳实践
- 不要保留对上下文/活动的长期位图引用。
- 如果您在应用程序中使用大位图作为背景或其他内容,请不要将完整图像拉入主内存。可以使用位图的 insample size 属性来调整屏幕所需的大小。
- 清理位图引用一次不再使用。
我需要将大尺寸图像加载到位图中,我使用 Glide 解决了这个问题。首先使用 BitmapFactory.Options 将 inJustDecodeBounds 设置为 true 检查图像大小,然后使用 Glide 获取 Bitmap 对象。我使用分析器检查内存使用情况,但我没有看到任何内存峰值,就像我使用 BitmapFactory.decodeFile() 时那样。 当我使用 Xamarin 时,我正在用 c# 编写,因此需要稍作调整才能在 Java 中使用。Glide 库文档
private Bitmap DecodeFile(File file) {
// Decode image size
BitmapFactory.Options options = new BitmapFactory.Options();
// setting inJustDecodeBounds to true won't load the file into memory,
// but gives you the actual file size.
options.InJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(file), null, options);
int actualWidth = options.OutWidth;
int actualHeight = options.OutHeight;
var ratio = (double)actualHeight / actualWidth;
// Default to 800 x 600. changed the size whatever you need.
var desiredWidth = 800;
var desiredHeight = 600;
if(actualHeight > actualWidth)
{
var ratio = (double)actualWidth / actualHeight;
var futureTarget = Glide.With(Application.Context)
.AsBitmap()
.Load(file)
.SetDiskCacheStrategy(DiskCacheStrategy.None)
.SkipMemoryCache(true)
.Submit((int)(desiredWidth * ratio), desiredWidth);
bitmap = futureTarget.Get() as Bitmap;
}
else
{
var ratio = (double)actualHeight / actualWidth;
var futureTarget = Glide.With(Application.Context)
.AsBitmap()
.Load(file)
.SetDiskCacheStrategy(DiskCacheStrategy.None)
.SkipMemoryCache(true)
.Submit(desiredWidth, (int)(desiredWidth * ratio));
bitmap = futureTarget.Get() as Bitmap;
}return bitmap;}
评论