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

ListView上的图像预览是用光标和ListAdapter完成的。这使得它非常简单,但我不确定我如何可以把一个调整大小的图像(即较小的位大小而不是像素作为图像按钮的src)。我只是调整了从手机摄像头拍下来的图片的大小。

问题是,当它试图返回并重新启动第二个活动时,我得到一个OutOfMemoryError。

是否有一种方法,我可以轻松地构建列表适配器行,在那里我可以调整大小的飞行(位)?

这将是可取的,因为我还需要对每行中的小部件/元素的属性做出一些更改,因为我无法选择一行与触摸屏,因为焦点问题。(我会用滚轮。)

我知道我可以做带外调整并保存我的图像,但这不是我真正想做的,但一些示例代码会很好。

当我在ListView上禁用图像时,它又可以正常工作了。

供你参考:我是这样做的:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME, DBHelper.KEY_ADDRESS,
    DBHelper.KEY_CITY, DBHelper.KEY_GPSLONG, DBHelper.KEY_GPSLAT,
    DBHelper.KEY_IMAGEFILENAME  + ""};
int[] to = new int[] { R.id.businessname, R.id.address, R.id.city, R.id.gpslong,
    R.id.gpslat, R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);

其中R.id.imagefilename是一个ButtonImage。

这是我的LogCat:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

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

22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed

当前回答

我对费多的代码做了一些小改进。它基本上做了同样的事情,但没有(在我看来)丑陋的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;
}

其他回答

我认为避免OutOfMemoryError的最好方法是面对它并理解它。

我做了一个应用程序,故意引起OutOfMemoryError,并监控内存使用情况。

在对这个App做了大量的实验之后,我得到了以下结论:

在Honey Comb之前,我先谈谈SDK版本。

位图存储在原生堆中,但它会自动被垃圾收集,调用recycle()是不必要的。 如果{VM堆大小}+{已分配的本机堆内存}>={设备的VM堆大小限制},并且您正在尝试创建位图,则会抛出OOM。 注意:计算的是虚拟机堆大小,而不是虚拟机已分配内存。 VM堆大小在增长后永远不会缩小,即使分配的VM内存减少了。 所以你必须保持VM内存的峰值尽可能低,以保持VM堆大小不会变得太大,以节省可用的内存位图。 手动调用system .gc()是没有意义的,系统会在尝试增加堆大小之前先调用它。 原生堆大小也永远不会缩小,但它不计入OOM,所以不用担心。

然后,让我们谈谈SDK从蜂巢开始。

位图存储在VM堆中,OOM不计算本机内存。 OOM的条件要简单得多:{虚拟机堆大小}>={设备的虚拟机堆大小限制}。 所以你有更多的可用内存来创建位图与相同的堆大小限制,OOM不太可能被抛出。

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

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

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

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

回调也是匿名的内部类,所以如果项目中的静态实例持有它们并且不释放它们,内存就会泄漏。

如果你计划一个重复或延迟的任务,例如定时器,你不调用取消()和清除()在onPause(),内存将被泄漏。

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

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

为了更好地理解这一点,想象一下你把你的图像保存在可绘制文件夹中。你只需要通过getResources(). getdrwable (R.drawable.)来获取图像。这将不是每次解码你的图像,而是在每次调用它时重用一个已经解码的实例。所以本质上它是被缓存的。

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

希望这能有所帮助。

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

JAVA

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

科特林

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

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

 <application
         android:largeHeap="true"

Android培训课程“高效显示位图”提供了一些很好的信息,用于理解和处理异常“java.lang.OutOfMemoryError:加载位图时,位图大小超过虚拟机预算”。


读取位图尺寸和类型

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

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

为了避免java.lang.OutOfMemory异常,在解码位图之前请检查位图的尺寸,除非您完全相信源代码能够为您提供可预测大小的图像数据,并且这些图像数据可以很好地适应可用内存。


将缩小的版本加载到内存中

既然知道了图像尺寸,就可以使用它们来决定是否应该将完整的图像加载到内存中,还是应该加载子采样版本。以下是一些需要考虑的因素:

估计在内存中加载完整图像的内存使用情况。 给定应用程序的任何其他内存需求,您愿意用于加载此映像的内存量。 图像要加载到的目标ImageView或UI组件的尺寸。 屏幕大小和当前设备的密度。

例如,如果最终将在ImageView中以128x96像素的缩略图显示,则不值得将1024x768像素的图像加载到内存中。

要告诉解码器对图像进行子采样,将较小的版本加载到内存中,在BitmapFactory中将inSampleSize设置为true。选择对象。例如,使用inSampleSize为4进行解码的分辨率为2048x1536的图像将生成大约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的幂 最后的值,舍入到最接近的2的幂,按照 inSampleSize文档。

要使用此方法,首先将inJustDecodeBounds设置为true进行解码,传递选项,然后使用新的inSampleSizevalue和injustdecodeboundsset设置为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);
}

这个方法可以很容易地将任意大小的位图加载到显示100x100像素缩略图的ImageView中,如下面的示例代码所示:

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

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