Android 幀動畫OOM問題優化
轉載請註明出處,謝謝
- 普通實現
實現一個幀動畫,最先想到的就是用animation-list將全部圖片按順序放入,並設定時間間隔和播放模式。然後將該drawable設定給ImageView或Progressbar就OK了。
首先建立幀動畫資原始檔drawable/anim.xml,oneshot=false為迴圈播放模式,ture為單次播放;duration為每幀時間間隔,單位毫秒。
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false" >
<item
android:drawable="@drawable/img0"
android:duration="17" />
<item
android:drawable="@drawable/img1"
android:duration="17" />
<item
android:drawable="@drawable/img2"
android:duration="17" />
</animation-list>
然後在程式碼中找到播放該動畫的ImageView,將資源賦給該控制元件就可以控制動畫開始與結束了,是不是超簡單。
AnimationDrawable animationDrawable;
if (imageView.getDrawable() == null) {
imageView.setImageResource(R.drawable.loading_anim);
animationDrawable = (AnimationDrawable) imageView.getDrawable();
}
animationDrawable.start();//開始
animationDrawable.stop();//結束
- OOM問題及優化
. 記憶體溢位咋辦
用普通方法實現幀動畫用到普通場景是沒問題的,如果碰到幾十甚至幾百幀圖片,而且每張圖片幾百K的情況,呵呵。例如,做一個很炫的閃屏幀動畫,要保證高清且動作絲滑,就需要至少幾十張高清圖片。這時,OOM問題就出來了,閃屏進化成了一閃~
別慌,像我這樣的菜鳥遇到問題就會找度娘和gayhub,大神們肯定已經有解決方案了。隨手一搜就在StackOverflow上找到了解決辦法:
http://stackoverflow.com/questions/8692328/causing-outofmemoryerror-in-frame-by-frame-animation-in-android
. 解決思路
先分析下普通方法為啥會OOM,從xml中讀取到圖片id列表後就去硬碟中找這些圖片資源,將圖片全部讀出來後按順序設定給ImageView,利用視覺暫留效果實現了動畫。一次拿出這麼多圖片,而系統都是以Bitmap點陣圖形式讀取的(作為OOM的常客,這鍋Bitmap來背);而動畫的播放是按順序來的,大量Bitmap就排好隊等待播放然後釋放,然而這個排隊的地方只有10平米,呵呵~發現問題了吧。
按照大神的思路,既然來這麼多Bitmap,一次卻只能臨幸一個,那麼就翻牌子吧,輪到誰就派個執行緒去叫誰,bitmap1叫到了得叫上下一位bitmap2做準備,這樣更迭效率高一些。為了避免某個bitmap已被叫走了執行緒白跑一趟的情況,加個Synchronized同步下資料資訊,實現程式碼如下:
public synchronized void start() {
mShouldRun = true;
if (mIsRunning)
return;
Runnable runnable = new Runnable() {
@Override
public void run() {
ImageView imageView = mSoftReferenceImageView.get();
if (!mShouldRun || imageView == null) {
mIsRunning = false;
if (mOnAnimationStoppedListener != null) {
mOnAnimationStoppedListener.AnimationStopped();
}
return;
}
mIsRunning = true;
//新開執行緒去讀下一幀
mHandler.postDelayed(this, mDelayMillis);
if (imageView.isShown()) {
int imageRes = getNext();
if (mBitmap != null) { // so Build.VERSION.SDK_INT >= 11
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeResource(imageView.getResources(), imageRes, mBitmapOptions);
} catch (Exception e) {
e.printStackTrace();
}
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(imageRes);
mBitmap.recycle();
mBitmap = null;
}
} else {
imageView.setImageResource(imageRes);
}
}
}
};
mHandler.post(runnable);
}
. 進一步優化
為了快速讀取SD卡中的圖片資源,這裡用到了Apache的IOUtils。在點陣圖處理上使用了BitmapFactory.Options()相關設定,InBitmap,當圖片大小型別相同時,虛擬機器就對點陣圖進行記憶體複用,不再分配新的記憶體,可以避免不必要的記憶體分配及GC。
if (Build.VERSION.SDK_INT >= 11) {
Bitmap bmp = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
int width = bmp.getWidth();
int height = bmp.getHeight();
Bitmap.Config config = bmp.getConfig();
mBitmap = Bitmap.createBitmap(width, height, config);
mBitmapOptions = new BitmapFactory.Options();
//設定Bitmap記憶體複用
mBitmapOptions.inBitmap = mBitmap;//Bitmap複用記憶體塊
mBitmapOptions.inMutable = true;//解碼時返回可變Bitmap
mBitmapOptions.inSampleSize = 1;//縮放比例
}
- 實現過程
/**
* 將幀動畫資源id以字串陣列形式寫到values/arrays.xml中
* FPS為每秒播放幀數,FPS = 1/T,(T--每幀間隔時間秒)
*/
AnimationsContainer.FramesSequenceAnimation animation
= AnimationsContainer.getInstance(R.array.XXX, FPS).createProgressDialogAnim(imageView);
animation.start();//動畫開始
animation.stop();//動畫結束
注意圖片資源ID需要以String陣列形式放入xml中,然後再利用TypedArray將字串轉為資源ID。如果直接用@drawable/img1這樣的形式放入Int陣列中,是沒法讀取到正真的資源ID的。
從xml中讀取資源ID陣列程式碼:
/**
* 從xml中讀取幀陣列
* @param resId
* @return
*/
private int[] getData(int resId){
TypedArray array = mContext.getResources().obtainTypedArray(resId);
int len = array.length();
int[] intArray = new int[array.length()];
for(int i = 0; i < len; i++){
intArray[i] = array.getResourceId(i, 0);
}
array.recycle();
return intArray;
}
- 實現效果
實現完了當然迫不及待的到真機上跑跑,測下效果。
看圖說話,這麼明顯的坎兒就是應為一次讀取圖片造成的,分分鐘OOM的節奏。再看看優化後的結果:
是不是很養眼,不過最前面剛進介面時的蜜汁GC我還沒弄清除~~
最後奉上動圖效果,是不是非常絲滑酸爽
Demo地址:
https://github.com/VDshixiaoming/AnimationTest