ビットマップを再利用して、高解像度のシーケンスフレームアニメーションを実現します

多数のフレームを含む高解像度のフレームアニメーションを再生する場合、AnimationDrawableは膨張時にすべてのアニメーション画像を一度にロードするため、AnimationDrawableを直接OOMに使用するのは簡単です。
したがって、別のアプローチを取り、可変ビットマップを使用します。アニメーション画像をレンダリングするたびに、画像をBimapにロードしてから、ビットマップをSurfaceViewにレンダリングし、アニメーションまで1000 / FPSミリ秒ごとに次のアニメーション画像にループします。終了しました。

コードの実装は次のとおりです(手元のボードのパフォーマンスは弱く、20 FPSにしか到達できません。または、複数の可変ビットマップと複数の対応するスレッドを使用して、異なるアニメーション画像を同時にロードし、SurfaceViewに同時にレンダリングしてFPSを向上させます)。

import android.app.Activity;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Bundle;
import android.os.Process;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

public abstract class AnimationActivity extends Activity implements Runnable, AnimationListener,
        SurfaceHolder.Callback {
    
    
    private static final boolean DEBUG = false;
    private static final float DRAW_FPS = 20f;
    private static final long DRAW_PERIOD = (long) (1000 / DRAW_FPS);

    protected final String mTag = getClass().getSimpleName();

    private SurfaceView mSurfaceView;
    private Bitmap mReusableBitmap;

    private ScheduledExecutorService mScheduledExecutor;
    private ScheduledFuture<?> mScheduledFuture;

    private boolean mIsBoot;
    private int[] mDrawableIds;
    private int mDrawableIndex;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        Log.v(mTag, "onCreate");
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
    
    
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
        } else {
    
    
            getWindow().getDecorView().setSystemUiVisibility(
                    /**5894**/View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
        }

        mSurfaceView = new SurfaceView(this);
        mSurfaceView.getHolder().setFormat(PixelFormat.RGBA_8888);
        mSurfaceView.getHolder().addCallback(this);
        setContentView(mSurfaceView);
        initAnimationDrawables();
        AnimationController.getInstance().addAnimationListener(this);
    }

    protected abstract int getAnimationDrawableArrayId(boolean isBoot);

    private void initAnimationDrawables() {
    
    
        int drawableArrayId = getAnimationDrawableArrayId(mIsBoot);
        TypedArray ar = getResources().obtainTypedArray(drawableArrayId);
        int len = ar.length();
        mDrawableIds = new int[len];
        for (int i = 0; i < len; i++) {
    
    
            int id = ar.getResourceId(i, 0);
            mDrawableIds[i] = id;
        }
        ar.recycle();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
    
    
        Log.v(mTag, "surfaceCreated");
        AnimationController.getInstance().notifyReadyToPlay(this);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    
    
        Log.v(mTag, "surfaceChanged");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    
    
        Log.v(mTag, "surfaceDestroyed");
        stopDraw();
    }

    private long mLastTime = 0;
    private final StringBuilder mDrawInterval = DEBUG ? new StringBuilder() : null;
    private final StringBuilder mDecodeTime = DEBUG ? new StringBuilder() : null;

    @Override
    public void run() {
    
    
        if (DEBUG) {
    
    
            long curTime = System.currentTimeMillis();
            if (mDrawableIndex == 0) {
    
    
                mDrawInterval.delete(0, mDrawInterval.length());
                mDecodeTime.delete(0, mDecodeTime.length());
            }
            mDrawInterval.append(curTime - mLastTime).append(' ');
            mLastTime = curTime;
        }
        long startTime = DEBUG ? System.currentTimeMillis() : 0;
        mReusableBitmap = decodeSampledBitmapFromResources(getResources(),
                mDrawableIds[mDrawableIndex++], mReusableBitmap);
        if (DEBUG) {
    
    
            mDecodeTime.append(System.currentTimeMillis() - startTime).append(' ');
        }
        Canvas canvas = mSurfaceView.getHolder().lockCanvas();
        if (canvas != null) {
    
    
            try {
    
    
                canvas.drawBitmap(mReusableBitmap, 0, 0, null);
            } catch (RuntimeException e) {
    
    
                Log.e(mTag, "drawBitmap: " + e);
            } finally {
    
    
                mSurfaceView.getHolder().unlockCanvasAndPost(canvas);
            }
        }
        if (mDrawableIndex == mDrawableIds.length) {
    
    
            if (DEBUG) {
    
    
                Log.w(mTag, mDrawInterval.toString());
                Log.w(mTag, mDecodeTime.toString());
            }
            stopAnimation(false);
        }
    }

    @Override
    protected void onStart() {
    
    
        super.onStart();
        Log.v(mTag, "onStart");
    }

    @Override
    protected void onPause() {
    
    
        super.onPause();
        Log.v(mTag, "onPause");
    }

    @Override
    protected void onDestroy() {
    
    
        Log.v(mTag, "onDestroy");
        super.onDestroy();
        if (mScheduledExecutor != null) {
    
    
            mScheduledExecutor.shutdownNow();
        }
        AnimationController.getInstance().removeAnimationListener(this);
    }

    private void startDraw() {
    
    
        Log.d(mTag, "startDraw");
        mDrawableIndex = 0;
        mScheduledExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
    
    
            @Override
            public Thread newThread(Runnable r) {
    
    
                return new Thread(r, "DRAW-THREAD@" + mTag) {
    
    
                    @Override
                    public void run() {
    
    
                        Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
                        super.run();
                    }
                };
            }
        });
        mScheduledFuture = mScheduledExecutor.scheduleAtFixedRate(this, 0, DRAW_PERIOD,
                TimeUnit.MILLISECONDS);
    }

    private void stopDraw() {
    
    
        Log.d(mTag, "stopDraw");
        if (mScheduledFuture == null) {
    
    
            Log.w(mTag, "mScheduledFuture is null");
            return;
        }
        mScheduledFuture.cancel(false);
        mScheduledFuture = null;
    }

    @Override
    public void startAnimation() {
    
    
        Log.d(mTag, "startAnimation");
        startDraw();
    }

    @Override
    public void stopAnimation(boolean withNewAnimation) {
    
    
        Log.d(mTag, "stopAnimation " + withNewAnimation);
        stopDraw();
        finish();
    }

    @Override
    public void finish() {
    
    
        super.finish();
        Log.v(mTag, "finish");
    }

    /**
     * Decode a bitmap from resources.
     *
     * @param res       The resources to get drawable
     * @param id        The drawable resource id
     * @param candidate The candidate bitmap to reuse
     * @return A bitmap with the same dimensions that are equal to the candidate's width and height
     */
    public static Bitmap decodeSampledBitmapFromResources(Resources res, int id, Bitmap candidate) {
    
    
        if (candidate == null || candidate.isRecycled()) {
    
    
            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inMutable = true;
            Bitmap bitmap = BitmapFactory.decodeResource(res, id, options);
            Log.d("Bitmap", "bitmap.getConfig(): " + bitmap.getConfig());
            return bitmap;
        }
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, id, options);
        if (canUseForInBitmap(candidate, options)) {
    
    
            // inBitmap only works with mutable bitmaps so force the decoder to
            // return mutable bitmaps.
            options.inMutable = true;
            options.inBitmap = candidate;
        } else {
    
    
            candidate.recycle();
        }
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, id, options);
    }

    /**
     * @param candidate Bitmap to check
     * @param options   Options that have the out* value populated
     * @return true if candidate can be used for inBitmap re-use with options
     */
    private static boolean canUseForInBitmap(Bitmap candidate, BitmapFactory.Options options) {
    
    
        // From Android 4.4 (KitKat) onward we can re-use if the byte size of the new bitmap
        // is smaller than the reusable bitmap candidate allocation byte count.
        int byteCount = options.outWidth * options.outHeight
                * getBytesPerPixel(candidate.getConfig());
        return byteCount <= candidate.getAllocationByteCount();
    }

    /**
     * Return the byte usage per pixel of a bitmap based on its configuration.
     *
     * @param config The bitmap configuration.
     * @return The byte usage per pixel.
     */
    private static int getBytesPerPixel(Bitmap.Config config) {
    
    
        if (config == Bitmap.Config.ARGB_8888) {
    
    
            return 4;
        } else if (config == Bitmap.Config.RGB_565) {
    
    
            return 2;
        } else if (config == Bitmap.Config.ARGB_4444) {
    
    
            return 2;
        } else if (config == Bitmap.Config.ALPHA_8) {
    
    
            return 1;
        }
        return 1;
    }
}

array.xmlを使用してアニメーション画像を指定します

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <array name="shutdown_drawables">
        <item>@drawable/shutdown_000</item>
        ...
        <item>@drawable/shutdown_199</item>
    </array>
</resources>

おすすめ

転載: blog.csdn.net/hegan2010/article/details/103992707