安卓:内存耗尽错误

4
当我将我的Android应用最小化4到5次左右时,总是会出现以下错误:
02-01 19:24:11.980: E/dalvikvm-heap(22362): Out of memory on a 3686416-byte allocation.
02-01 19:24:12.000: E/dalvikvm(22362): Out of memory: Heap Size=62755KB, Allocated=55237KB, Limit=65536KB
02-01 19:24:12.000: E/dalvikvm(22362): Extra info: Footprint=62435KB, Allowed Footprint=62755KB, Trimmed=2144KB
02-01 19:24:12.000: E/Bitmap_JNI(22362): Create Bitmap Failed.    
02-01 19:24:12.000: E/Bitmap_JNI(22362): Failed to create SkBitmap!
02-01 19:24:12.000: E/AndroidRuntime(22362): FATAL EXCEPTION: main
02-01 19:24:12.000: E/AndroidRuntime(22362): java.lang.OutOfMemoryError: (Heap Size=62755KB, Allocated=55237KB)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at android.graphics.Bitmap.nativeCreateScaledBitmap(Native Method)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:744)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at de.vauge.mb.Utils.getResizedBitmap(Utils.java:56)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at de.vauge.mb.MenuView.initialize(MenuView.java:74)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at de.vauge.mb.MenuView$1.handleMessage(MenuView.java:137)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at android.os.Handler.dispatchMessage(Handler.java:99)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at android.os.Looper.loop(Looper.java:156)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at android.app.ActivityThread.main(ActivityThread.java:5045)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at java.lang.reflect.Method.invokeNative(Native Method)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at java.lang.reflect.Method.invoke(Method.java:511)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
02-01 19:24:12.000: E/AndroidRuntime(22362):    at dalvik.system.NativeStart.main(Native Method)

我的应用程序只包含一个活动,其中有7个不同的自编写视图(它们都包含一些位图),在它们不需要时切换为不可见状态(可能不是很好的风格,但是对我来说可以工作...)。这些视图中的每一个都有一个destroy()函数,可以回收其中使用的所有位图,并且MainActivity的onDestroy()调用所有这些destroy()函数。此外,我没有使用任何静态位图。

那么,除了回收所有位图并不使用静态位图之外,还有什么其他方法可以尝试吗?

4个回答

10

嗯,Android上的位图可以有些棘手。您能否提供有关位图来源和大小的更好信息?

否则,我建议您查看以下内容:

  1. 如果您正在加载远程图像,请查看Fresco。您也可以查看Picasso。我个人曾经喜欢过ImageLoader,但不再维护。

  2. 如果您正在使用以前推荐的inPurgable标志,请尝试找到避免使用它的方法,因为它实际上会为每个图像分配更多内存。

  3. 如果你频繁地解码小型本地资源,请考虑将您的可绘制对象保存在哈希映射中,并在需要时重用它们。这样可减少GC。

  4. 如果您想要对Application进行子类化,可以使用OnLowMemory回调来知道何时可能真正需要清理(大多数情况下只用于调试,而非现实生活中的情况)... 如果那还不算太晚的话... :)

  5. 查看Chris Banes的博客。 这是一个非常有趣的内存缓存解决方案

  6. 实现一个内存修剪器,您可以在需要和可能的情况下调用它。

  7. 另一个意料之中的优化是尽可能使用较小的对象...考虑您的最小数据模型和图像大小,并尝试为其提供符合要求的API。


1

对于 Ben Max 评论中的 #3,我创建了两个有用的类:

public abstract class SoftReferenceStorage<K, V>{
private static HashMap<Object, SoftReference<Object>> objectsHash = new HashMap<Object, SoftReference<Object>>();

@SuppressWarnings("unchecked")
public V get(K key) {
    if (objectsHash.containsKey(key)) {
        SoftReference<Object> ref = objectsHash.get(key);
        if (ref.get() == null) {
            objectsHash.put(key, new SoftReference<Object>(createValueForKey(key)));
            return (V)objectsHash.get(key).get();
        } else {
            return (V)ref.get();
        }
    } else {
        objectsHash.put(key, new SoftReference<Object>(createValueForKey(key)));
        return (V)objectsHash.get(key).get();
    }
}

protected abstract V createValueForKey(K key);
}

并且

public class FrequentlyUsedBitmapResources extends SoftReferenceStorage<Integer, Bitmap>{
private static FrequentlyUsedBitmapResources instance = null;

private Resources resources;

public FrequentlyUsedBitmapResources(Resources resources) {
    super();
    this.resources = resources;
}

public static FrequentlyUsedBitmapResources getInstance() {
    if (instance == null) {
        instance = new FrequentlyUsedBitmapResources(HiDriveApp.getContext().getResources());
    }
    return instance;
}

@Override
protected Bitmap createValueForKey(Integer resId) {
    return BitmapFactory.decodeResource(resources, resId);
}
}

可以这样使用:
Bitmap b = FrequentlyUsedBitmapResources.getInstance().get(R.drawable.overview_photo_placeholder);

0

确保在onCreate()中加载它们,而不是在onStart()onResume()中加载。听起来像是每次恢复时都重新加载了它们,但它们没有被销毁,因为当你最小化应用程序时,onDestroy()没有被调用。


嗯...它们是在onCreate()中加载的。我刚意识到,最小化它们根本不是问题。只有当我锁定屏幕时,应用程序似乎被销毁或至少调用了onDestroy(),当我多次锁定和解锁手机时,错误就会发生...有什么想法吗? - vauge
你确定在锁屏时会调用 onDestroy() 吗?不管怎样,如果你只是在 onCreate() 中创建它们,这没关系。我能想到的唯一其他的事情就是打印一个日志,对每个位图调用 isRecycled() 来确保它们实际上已被回收。 - Tim

0

如果图像位于本地设备上(即与您的代码一起合并或来自用户图像库),那么我可能选择不仅仅让它们消失或不可见,而是从磁盘上动态加载它们。事实证明,所有这些设备基本上都是基于闪存的,与磁盘相比速度极快。很可能,用户无法感受到图像的磁盘IO性能损失。

这种方式还可以限制您在任何时候在内存中保存的图像数量。

我同意您应该查看Tim的评估。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接