1.xml 文件定義 animation-list
2.java 文件設置 AnimationDrawable
# [缺點]
- 系統會把每一幀圖片讀取到內存中
- 當圖片不少且每張都很大的狀況下,容易出現卡頓,甚至 OOM
複製代碼
解決問題的關鍵在於避免一次性讀取全部圖片java
[方案] 在每一幀繪製以前,才加載圖片到內存中,而且釋放前一幀圖片的資源
複製代碼
inPreferredConfig
設置顏色模式,不帶透明度的 RGB_565 內存只有默認的 ARGB_8888 的一半inSampleSize
根據顯示控件的大小對圖像採樣,返回較小的圖像以節省內存經過以上處理,能夠實現幀動畫的流暢播放android
經過 Android Profiler,看到頻繁的 IO 操做(每讀取一張圖片的同時釋放一張圖片)致使內存劇烈抖動。內存頻繁的分配和回收容易產生內存碎片,存在 OOM 的風險,頻繁的 GC 也容易致使UI卡頓。緩存
inMutable
設置解碼獲得的 bitmap 可變inBitmap
複用前一幀圖片,避免內存抖動(效果以下圖)暫時處理了內存問題後繼續思考,頻繁的 IO 也會致使 CPU 的使用率高bash
在某華爲榮耀9手機上,測試簡單頁面播放 sd 卡里某一幀動畫循環播放的 CPU 狀況ide
App 中循環或屢次播放的幀動畫大部分狀況是局部的小圖(什麼地方須要無限播放全屏的幀動畫呢?),對這類小圖添加緩存就挺合適的。優化效果以下圖: 測試
另外: 什麼業務場景須要幀動畫無限循環播放呢?用戶會盯着手機上的某個動畫多長時間?是否能夠針對大部分狀況,設置一個上限,播放 n 次以後就中止動畫,只保留最後一幀的畫面優化
Android 提供了 LruCache,根據最近最少使用優先清理的原則緩存數據。動畫
public class FrameAnimationCache extends LruCache<String, Bitmap> {
private static int mCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8);
public FrameAnimationCache() {
super(mCacheSize);
}
@Override
protected int sizeOf(@NonNull String key, @NonNull Bitmap value) {
return value.getByteCount();
}
}
複製代碼
對於內存使用不太緊張的 App, 這樣一個緩存就夠用了,圖片緩存最多隻會佔用 mCacheSize 大小的內存。ui
當緩存裏的幀動畫圖片長時間沒有使用,如何釋放?this
SoftReference(軟引用)若是內存空間足夠,垃圾回收器就不會回收它,若是內存空間不足,就會回收這些對象的內存(系統自動幫你回收,不用操心多好)
public class FrameAnimationCache extends LruCache<String, SoftReference<Bitmap>> {
private static int mCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8);
public FrameAnimationCache() {
super(mCacheSize);
}
@Override
protected int sizeOf(@NonNull String key, @NonNull SoftReference<Bitmap> value) {
if (value.get() != null) {
return value.get().getByteCount();
} else {
return 0;
}
}
}
複製代碼
當 GC 自動回收 SoftReference,會致使緩存的 sizeOf 計算出錯,日誌裏可能看到這樣的警告
W/System.err: java.lang.IllegalStateException: xxx.xxxAnimationCache.sizeOf() is reporting inconsistent results!
W/System.err: at android.support.v4.util.LruCache.trimToSize(LruCache.java:167)
W/System.err: at android.support.v4.util.LruCache.put(LruCache.java:150)
複製代碼
假如咱們經過 get(K key) 獲取的以前已緩存過的 Bitmap 軟引用,而剛好它已被 GC 回收,那麼返回 null,須要從新解碼圖片,調用 put(K key, V value) 緩存起來。
public final V put(@NonNull K key, @NonNull V value) {
if (key != null && value != null) {
Object previous;
synchronized(this) {
++this.putCount;
this.size += this.safeSizeOf(key, value);
previous = this.map.put(key, value);
if (previous != null) {
this.size -= this.safeSizeOf(key, previous);
}
}
if (previous != null) {
this.entryRemoved(false, key, previous, value);
}
this.trimToSize(this.maxSize);
return previous;
} else {
throw new NullPointerException("key == null || value == null");
}
}
複製代碼
public void trimToSize(int maxSize) {
while(true) {
Object key;
Object value;
synchronized(this) {
if (this.size < 0 || this.map.isEmpty() && this.size != 0) {
throw new IllegalStateException(this.getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
if (this.size <= maxSize || this.map.isEmpty()) {
return;
}
Entry<K, V> toEvict = (Entry)this.map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
this.map.remove(key);
this.size -= this.safeSizeOf(key, value);
++this.evictionCount;
}
this.entryRemoved(true, key, value, (Object)null);
}
}
複製代碼
問題已知 —— 數據回收致使大小計算出錯,那麼解決這個問題就能夠了。
ReferenceQueue
而我用了以下方式
public class FrameAnimationCache extends LruCache<String, SizeSoftReferenceBitmap> {
private static int mCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8);
public FrameAnimationCache() {
super(mCacheSize);
}
@Override
protected int sizeOf(@NonNull String key, @NonNull SizeSoftReferenceBitmap value) {
return value.getSize();
}
}
private class SizeSoftReferenceBitmap {
private SoftReference<Bitmap> mBitmap;
private int mSize;
private SizeSoftReferenceBitmap(SoftReference<Bitmap> bitmap, int size) {
mBitmap = bitmap;
mSize = size;
}
private int getSize() {
return mSize;
}
private SoftReference<Bitmap> getBitmap() {
return mBitmap;
}
}
public Bitmap getBitmapFromCache(String key) {
SizeSoftReferenceBitmap value = mFrameAnimationCache.get(key);
return value != null && value.getBitmap() != null ? value.getBitmap().get() : null;
}
public void addBitmapToCache(String key, Bitmap value) {
mFrameAnimationCache.put(key, new SizeSoftReferenceBitmap(new SoftReference<>(value), value.getByteCount()));
}
複製代碼
用一個 SizeSoftReferenceBitmap 類,作了簡單的對象組合,在建立緩存的時候提早存下 size。