1. 程式人生 > >Android 用surfaceview模擬幀動畫的效果,解決幀動畫的OOM問題

Android 用surfaceview模擬幀動畫的效果,解決幀動畫的OOM問題

最近做的專案,客戶臨時要求改版,我真的是最煩這個,要求跟換主頁面的背景,換上新的背景圖,要求是動態的。

效果(我隨便拿的五個圖片做的gif):


方案:

幀動畫方案:

        缺點:1.好像只能imageview才能播放幀動畫 

                2.容易OOM(播三四張還行,播九十張以上,且,每張都在300k左右就有OOM問題)

                3.看到網上方案解決幀動畫OOM,是一次播放十張,再往裡面添十張,再播放。問題就有了,動畫有一個                      新增十個圖片的時間卡頓。

surfaceview方案:

         因為imageview要是以一張一張換,效能必定不如surfaceview。核心應該是

      canvas = surfaceHolder.lockCanvas(rect);
      canvas.drawBitmap(lruCache.get(folderName + "/" + assets[position]), null, rect, null);
        我把上百張的圖片放在assets資料夾下,然後讀取圖片(這邊可以做Lrucache優化),下邊貼出我的程式碼:
        surfaceHolder = sv_main.getHolder();
        lruCache = new StringBitmapLruCache();
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {

            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {


                rect = new Rect(0, 0, width, height);

                //獲取主題
                SharedPreferences sp = getSharedPreferences("setting", Context.MODE_PRIVATE);
                String theme = sp.getString("theme", "bgone");
                switch (theme) {
                    case "bgone":
                        folderName = "bgone";
                        break;
                    case "bgtwo":
                        folderName = "bgtwo";
                        break;
                    case "bgthree":
                        folderName = "bgthree";
                        break;
                }

                try {
                    assetManager = getAssets();
                    assets = assetManager.list(folderName);
                    totalCount = assets.length;
                    if (lruCache.get(folderName + "/" + assets[0]) == null) {
                        Bitmap bitmap = BitmapFactory.decodeStream(assetManager.open(folderName + "/" + assets[0]));
                        lruCache.put(folderName + "/" + assets[0], bitmap);
                    }
                    canvas = surfaceHolder.lockCanvas(rect);
                    canvas.drawBitmap(lruCache.get(folderName + "/" + assets[0]), null, rect, null);
                    holder.unlockCanvasAndPost(canvas);

                    if (!isFlag) {
                        startDecodeThread();
                        isFlag = true;
                    }


                } catch (IOException e) {
                    e.printStackTrace();
                }

                isFlag = true;


            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                RxUtils.unsubscribe(animationSub);
            }
        });

 private void startDecodeThread() {

        RxUtils.unsubscribe(animationSub);
        animationSub = Observable.interval(200, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.io()).subscribe(new Subscriber<Long>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(Long aLong) {
                Logger.i("DDSSK::" + position);
                if (position >= totalCount) {
                    position = 0;
                }

                canvas = surfaceHolder.lockCanvas(rect);
                // if (canvas != null) {

                canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                if (lruCache.get(folderName + "/" + assets[position]) == null) {
                    try {
                        Bitmap bitmap = BitmapFactory.decodeStream(assetManager.open(folderName + "/" + assets[position]));
                        lruCache.put(folderName + "/" + assets[position], bitmap);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                canvas.drawBitmap(lruCache.get(folderName + "/" + assets[position]), null, rect, null);
                surfaceHolder.unlockCanvasAndPost(canvas);


              /*  try {
                    Bitmap bitmap = BitmapFactory.decodeStream(assetManager.open(folderName + "/" + assets[0]));
                    canvas.drawBitmap(bitmap, null, rect, null);
                    surfaceHolder.unlockCanvasAndPost(canvas);

                } catch (IOException e) {
                    e.printStackTrace();
                }*/

                position++;
            }
        });
    }

好,下面講一下我遇到的坑

1.surfaceview準備的時候,介面是一片黑。所以在surfacechanged的時候我要展示動畫的第一張圖,這樣就不黑了

2.canvas為空?因為canvas是從surfaceview獲得的,如果surfaceview沒有準備好,它絕對為空

3.如果介面跳轉再返回,surfaceview還會建立,但是我點了黑屏再亮屏,surfaceview就不會在建立,動畫就播不了,所以要寫一個開關,在onresume裡寫程式碼觸發動畫

4.surfaceview的優化,因為播放太多的圖片,佔用太多資源,導致其他的元件超卡。因為surfaceview可以在子執行緒跟新UI,所以我們可以起執行緒輪播圖

附件:

package utils;

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

/**
 * Created by Administrator on 2017/6/23 0023.
 */

public class StringBitmapLruCache extends LruCache<String, Bitmap> {
    public StringBitmapLruCache() {
        // 構造方法傳入當前應用可用最大記憶體的八分之一
        super((int) (Runtime.getRuntime().maxMemory() / 1024 / 8));
    }

    @Override
    // 重寫sizeOf方法,並計算返回每個Bitmap物件佔用的記憶體
    protected int sizeOf(String key, Bitmap value) {
        return value.getByteCount() / 1024;
    }

    @Override
    // 當快取被移除時呼叫,第一個引數是表明快取移除的原因,true表示被LruCache移除,false表示被主動remove移除,可不重寫
    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap
            newValue) {
        super.entryRemoved(evicted, key, oldValue, newValue);
    }

    @Override
    // 當get方法獲取不到快取的時候呼叫,如果需要建立自定義預設快取,可以在這裡新增邏輯,可不重寫
    protected Bitmap create(String key) {
        return super.create(key);
    }
}

結束語:雖然這個需求是實現了,但造成了另外一個問題,apk過大,因為播放一個主題,就是至少90圖,每張300k。我要實現三個主題,那就是五六十M。於是我就在想,當初要是弄成視訊檔案,我播放視屏的話,既解決了OOM也解決了APK過大的問題。