将图像加载到Bitmap对象时出现奇怪的内存不足问题

android image bitmap out-of-memory android-bitmap

591953 观看

30回复

8821 作者的声誉

我有一个列表视图,每行有几个图像按钮。单击列表行时,将启动新活动。由于相机布局有问题,我不得不建立自己的标签。为结果启动的活动是地图。如果我单击我的按钮启动图像预览(从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.imagefilenameButtonImage

这是我的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
作者: Chrispix 的来源 发布者: 2009 年 1 月 25 日

回应 30


38

8821 作者的声誉

我做了以下操作来拍摄图像并在运行中调整大小。希望这可以帮助

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    
作者: Chrispix 发布者: 2009 年 2 月 16 日

877

40641 作者的声誉

要修复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;
}
作者: Fedor 发布者: 2009 年 5 月 5 日

360

1996 作者的声誉

我对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;
}
作者: Thomas Vervest 发布者: 2010 年 8 月 23 日

28

85 作者的声誉

使用bitmap.recycle();此功能可以避免任何图像质量问题。

作者: Arsalan 发布者: 2010 年 11 月 7 日

25

101 作者的声誉

我有一个更有效的解决方案,不需要任何类型的扩展。只需解码您的位图一次,然后将其缓存在地图中,与其名称相对应。然后只需根据名称检索位图并在ImageView中设置它。没有什么需要做的。

这将起作用,因为解码位图的实际二进制数据不存储在dalvik VM堆中。它存储在外部。因此,每次解码位图时,它都会在VM堆外部分配内存,而这些内存永远不会被GC回收

为了帮助您更好地理解这一点,想象一下您将图像保留在可绘制文件夹中。您只需通过执行getResources()。getDrwable(R.drawable。)来获取图像。这不会每次解码您的图像,但每次调用时都会重新使用已解码的实例。所以从本质上说它是缓存的。

现在,由于您的图像位于某个文件中(或者甚至可能来自外部服务器),因此您有责任将解码后的位图实例缓存,以便在需要的地方重复使用。

希望这可以帮助。

作者: Parth Mehta 发布者: 2011 年 3 月 2 日

24

3905 作者的声誉

这里有两个问题....

  • 位图内存不在VM堆中,而是在本机堆中 - 请参阅BitmapFactory OOM让我疯狂
  • 本机堆的垃圾收集比VM堆更懒 - 所以每次通过Activity的onPause或onDestroy时,你需要非常积极地做bitmap.recycle和bitmap = null
作者: Torid 发布者: 2011 年 5 月 12 日

31

151 作者的声誉

这似乎是一个非常长期存在的问题,有很多不同的解释。我在这里接受了两个最常见的答案的建议,但这些都没有解决我的VM问题,声称它无法承担字节来执行过程的解码部分。经过一番挖掘后,我了解到这里的真正问题是解除NATIVE堆的过程。

看到这里:BitmapFactory OOM让我疯狂

这引导我进入另一个讨论主题,在那里我找到了更多解决这个问题的方法。一种是System.gc();在显示图像后手动调用。但这实际上使您的应用程序使用更多内存,以减少本机堆。2.0(Donut)发布时更好的解决方案是使用BitmapFactory选项“inPurgeable”。所以,我只是增加o2.inPurgeable=true;刚过o2.inSampleSize=scale;

有关该主题的更多信息:内存堆的限制是否只有6M?

现在,说完所有这些之后,我对Java和Android也是一个完整的过程。因此,如果您认为这是解决此问题的可怕方法,那么您可能是对的。;-)但这对我有用,我发现现在无法从堆缓存中运行VM。我能找到的唯一缺点就是你在捣毁你的缓存绘制图像。这意味着如果你回到那个图像,你每次都会重新绘制它。在我的应用程序如何工作的情况下,这不是一个真正的问题。你的旅费可能会改变。

作者: RayHaque 发布者: 2011 年 5 月 19 日

87

984 作者的声誉

这是一个已知的错误,它不是因为大文件。由于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);
}
作者: Anto Binish Kaspar 发布者: 2011 年 5 月 24 日

