通过缓存位图

时间:2018-03-18 17:38:38

标签: android memory bitmap imageview out-of-memory

简介

这将是一个漫长的过程。我周末的大部分时间都在尝试不同的方法,并且正在努力帮助其他人解决类似的问题。我没有希望找到一个解决所有问题的完美解决方案,但也许这里有人会让我感到惊讶。 : - )

上下文

我的Android应用中有4 ImageView代表扑克牌。发出新卡时,Animation(“ HideAnimation ”)会缩小旧卡。然后,250分钟后onAnimationEndImageView中的图片被替换,另一个Animation(“ ShowAnimation ”)将其放大,看起来像卡片翻转周围。
这些卡是从Android drawable文件夹中的.png文件加载的。


这显示了两种动画样式。一个翻过甲板,另一个直接改变。

问题

我的第一个实现直接在UI线程中使用了setImageResource

ImageView card = findViewById(R.id.cardA);
[…]
public void onAnimationEnd(Animation animation) {
    […]
    card.setImageResource(R.drawable.deck);
    […]
}

这很有效,但从未崩溃,但你可以清楚地看到它对资源进行解码所造成的延迟,动画中存在明显的延迟。
我需要的是事先解码资源,所以我可以在时间快速交换图像,但这是痛苦开始的地方,伴随着可怕的OutOfMemoryError(OOME)。

(旁注:显然,首先要确保图像尺寸合理,它们是:它们的分辨率是根据它们在全高清设备上的尺寸选择的,每张图像为507x720。因此,下采样图像,根据Loading Large Bitmaps Efficiently不适用。强制下采样会显着降低图像质量。)

第一次尝试 - LruCache

我尝试的第一个解决方案是使用Android开发者网站上描述的LruCacheCaching Bitmaps。它使用LinkedHashMap来保留最近引用的对象。但是,此解决方案存在内存问题:

  • 变体1:我将LruCache限制为5位图,因为我必须正好缓存5位图(每张卡一个,加上卡座图像)。这首先工作正常,但在“压力测试”期间导致OutOfMemoryError(快速连续运行动画,例如垃圾邮件“新卡”按钮)。

  • 变式2:我使用了建议的内存限制。但是,限制为1/8可用内存仅足以缓存单个,可能是两个位图,因此滞后仍然存在。将此限制增加到1/4内存可以提供流畅的动画,但在“压力测试”期间偶尔会导致OOME。

(旁注:可能的解决办法可能是显着限制新卡的请求速度。虽然这可能会减少OOME的数量,因为它为垃圾收集器提供了更多的运行时间,但它没有感觉就像是修复,而是一种解决方法,因为底层问题,位图分配,在其他情况下仍可能导致OOME。)

第二次尝试 - 位图数组

我的下一次尝试要简单得多。我只是在一个数组中有5个位图。

Bitmap[] cardCache = new Bitmap[5];

当“ HideAnimation ”开始时,我开始解码位图(cn为卡号1-4,甲板侧为0):

public static void prepareBitmapInMemoryCache(final Context context, final int resourceId, final int cn) {
    Thread t = new Thread(new Runnable() {
        public void run() {
            final BitmapFactory.Options options = new BitmapFactory.Options();
            Bitmap drawable = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
            if(drawable != null) {
                synchronized(cardCache) {
                    cardCache[cn] = drawable;
                }
            }
        }
    });
    t.start();
}

然后,当需要更改图像并启动“ ShowAnimation ”时:

card.setImageBitmap(getBitmapFromMemoryCache(this, resourceId, cn);
[...]
public static Bitmap getBitmapFromMemoryCache(Context context, int resourceId, int cn) {
    synchronized(cardCache) {
        if(cardCache[cn] != null && !cardCache[cn].isRecycled()) {
            return cardCache[cn];
        }
    }
    return getBitmapFromResources(context, resourceId);
}

在“压力测试”期间,这也偶尔会导致OutOfMemoryError decodeResource

(旁注:我也试过缓存Drawable而不是Bitmap而没有任何变化。)

所以,我开始变得有创意。获取位图时,我尝试确保有足够的内存:

private static Bitmap getBitmapFromResources(Context context, int resourceId) {
    int availableMemory = getAvailableMemoryInMB();
    final BitmapFactory.Options options = new BitmapFactory.Options();
    if(availableMemory > 60) options.inSampleSize = 1;
    else options.inSampleSize = 2;
    return BitmapFactory.decodeResource(context.getResources(), resourceId, options);
}
private static int getAvailableMemoryInMB() {
    final Runtime runtime = Runtime.getRuntime();
    final long usedMemInMB = (runtime.totalMemory() - runtime.freeMemory()) / 1048576L;
    final long maxHeapSizeInMB = runtime.maxMemory() / 1048576L;
    final Long availHeapSizeInMB = maxHeapSizeInMB - usedMemInMB;
    return availHeapSizeInMB.intValue();
}

设置位图时,我尝试在旧位图上主动调用recycle

public static void setCardImage(ImageView card, Bitmap bitmap) {
    Drawable oldDrawable = card.getDrawable();
    Bitmap oldBitmap = null;
    if(oldDrawable != null && oldDrawable instanceof BitmapDrawable) {
        oldBitmap = ((BitmapDrawable) oldDrawable).getBitmap();
    }
    if(drawable != null) card.setImageBitmap(bitmap);
    if(oldBitmap != null && oldBitmap != cardCache[0]) oldBitmap.recycle();
}

但是,唉,OutOfMemoryError仍然偶尔返回(在“压力测试”期间)并且我准备放弃并简单地回到我知道在所有条件下都稳定的setImageResource解决方案。

如果您想亲自查看,该应用已在Google PlayAmazon上。当前版本1。3。1(2018年3月16日)使用上述“位图数组”解决方案,因此您可以尝试激发OOME。较旧的版本使用setImageResource方法,该方法从未崩溃。如果我找不到更好的解决方案,我希望明天(2018年3月19日)发布更新,恢复旧的,稳定的方法。

0 个答案:

没有答案