我已经阅读了许多关于解码位图的内存分配问题的相关帖子,但即使使用了官方网站提供的代码,我仍然无法找到解决以下问题的方法。
这是我的代码:
public static Bitmap decodeSampledBitmapFromResource(InputStream inputStream, int reqWidth, int reqHeight) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
baos.write(buffer, 0, len);
}
baos.flush();
InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
InputStream is2 = new ByteArrayInputStream(baos.toByteArray());
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is1, null, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeStream(is2, null, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
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;
}
return inSampleSize;
}
bitmap = decodeSampledBitmapFromResource(inputStream, 600, 600);
我在这行中得到“3250016字节分配的内存不足错误”:
return BitmapFactory.decodeStream(is2, null, options);
在我看来,3.2 MB足够小,可以分配。我哪里错了?我该如何解决这个问题?
修改
通过N-Joy查看此解决方案HERE后,它可以正常使用所需尺寸300,但我所需的尺寸为800,所以我仍然会收到错误。
答案 0 :(得分:36)
方法 decodeSampledBitmapFromResource
不具有内存效率,因为它使用3个流:ByteArrayOutputStream baos,ByteArrayInputStream is1和ByteArrayInputStream is2,每个存储相同的流数据image (每个字节数组)。
当我使用我的设备(LG nexus 4)测试将SD卡上的2560x1600图像解码为目标尺寸800时,需要执行以下操作:
03-13 15:47:52.557: E/DecodeBitmap(11177): dalvikPss (beginning) = 1780
03-13 15:47:53.157: E/DecodeBitmap(11177): dalvikPss (decoding) = 26393
03-13 15:47:53.548: E/DecodeBitmap(11177): dalvikPss (after all) = 30401 time = 999
我们可以看到:分配太多内存(28.5 MB)只是为了解码4096000像素图像。
解决方案:我们读取InputStream并将数据直接存储到一个字节数组中,并使用此字节数组进行其余工作。
示例代码:
public Bitmap decodeSampledBitmapFromResourceMemOpt(
InputStream inputStream, int reqWidth, int reqHeight) {
byte[] byteArr = new byte[0];
byte[] buffer = new byte[1024];
int len;
int count = 0;
try {
while ((len = inputStream.read(buffer)) > -1) {
if (len != 0) {
if (count + len > byteArr.length) {
byte[] newbuf = new byte[(count + len) * 2];
System.arraycopy(byteArr, 0, newbuf, 0, count);
byteArr = newbuf;
}
System.arraycopy(buffer, 0, byteArr, count, len);
count += len;
}
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(byteArr, 0, count, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
int[] pids = { android.os.Process.myPid() };
MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (decoding) = " + myMemInfo.dalvikPss);
return BitmapFactory.decodeByteArray(byteArr, 0, count, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
进行计算的方法:
public void onButtonClicked(View v) {
int[] pids = { android.os.Process.myPid() };
MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (beginning) = " + myMemInfo.dalvikPss);
long startTime = System.currentTimeMillis();
FileInputStream inputStream;
String filePath = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/test2.png";
File file = new File(filePath);
try {
inputStream = new FileInputStream(file);
// mBitmap = decodeSampledBitmapFromResource(inputStream, 800, 800);
mBitmap = decodeSampledBitmapFromResourceMemOpt(inputStream, 800,
800);
ImageView imageView = (ImageView) findViewById(R.id.image);
imageView.setImageBitmap(mBitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (after all) = " + myMemInfo.dalvikPss
+ " time = " + (System.currentTimeMillis() - startTime));
}
结果:
03-13 16:02:20.373: E/DecodeBitmap(13663): dalvikPss (beginning) = 1823
03-13 16:02:20.923: E/DecodeBitmap(13663): dalvikPss (decoding) = 18414
03-13 16:02:21.294: E/DecodeBitmap(13663): dalvikPss (after all) = 18414 time = 917
答案 1 :(得分:4)
这是用户在玩大型位图时通常会遇到的常见问题,网站上讨论的问题很多,here,here,here和here还有更多,即使用户无法操纵确切的解决方案。
我偶然发现了一个图书馆,它可以顺利地管理位图,以及我在下面列出的其他链接。希望这可以帮助!
Android-Universal-Image-Loader Solution for OutOfMemoryError: bitmap size exceeds VM budget
答案 2 :(得分:2)
ARGB_8888
使用更多内存,因为它需要Alpha颜色值,因此我的建议是使用RGB_565
HERE
注意:与ARGB_8888
相比,质量会低一些。
答案 3 :(得分:1)
您可能正在坚持以前的位图引用。我猜你正在多次执行这段代码,从不执行bitmap.recycle()
。记忆将不可避免地耗尽。
答案 4 :(得分:1)
我在使用Bitmap内存方面遇到了很多问题。
结果:
答案 5 :(得分:1)
使用 WebView 动态加载任意数量的图像,它是使用NDK(低级别)构建的,因此没有GDI堆内存限制。 它工作顺畅而快速:)
答案 6 :(得分:1)
解码位图时内存不足问题通常与您正在解码的图像大小无关。 当然,如果您尝试打开图像5000x5000px,您将失败并出现OutOfMemoryError,但是大小为800x800px,这是完全合理的,应该可以正常工作。
如果您的设备内存不足,带有3.2 MB的图像,可能是因为您在应用程序的某处泄露了上下文。
这是this post的第一部分:
我猜问题不在你的布局中,问题在其他地方 你的代码。并且可能是你在某处泄露了背景。
这意味着你在不应该使用的组件中使用Activity Context,防止它们被垃圾收集。因为组件通常由活动保存,所以这些活动不是GC,并且您的Java堆将快速增长,并且您的应用程序将在某个时间崩溃。
正如Raghunandan所说,你将不得不使用MAT来找到所持有的Activity / Component并删除上下文泄漏。
我现在发现检测上下文泄漏的最佳方法是方向更改。 例如,多次旋转ActivityMain,运行MAT并检查是否只有一个ActivityMain实例。如果您有多个(与旋转更改一样多),则表示存在上下文泄漏。
我多年前发现了good tutorial on using MAT。也许现在有更好的一个。
关于内存泄漏的其他帖子:
答案 7 :(得分:0)
看看这个视频。 http://www.youtube.com/watch?v=_CruQY55HOk。请勿使用视频中建议的system.gc()。使用MAT分析器查找内存泄漏。返回的位图太大导致内存泄漏我猜。
答案 8 :(得分:0)
答案 9 :(得分:-1)
我之前也有同样的问题..我已经通过使用这个功能管理它,你可以得到你所需的宽度和高度。
private Bitmap decodeFile(FileInputStream f)
{
try
{
//decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(f,null,o);
//Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE=70;
int width_tmp=o.outWidth, height_tmp=o.outHeight;
int scale=1;
while(true)
{
if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
break;
width_tmp/=2;
height_tmp/=2;
scale*=2;
}
//decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
return BitmapFactory.decodeStream(f, null, o2);
}
catch (FileNotFoundException e) {}
return null;
}
并参考Memory Leak Error Android和error in loading images to gridview android