25

320 作者的声誉

我已按以下方式解决了同样的问题。

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);
作者: Prerna 发布者: 2011 年 6 月 4 日

72

5299 作者的声誉

我有同样的问题,通过避免使用BitmapFactory.decodeStream或decodeFile函数来解决它 BitmapFactory.decodeFileDescriptor

decodeFileDescriptor 看起来它比decodeStream / decodeFile调用不同的本机方法。

无论如何,这是有效的(请注意,我添加了一些选项,如上所述,但这不是产生差异的原因。关键是调用BitmapFactory.decodeFileDescriptor而不是decodeStreamdecodeFile):

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方式分配,而是通过本机调用分配;分配是在虚拟堆之外完成的,但是要对它进行 计数!

作者: Fraggle 发布者: 2011 年 8 月 19 日

18

646 作者的声誉

None of the answers above worked for me, but I did come up with a horribly ugly workaround that solved the problem. I added a very small, 1x1 pixel image to my project as a resource, and loaded it into my ImageView before calling into garbage collection. I think it might be that the ImageView was not releasing the Bitmap, so GC never picked it up. It's ugly, but it seems to be working for now.

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.
作者: Mike 发布者: 2011 年 12 月 7 日

224

2189 作者的声誉

我来自iOS经验,我很沮丧地发现了一个像加载和显示图像这样基本的问题。毕竟,遇到此问题的每个人都试图显示合理大小的图像。无论如何,这里有两个修改我的问题的变化(并使我的应用程序非常敏感)。

1)你做的每一次BitmapFactory.decodeXYZ(),请务必在一传BitmapFactory.OptionsinPurgeable集于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

这几乎可以保证,如果不是几天,你可以节省数小时。所有关于缩放图像等的讨论都没有真正起作用(除非您考虑将错误的大小或降级的图像作为解决方案)。

作者: Ephraim 发布者: 2011 年 12 月 15 日

13

1377 作者的声誉

我刚刚在几分钟前遇到过这个问题。我通过更好地管理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);
    }
...
作者: BajaBob 发布者: 2012 年 2 月 24 日

12

1251 作者的声誉

我花了整整一天来测试这些解决方案,唯一对我有用的是上面获取图像和手动调用GC的方法,我知道这不是必需的,但它是唯一有效的方法当我把我的应用程序置于重负载测试之间切换活动。我的应用程序在列表视图中有一个缩略图列表(比如活动A),当您点击其中一个图像时,它会带您进入另一个显示该项目主图像的活动(比如活动B)。当我在两个活动之间来回切换时,我最终会得到OOM错误,应用程序会强行关闭。

当我在列表视图中间一半时,它会崩溃。

现在,当我在活动B中实现以下内容时,我可以浏览整个列表视图,没有任何问题,继续前进和继续......并且它的速度很快。

@Override
public void onDestroy()
{   
    Cleanup();
    super.onDestroy();
}

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
作者: Jesse 发布者: 2012 年 3 月 27 日

607

13367 作者的声誉

决定

Android的训练课,“ 显示位图高效 ”,提供了一些伟大的信息,理解和处理异常java.lang.OutOfMemoryError: bitmap size exceeds VM budget装载位图时。


读取位图尺寸和类型

BitmapFactory类提供了几种解码方法(decodeByteArray()decodeFile()decodeResource(),等等),用于创建Bitmap来自各种来源。根据图像数据源选择最合适的解码方法。这些方法尝试为构造的位图分配内存,因此很容易导致OutOfMemory异常。每种类型的解码方法都有其他签名,可让您通过BitmapFactory.Options类指定解码选项。在解码时将inJustDecodeBounds属性设置为true避免内存分配,返回null位图对象但设置outWidthoutHeightoutMimeType。此技术允许您在构造(和内存分配)位图之前读取图像数据的尺寸和类型。

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

告诉解码器子样本图像,加载一个较小的版本到内存中,设置inSampleSizetrue你的BitmapFactory.Options对象。例如,分辨率为2048x1536且用inSampleSize4 解码的图像产生大约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文档。

