1. 程式人生 > >Android 幀動畫OOM問題優化

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