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

Strange out of memory issue while loading an image to a Bitmap object

我有一个列表视图,每行有几个图像按钮。当您单击列表行时,它将启动一个新的活动。我不得不建立自己的标签,因为相机布局有问题。为结果启动的活动是一个映射。如果我单击我的按钮启动图像预览(从SD卡加载图像),应用程序将从活动返回到结果处理程序的listview活动,以重新启动我的新活动,该活动只不过是一个图像小部件。

列表视图上的图像预览是通过光标和ListAdapter完成的。这使得它非常简单,但我不确定如何将调整大小的图像(即较小的位大小而不是像素作为src)用于图像按钮。所以我调整了手机摄像头的图像大小。

问题是,当它试图返回并重新启动第二个活动时,会出现内存不足错误。

  • 有没有一种方法可以让我一行一行地轻松构建列表适配器,在那里我可以动态地调整大小(按位排列)?

这是更好的选择,因为我还需要对每行中的小部件/元素的属性进行一些更改,因为由于焦点问题,我无法用触摸屏选择行。(我可以用滚球。)

  • 我知道我可以做一个带外调整和保存我的图像,但这不是我真正想做的,但一些样本代码会很好。

一旦我禁用了列表视图中的图像,它就会再次正常工作。

仅供参考:我就是这样做的:

1
2
3
4
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

这是我的日志猫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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

我在显示图像时也有一个新错误:

1
2
3
4
5
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


要修复内存不足错误,应执行以下操作:

1
2
3
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

inSampleSize选项减少了内存消耗。

这是一个完整的方法。首先,它读取图像大小而不解码内容本身。然后找到最佳的inSampleSize值,它应该是2的幂,最后对图像进行解码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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;
}


安卓培训班"高效显示位图"为理解和处理加载位图时出现的异常java.lang.OutOfMemoryError: bitmap size exceeds VM budget提供了一些很好的信息。

读取位图尺寸和类型

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

1
2
3
4
5
6
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中,那么它不值得加载到内存中。

要让解码器对图像进行次采样,将较小的版本加载到内存中,请将inSampleSize设置为true对象中的BitmapFactory.Options。例如,分辨率为2048x1536的图像用4的inSampleSize解码后,生成的位图大约为512x384。将它加载到内存中使用0.75MB而不是12MB作为完整图像(假设ARGB_8888的位图配置)。下面是一种根据目标宽度和高度计算样本大小值(2的幂)的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

Note: A power of two value is calculated because the decoder uses a
final value by rounding down to the nearest power of two, as per the
inSampleSize documentation.

要使用此方法,首先使用inJustDecodeBounds设置为true进行解码,通过选项,然后使用新的inSampleSize值和inJustDecodeBounds设置为false再次解码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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中,如下示例代码所示:

1
2
mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

您可以根据需要替换适当的BitmapFactory.decode*方法,按照类似的过程从其他来源解码位图。


我对Fedor的代码做了一个小小的改进。它基本上是一样的,但是没有(在我看来)难看的while循环,它总是产生两个幂。费多对最初的解决方案的赞誉,我一直坚持到找到了他的方案,然后我就做出了这个方案:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 private Bitmap decodeFile(File f){
    Bitmap b = null;

        //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;

    FileInputStream fis = new FileInputStream(f);
    BitmapFactory.decodeStream(fis, null, o);
    fis.close();

    int scale = 1;
    if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
        scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /
           (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
    }

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize = scale;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, o2);
    fis.close();

    return b;
}


我来自iOS的经验,我很沮丧地发现了一个问题,比如加载和显示图像。毕竟,每个有此问题的人都在尝试显示大小合理的图像。不管怎样,这里有两个改变可以解决我的问题(并且使我的应用程序非常响应)。

1)每次执行BitmapFactory.decodeXYZ()操作时,一定要将inPurgeable设置为true(最好是inInputShareable也设置为true)的BitmapFactory.Options通过。