要使用此方法,首先使用inJustDecodeBoundsset 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*方法是根据需要替换相应的方法。

作者: AdamK 发布者: 2012 年 4 月 12 日

24

5382 作者的声誉

这对我有用!

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());
    }
}
作者: Luke Taylor 发布者: 2012 年 6 月 9 日

10

525 作者的声誉

这样OutofMemoryException就不能通过调用等来完全解决System.gc()

通过参考活动生命周期

活动状态由操作系统本身决定,取决于每个进程的内存使用情况和每个进程的优先级。

您可以考虑使用的每个位图图片的大小和分辨率。我建议缩小尺寸,重新采样以降低分辨率,参考画廊的设计(一张小图片PNG和一张原始图片。)

作者: Raju yourPepe 发布者: 2012 年 8 月 8 日

12

153 作者的声誉

此问题仅发生在Android模拟器中。我也在模拟器中遇到过这个问题但是当我检查了一个设备然后它工作正常。

所以请检查设备。它可以在设备中运行。

作者: Android Dev 发布者: 2012 年 8 月 13 日

59

24654 作者的声誉

我最近看到了很多关于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<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。但这无论如何都不是理想的情况,很可能无法以更可行的方式解决。

在评论中报告错误!:-)

作者: Wroclai 发布者: 2012 年 8 月 24 日

18

130 作者的声誉

This works for me.

Bitmap myBitmap;

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;

File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

and this is on C# monodroid. you can easily change the path of the image. what important here is the options to be set.

作者: Dobermaxx99 发布者: 2012 年 10 月 2 日

10

234 作者的声誉

此代码将有助于从drawable加载大位图

public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {

    Context context;

    public BitmapUtilsTask(Context context) {
        this.context = context;
    }

    /**
     * Loads a bitmap from the specified url.
     * 
     * @param url The location of the bitmap asset
     * @return The bitmap, or null if it could not be loaded
     * @throws IOException
     * @throws MalformedURLException
     */
    public Bitmap getBitmap() throws MalformedURLException, IOException {       

        // Get the source image's dimensions
        int desiredWidth = 1000;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        int srcWidth = options.outWidth;
        int srcHeight = options.outHeight;

        // Only scale if the source is big enough. This code is just trying
        // to fit a image into a certain width.
        if (desiredWidth > srcWidth)
            desiredWidth = srcWidth;

        // Calculate the correct inSampleSize/scale value. This helps reduce
        // memory use. It should be a power of 2
        int inSampleSize = 1;
        while (srcWidth / 2 > desiredWidth) {
            srcWidth /= 2;
            srcHeight /= 2;
            inSampleSize *= 2;
        }
        // Decode with inSampleSize
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = inSampleSize;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inPurgeable = true;
        Bitmap sampledSrcBitmap;

        sampledSrcBitmap =  BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        return sampledSrcBitmap;
    }

    /**
     * The system calls this to perform work in a worker thread and delivers
     * it the parameters given to AsyncTask.execute()
     */
    @Override
    protected Bitmap doInBackground(Object... item) {
        try { 
          return getBitmap();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
作者: vineet 发布者: 2012 年 10 月 31 日

14

10551 作者的声誉

This seems like the appropriate place to share my utility class for loading and processing images with the community, you are welcome to use it and modify it freely.

package com.emil;

import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * A class to load and process images of various sizes from input streams and file paths.
 * 
 * @author Emil http://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;
    }
}
作者: Emil Davtyan 发布者: 2012 年 11 月 2 日

10

2464 作者的声誉

一般的Android设备堆的大小只有16MB(从设备/ OS看帖子变化堆大小),如果要加载的图像和它跨越16MB的大小,它会抛出内存溢出异常,而不是使用位图,装载来自SD卡或资源甚至网络的图像尝试使用getImageUri,加载位图需要更多内存,或者如果您使用该位图完成工作,则可以将位图设置为null。

作者: Mahesh 发布者: 2012 年 11 月 26 日

66

1286 作者的声誉

我认为避免这种OutOfMemoryError情况的最佳方法是面对它并理解它。

我制作了一个有意识地引起并监控内存使用情况的应用程序OutOfMemoryError

在我使用此应用程序完成了大量实验后,我得出以下结论:

我将在Honey Comb之前讨论SDK版本。

  1. 位图存储在本机堆中,但它会自动收集垃圾,调用recycle()是不必要的。

  2. 如果{VM堆大小} + {已分配的本机堆内存}> = {设备的VM堆大小限制},并且您尝试创建位图,则将抛出OOM。

    注意:计算VM HEAP SIZE而不是VM ALLOCATED MEMORY。

  3. 即使分配的VM内存缩小,VM堆大小也不会在增长后缩小。

  4. 因此,您必须尽可能降低峰值VM内存,以防止VM堆大小过大而无法为Bitmaps节省可用内存。

  5. 手动调用System.gc()是没有意义的,系统会在尝试增加堆大小之前先调用它。

  6. 原生堆大小永远不会缩小,但它不计入OOM,因此无需担心它。

然后,我们来谈谈从Honey Comb开始的SDK。

  1. 位图存储在VM堆中,本机内存不计入OOM。

  2. OOM的条件要简单得多:{VM heap size}> = {设备的VM堆大小限制}。

  3. 因此,您有更多可用内存来创建具有相同堆大小限制的位图,因此不太可能抛出OOM。

以下是我对垃圾收集和内存泄漏的一些观察。

您可以在App中自己查看。如果一个Activity执行了一个在销毁Activity之后仍在运行的AsyncTask,那么在AsyncTask完成之前,Activity不会被垃圾收集。

这是因为AsyncTask是匿名内部类的一个实例,它拥有Activity的引用。

如果在后台线程中的IO操作中阻止任务,则调用AsyncTask.cancel(true)将不会停止执行。

回调也是匿名内部类,因此如果项目中的静态实例保留它们并且不释放它们,则内存将被泄露。

如果您安排了重复或延迟的任务,例如Timer,并且您没有在onPause()中调用cancel()和purge(),则内存将被泄露。

作者: coocood 发布者: 2012 年 12 月 1 日

19

5145 作者的声誉

这里有很好的答案,但我想要一个完全可用的类来解决这个问题..所以我做了一个。

Here is my BitmapHelper class that is OutOfMemoryError proof :-)

