这将是一个漫长的过程。我周末的大部分时间都在尝试不同的方法,并且正在努力帮助其他人解决类似的问题。我没有希望找到一个解决所有问题的完美解决方案,但也许这里有人会让我感到惊讶。 : - )
我的Android应用中有4 ImageView
代表扑克牌。发出新卡时,Animation
(“ HideAnimation ”)会缩小旧卡。然后,250分钟后onAnimationEnd
,ImageView
中的图片被替换,另一个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不适用。强制下采样会显着降低图像质量。)
我尝试的第一个解决方案是使用Android开发者网站上描述的LruCache
:Caching 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 Play和Amazon上。当前版本1。3。1(2018年3月16日)使用上述“位图数组”解决方案,因此您可以尝试激发OOME。较旧的版本使用setImageResource
方法,该方法从未崩溃。如果我找不到更好的解决方案,我希望明天(2018年3月19日)发布更新,恢复旧的,稳定的方法。