2)切勿使用Bitmap.createBitmap(width, height, Config.ARGB_8888)。我的意思是永远不会!我从来没有经历过这样的事情:几次通过后,记忆错误就不会出现。没有任何数量的recycle()System.gc(),无论有什么帮助。它总是引发异常。另一种实际工作的方法是在您的抽屉中放置一个虚拟图像(或使用上面的步骤1解码的另一个位图),将其重新缩放到您想要的任何位置,然后操作生成的位图(例如将其传递到画布上以获得更多乐趣)。因此,您应该使用:Bitmap.createScaledBitmap(srcBitmap, width, height, false)。如果出于任何原因必须使用蛮力创建方法,那么至少要传递Config.ARGB_4444

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


这是一个已知的错误,不是因为文件太大。由于android缓存了这些抽屉,所以在使用了很少的图片之后,它会耗尽内存。但我找到了另一种方法,跳过Android默认缓存系统。

解决方案:将图像移动到"assets"文件夹,并使用以下功能获取位图绘图:

1
2
3
4
5
6
public static Drawable getAssetImage(Context context, String filename) throws IOException {
    AssetManager assets = context.getResources().getAssets();
    InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename +".png")));
    Bitmap bitmap = BitmapFactory.decodeStream(buffer);
    return new BitmapDrawable(context.getResources(), bitmap);
}

我也遇到了同样的问题,通过避免bitmapfactory.decodestream或decodefile函数来解决它,而是使用BitmapFactory.decodeFileDescriptor

decodeFileDescriptor似乎调用的本地方法与decodestream/decodefile不同。