import java.io.File;
import java.io.FileInputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

public class BitmapHelper
{

    //decodes image and scales it to reduce memory consumption
    public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
    {
        try
        {
            //Decode image size
            BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
            bitmapSizeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);

            // load image using inSampleSize adapted to required image size
            BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
            bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
            bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
            bitmapDecodeOptions.inPurgeable = true;
            bitmapDecodeOptions.inDither = !quickAndDirty;
            bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

            Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);

            // scale bitmap to mathc required size (and keep aspect ratio)

            float srcWidth = (float) bitmapDecodeOptions.outWidth;
            float srcHeight = (float) bitmapDecodeOptions.outHeight;

            float dstWidth = (float) requiredWidth;
            float dstHeight = (float) requiredHeight;

            float srcAspectRatio = srcWidth / srcHeight;
            float dstAspectRatio = dstWidth / dstHeight;

            // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
            // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
            // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
            // I do not excatly understand why, but this way it's OK

            boolean recycleDecodedBitmap = false;

            Bitmap scaledBitmap = decodedBitmap;
            if (srcAspectRatio < dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
                // will recycle recycleDecodedBitmap
                recycleDecodedBitmap = true;
            }
            else if (srcAspectRatio > dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
                recycleDecodedBitmap = true;
            }

            // crop image to match required image size

            int scaledBitmapWidth = scaledBitmap.getWidth();
            int scaledBitmapHeight = scaledBitmap.getHeight();

            Bitmap croppedBitmap = scaledBitmap;

            if (scaledBitmapWidth > requiredWidth)
            {
                int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }
            else if (scaledBitmapHeight > requiredHeight)
            {
                int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }

            if (recycleDecodedBitmap)
            {
                decodedBitmap.recycle();
            }
            decodedBitmap = null;

            scaledBitmap = null;
            return croppedBitmap;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
     * 
     * @param requiredWidth
     * @param requiredHeight
     * @param powerOf2
     *            weither we want a power of 2 sclae or not
     * @return
     */
    public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
    {
        int inSampleSize = 1;

        // Raw height and width of image
        final int srcHeight = options.outHeight;
        final int srcWidth = options.outWidth;

        if (powerOf2)
        {
            //Find the correct scale value. It should be the power of 2.

            int tmpWidth = srcWidth, tmpHeight = srcHeight;
            while (true)
            {
                if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
                    break;
                tmpWidth /= 2;
                tmpHeight /= 2;
                inSampleSize *= 2;
            }
        }
        else
        {
            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
            final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

    public static Bitmap drawableToBitmap(Drawable drawable)
    {
        if (drawable instanceof BitmapDrawable)
        {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

    public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
    {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;

        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // RECREATE THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
        return resizedBitmap;
    }

}
作者: Pascal 发布者: 2013 年 2 月 3 日

14

11471 作者的声誉

在我的一个应用程序中,我需要拍照Camera/Gallery。如果用户单击来自Camera的图像(可能是2MP,5MP或8MP),图像大小会因kBs 而异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;

}

我希望这能帮助那些面临同样问题的伙伴们!

更多请参考这个

作者: Rupesh Yadav 发布者: 2013 年 2 月 6 日

11

766 作者的声誉

我的2美分:我用位图解决了我的OOM错误:

a)将我的图像缩放2倍

b)在我的自定义适配器中使用Picasso库进行ListView,在getView中进行一次调用,如下所示:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

作者: matsoftware 发布者: 2013 年 7 月 14 日

10

1307 作者的声誉

All the solutions here require setting a IMAGE_MAX_SIZE. This limits devices with more powerful hardware and if the image size is too low it looks ugly on the HD screen.

I came out with a solution that works with my Samsung Galaxy S3 and several other devices including less powerful ones, with better image quality when a more powerful device is used.

The gist of it is to calculate the maximum memory allocated for the app on a particular device, then set the scale to be lowest possible without exceeding this memory. Here's the code:

public static Bitmap decodeFile(File f)
{
    Bitmap b = null;
    try
    {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        try
        {
            BitmapFactory.decodeStream(fis, null, o);
        }
        finally
        {
            fis.close();
        }

        // In Samsung Galaxy S3, typically max memory is 64mb
        // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
        // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
        // We try use 25% memory which equals to 16mb maximum for one bitmap
        long maxMemory = Runtime.getRuntime().maxMemory();
        int maxMemoryForImage = (int) (maxMemory / 100 * 25);

        // Refer to
        // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
        // A full screen GridView filled with images on a device with
        // 800x480 resolution would use around 1.5MB (800*480*4 bytes)
        // When bitmap option's inSampleSize doubled, pixel height and
        // weight both reduce in half
        int scale = 1;
        while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
        scale *= 2;

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        try
        {
            b = BitmapFactory.decodeStream(fis, null, o2);
        }
        finally
        {
            fis.close();
        }
    }
    catch (IOException e)
    {
    }
    return b;
}

我将此位图使用的最大内存设置为最大分配内存的25%,您可能需要根据需要进行调整,并确保清理此位图,并在使用完后不留在内存中。通常我使用此代码执行图像旋转(源和目标位图),因此我的应用程序需要同时在内存中加载2个位图,25%给我一个良好的缓冲区,而不会在执行图像旋转时耗尽内存。

希望这有助于那里的人..

作者: Bruce 发布者: 2013 年 8 月 31 日

11

105 作者的声誉

将这些代码用于从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()的图像路径。

作者: Gaurav Pansheriya 发布者: 2014 年 1 月 24 日

32

363 作者的声誉

不幸的是,如果以上都不起作用,那么将其添加到您的清单文件中。内部应用标签

 <application
         android:largeHeap="true"
作者: Himanshu Mori 发布者: 2015 年 5 月 19 日
32x32