不管怎样,有效的方法是这样的(注意,我添加了一些选项,就像上面提到的那样,但这并不是造成差异的原因。关键是调用BitmapFactory.decodeFileDescriptor而不是decodestream或decodefile):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void showImage(String path)   {

    Log.i("showImage","loading:"+path);
    BitmapFactory.Options bfOptions=new BitmapFactory.Options();
    bfOptions.inDither=false;                     //Disable Dithering mode
    bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
    bfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
    bfOptions.inTempStorage=new byte[32 * 1024];

    File file=new File(path);
    FileInputStream fs=null;
    try {
        fs = new FileInputStream(file);
    } catch (FileNotFoundException e) {
        //TODO do something intelligent
        e.printStackTrace();
    }

    try {
        if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
    } catch (IOException e) {
        //TODO do something intelligent
        e.printStackTrace();
    } finally{
        if(fs!=null) {
            try {
                fs.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    im.setImageBitmap(bm);
    //bm.recycle();
    bm=null;                        
}

我认为decodestream/decodefile中使用的本机函数有问题。我已经确认在使用decodeFileDescriptor时调用了不同的本机方法。我所读到的是"图像(位图)不是以标准Java方式分配的,而是通过本地调用分配的,这些分配是在虚拟堆之外完成的,但是算了吧!"


我认为最好的办法是面对并理解它。

我开发了一个应用程序来故意引起OutOfMemoryError,并监视内存使用情况。

在我用这个应用程序做了很多实验之后,我得出了以下结论:

我要先谈一下SDK版本,然后再谈蜂蜜梳。

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

  • 如果vm heap size+为设备分配的本机堆内存>=vm heap size limit,并且您尝试创建位图,则会引发OOM。

    注意:VM堆大小是计数的,而不是VM分配的内存。

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

  • 因此,您必须将峰值虚拟机内存保持在尽可能低的水平,以防止虚拟机堆大小增长过大,从而为位图保存可用内存。

  • 手工调用system.gc()是没有意义的,系统会先调用它,然后再尝试增大堆的大小。

  • 本机堆大小永远不会缩小,但它不计算OOM,因此无需担心。

  • 然后,让我们从Honey Comb开始讨论SDK。

  • 位图存储在VM堆中,本机内存不计算OOM。

  • OOM的条件要简单得多:VM堆大小>=设备的VM堆大小限制。

  • 所以您有更多的可用内存来创建具有相同堆大小限制的位图,OOM不太可能被抛出。

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

    你可以在应用程序中看到它。如果某个活动在该活动被破坏后执行了仍在运行的AsyncTask,则在AsyncTask完成之前,该活动将不会被垃圾收集。

    这是因为AsyncTask是一个匿名内部类的实例,它保存了活动的引用。

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

    回调也是匿名的内部类,因此,如果项目中的静态实例持有回调并不释放回调,则内存将被泄漏。

    如果计划了重复或延迟的任务(例如计时器),并且在onpause()中不调用cancel()和purge(),则内存将泄漏。


    最近我看到很多关于OOM异常和缓存的问题。《开发人员指南》有一篇关于这方面的非常好的文章,但有些人往往无法以适当的方式实现它。

    因此,我编写了一个示例应用程序,演示了Android环境中的缓存。此实现尚未获得OOM。

    请查看此答案的结尾,以获取指向源代码的链接。

    要求:

    • Android API 2.1或更高版本(我根本无法获得API 1.6中应用程序的可用内存——这是API 1.6中唯一不起作用的代码)
    • Android支持包

    Screenshot

    特征:

    • 如果有方向更改,则使用单实例保留缓存
    • 使用分配给缓存的应用程序内存的八分之一(如果需要,请修改)
    • 大位图被缩放(您可以定义要允许的最大像素)
    • 控制下载位图之前是否有可用的Internet连接
    • 确保每行只实例化一个任务
    • 如果你把ListView扔到一边,它就不会在

    这不包括:

    • 磁盘缓存。无论如何,这应该很容易实现——只需指向从磁盘获取位图的另一个任务。

    样例代码:

    正在下载的图像是来自Flickr的图像(75x75)。但是,放置您想要处理的任何图像URL,如果超出最大值,应用程序将缩小它。在这个应用程序中,URL只是在一个String数组中。

    LruCache有一个很好的方法来处理位图。但是,在这个应用程序中,我将LruCache的一个实例放在我创建的另一个缓存类中,以便使应用程序更可行。

    高速缓存。Java的关键部分(EDCOX1 4)方法是最重要的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    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文件中的任何内容。

    主要活动:Java的关键内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    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()经常被呼叫。如果我们没有执行一个确保我们不会在每行中启动无限数量线程的检查,那么在那里下载图像通常不是一个好主意。java检查rowObject.mBitmapUrl是否已经在任务中,如果已经在任务中,则不会启动另一个任务。因此,我们很可能不会超过来自AsyncTask池的工作队列限制。

    下载:

    您可以从https://www.dropbox.com/s/pvr9zyl811tfeem/listviewimagecache.zip下载源代码。

    最后的话:

    我已经测试了几个星期了,我还没有得到一个单独的OOM例外。我在模拟器、NexusOne和NexusS上测试过这个,我测试过包含高清图像的图像URL。唯一的瓶颈是需要更多的时间来下载。

    只有一个可能的场景,我可以想象OOM会出现,那就是如果我们下载许多非常大的图像,在它们被缩放并放入缓存之前,它们会同时占用更多的内存并导致OOM。但无论如何,这甚至不是一个理想的情况,而且很可能无法以更可行的方式解决。

    在评论中报告错误!-)


    我做了下面的工作来拍摄图像并动态调整其大小。希望这有帮助

    1
    2
    3
    4
    Bitmap bm;
    bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
    mPicture = new ImageView(context);
    mPicture.setImageBitmap(bm);


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

    1
    2
     <application
             android:largeHeap="true"


    这似乎是一个长期存在的问题,有很多不同的解释。我接受了这里两个最常见的答案的建议,但这两个都没有解决我的问题,虚拟机声称它负担不起字节来执行解码过程的一部分。经过一些挖掘,我了解到这里真正的问题是解码过程会从本地堆中去掉。

    看这里:BitmapFactory OOM让我发疯

    这就引出了另一个讨论主题,在这里我找到了更多解决这个问题的方法。一种是在图像显示后手动调用docx1〔3〕。但这实际上会使你的应用程序使用更多的内存,以减少本机堆。从2.0(甜甜圈)发布之日起,更好的解决方案是使用BitmapFactory选项"可手术"。所以我只是在o2.inSampleSize=scale;之后加了o2.inPurgeable=true;

    关于这个主题的更多信息,请看:内存堆的限制仅仅是6米吗?

    现在,说了这么多,我也是一个拥有Java和Android的完全笨蛋。所以如果你认为这是解决这个问题的一个糟糕的方法,你可能是对的。;-)但这对我来说是个奇迹,我发现现在不可能将VM从堆缓存中运行出来。我能发现的唯一缺点是,您正在破坏缓存的绘制图像。也就是说,如果你回到那个图像,你每次都要重新绘制它。在我的应用程序如何工作的情况下,这不是真正的问题。您的里程可能会有所不同。


    使用此bitmap.recycle();有助于避免任何图像质量问题。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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);


    我有一个更有效的解决方案,它不需要任何形式的缩放。只需对位图进行一次解码,然后将其缓存在映射中,与名称相对应。然后只需根据名称检索位图并在ImageView中设置它。没有什么需要做的了。

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

    为了帮助您更好地理解这一点,假设您将您的图像保存在可绘制文件夹中。只需执行getresources().getdrwable(r.drawable)即可获得图像。这不会每次都解码您的图像,但每次调用它时都会重复使用已解码的实例。所以本质上它是缓存的。

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

    希望这有帮助。


    这对我有用!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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());
        }
    }

    这里有两个问题……

    • 位图内存不在VM堆中,而是在本机堆中-请参阅BitmapFactory OOM让我抓狂
    • 本机堆的垃圾收集比VM堆慢-因此,每次执行活动的onPause或onDestroy时,都需要非常积极地执行bitmap.recycle和bitmap=null。


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

    这是我的BitmapHelper类,它不支持内存或错误:-)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    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;
        }

    }


    这对我有用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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单机器人。您可以轻松地更改图像的路径。这里重要的是要设置的选项。


    上面的答案对我来说都不管用,但我确实想出了一个可怕的解决问题的方法。我将一个非常小的1x1像素图像作为资源添加到我的项目中,并在调用垃圾收集之前将其加载到我的ImageView中。我认为这可能是因为ImageView没有释放位图,所以GC从来没有接收到它。它很难看,但现在似乎还有效。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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.


    这似乎是与社区共享我的实用程序类来加载和处理图像的合适地方,欢迎您使用它并自由修改。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    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;
        }
    }

    在我的一个应用程序中,我需要从Camera/Gallery中拍照。如果用户单击相机中的图像(可能是2MP、5MP或8MP),则图像大小从kBS到MBS不等。如果图像大小小于(或高达1-2MB),则代码正常工作,但如果图像大小大于4MB或5MB,则OOM进入帧:(

    然后我努力解决了这个问题,最后我对fedor的代码做了以下改进(所有这些都归功于fedor提出了这么好的解决方案)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    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;

    }

    希望这能帮助朋友们面对同样的问题!

    更多信息请参考此


    几分钟前我刚碰到这个问题。我通过更好地管理ListView适配器解决了这个问题。我认为这是我使用的数百张50x50px图像的问题,结果我每次显示行时都试图膨胀我的自定义视图。通过测试查看行是否已经膨胀,我消除了这个错误,并且使用了数百个位图。这实际上是针对微调器的,但基本适配器对ListView的工作方式完全相同。这个简单的修复也大大提高了适配器的性能。

    1
    2
    3
    4
    5
    6
    7
    8
    @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);
        }
    ...


    此问题仅在Android模拟器中发生。我在模拟器中也遇到了这个问题,但是当我签入一个设备时,它工作得很好。

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


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

    当我从ListView往下走一半的时候,它就会崩溃。

    现在,当我在活动B中实现以下内容时,我可以毫无问题地浏览整个listview,并继续前进和前进……而且速度非常快。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Override
    public void onDestroy()
    {  
        Cleanup();
        super.onDestroy();
    }

    private void Cleanup()
    {    
        bitmap.recycle();
        System.gc();
        Runtime.getRuntime().gc();  
    }


    将这些代码用于从SDCard或Drewable中选择的每个图像,以转换位图对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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()。


    我的2分:我通过位图解决了我的OOM错误:

    a)将图像缩放2倍

    b)在我的自定义适配器中使用毕加索库作为listview,在getview中有一个这样的调用:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);


    这里的所有解决方案都需要设置图像的最大尺寸。这限制了硬件功能更强大的设备,如果图像太小,在高清屏幕上会显得很难看。

    我提出了一个解决方案,可以与我的三星Galaxy S3和其他一些设备(包括功能较弱的设备)配合使用,当使用功能更强的设备时,图像质量更好。

    它的要点是计算在一个特定设备上为应用程序分配的最大内存,然后在不超过该内存的情况下将比例设置为尽可能低的值。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    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%在执行图像旋转时为我提供了一个良好的缓冲区,而不会耗尽内存。

    希望这能帮上忙。


    通常,Android设备堆大小仅为16MB(因设备/操作系统而异,请参阅后堆大小),如果您正在加载图像,并且它跨越了16MB的大小,它将抛出内存异常,而不是使用位图从SD卡或资源甚至网络加载图像,尝试使用GetImageUri,加载位图需要重新存储,或者,如果您的工作完成了位图,您可以将位图设置为空。


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

    通过参考活动生命周期

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

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    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;
        }
    }


    您使用的图像的大小似乎非常大。因此,一些较旧的设备由于堆内存已满而崩溃。在较旧的设备(Honey Comb或ICS或任何低端型号设备)中,请尝试在应用程序标记下的清单文件中使用android:largeHeap="true",或使用以下代码减小位图的大小。

    1
    2
    3
    4
    Bitmap bMap;
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.InSampleSize = 8;
    bMap= BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

    也可以给4、12或16以减小位图大小


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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();


    我尝试了Thomas Vervest的方法,但当图像最大尺寸为2048时,它返回图像尺寸2592x1944的1比例。

    此版本基于其他人提供的所有其他评论为我工作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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;
    }

    您好,请访问链接http://developer.android.com/training/displaying-bitmaps/index.html

    或者尝试使用给定的函数检索位图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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 ;
    }


    要修复内存不足,您应该执行类似的操作,请尝试此代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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;
                }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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();
                    }


    使用这个概念,这将有助于您,然后在图像视图上设置ImageBitmap

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    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的大图像中生成一个小图像,并快速滚动ListView,则使用此概念

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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;
            }

    我希望这对你有很大帮助。

    您可以在这里从开发人员网站获得帮助


    在浏览了所有的答案之后,我惊讶地发现没有人提到glide API来处理图像。伟大的库,抽象出位图管理的所有复杂性。您可以使用此库和一行代码快速加载和调整图像大小。

    1
         Glide.with(this).load(yourImageResource).into(imageview);

    您可以在这里获得存储库:https://github.com/bumptech/glide


    这将获得适当的位图并减少内存消耗。

    爪哇

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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();

    科特林

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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()

    如果你像我一样懒惰。您可以开始使用毕加索图书馆加载图像。http://square.github.io/毕加索/

    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);


    我使用了对我有用的解码文件描述符:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     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();
                    }
                }
            }

    从文件描述符解码采样位图的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
     /**
         * 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;
        }

    将以下行添加到manifest.xml文件中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <application

        android:hardwareAccelerated="false"
        android:largeHeap="true"

       
        </activity>

    </application>


    将位图设置为ImageView后,按如下方式循环:

    1
    2
    bitmap.recycle();
    bitmap=null;