diff options
author | Chris Craik <ccraik@google.com> | 2014-04-15 18:04:58 -0700 |
---|---|---|
committer | Chris Craik <ccraik@google.com> | 2014-04-15 18:29:21 -0700 |
commit | 3105099a73d4fea3408ea0cf6b358fff77dc8b67 (patch) | |
tree | e675f1be3e18ab10c6d85b4c9d1ba0aebafb23d6 /framesequence | |
parent | 9d34bc31927f47e91ba85980d4d146593cbbe1a8 (diff) | |
download | android_frameworks_ex-3105099a73d4fea3408ea0cf6b358fff77dc8b67.tar.gz android_frameworks_ex-3105099a73d4fea3408ea0cf6b358fff77dc8b67.tar.bz2 android_frameworks_ex-3105099a73d4fea3408ea0cf6b358fff77dc8b67.zip |
Add Bitmap reuse callbacks to FrameSequenceDrawable
Also fixes decoding thread priority, and now correctly handles
unscheduled swap events.
Change-Id: I8397a390ff5d0cbbd5f2f07ab18973fb821c881d
Diffstat (limited to 'framesequence')
-rw-r--r-- | framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java | 84 | ||||
-rw-r--r-- | framesequence/src/android/support/rastermill/FrameSequenceDrawable.java | 127 |
2 files changed, 176 insertions, 35 deletions
diff --git a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java index ea593dc..71f35e6 100644 --- a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java +++ b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java @@ -16,6 +16,7 @@ package com.android.rastermill.samples; import android.app.Activity; +import android.graphics.Bitmap; import android.os.Bundle; import android.support.rastermill.FrameSequence; import android.support.rastermill.FrameSequenceDrawable; @@ -24,51 +25,96 @@ import android.widget.ImageView; import android.widget.Toast; import java.io.InputStream; +import java.util.HashSet; public class AnimatedGifTest extends Activity { + FrameSequenceDrawable mDrawable; + + // This provider is entirely unnecessary, just here to validate the acquire/release process + private class CheckingProvider implements FrameSequenceDrawable.BitmapProvider { + HashSet<Bitmap> mBitmaps = new HashSet<Bitmap>(); + @Override + public Bitmap acquireBitmap(int minWidth, int minHeight) { + Bitmap bitmap = + Bitmap.createBitmap(minWidth + 1, minHeight + 4, Bitmap.Config.ARGB_8888); + mBitmaps.add(bitmap); + return bitmap; + } + + @Override + public void releaseBitmap(Bitmap bitmap) { + if (!mBitmaps.contains(bitmap)) throw new IllegalStateException(); + mBitmaps.remove(bitmap); + bitmap.recycle(); + } + + public boolean isEmpty() { + return mBitmaps.isEmpty(); + } + } + + final CheckingProvider mProvider = new CheckingProvider(); + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.basic_test_activity); - ImageView imageView = (ImageView) findViewById(R.id.imageview); - - InputStream is = getResources().openRawResource(R.raw.animated); - - FrameSequence fs = FrameSequence.decodeStream(is); - final FrameSequenceDrawable drawable = new FrameSequenceDrawable(fs); - drawable.setOnFinishedListener(new FrameSequenceDrawable.OnFinishedListener() { - @Override - public void onFinished(FrameSequenceDrawable drawable) { - Toast.makeText(getApplicationContext(), - "THE ANIMATION HAS FINISHED", Toast.LENGTH_SHORT).show(); - } - }); - imageView.setImageDrawable(drawable); - findViewById(R.id.start).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - drawable.start(); + mDrawable.start(); } }); findViewById(R.id.stop).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - drawable.stop(); + mDrawable.stop(); } }); findViewById(R.id.vis).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - drawable.setVisible(true, true); + mDrawable.setVisible(true, true); } }); findViewById(R.id.invis).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - drawable.setVisible(false, true); + mDrawable.setVisible(false, true); } }); } + + @Override + protected void onResume() { + super.onResume(); + + ImageView imageView = (ImageView) findViewById(R.id.imageview); + InputStream is = getResources().openRawResource(R.raw.animated); + + FrameSequence fs = FrameSequence.decodeStream(is); + mDrawable = new FrameSequenceDrawable(fs, mProvider); + mDrawable.setOnFinishedListener(new FrameSequenceDrawable.OnFinishedListener() { + @Override + public void onFinished(FrameSequenceDrawable drawable) { + Toast.makeText(getApplicationContext(), + "THE ANIMATION HAS FINISHED", Toast.LENGTH_SHORT).show(); + } + }); + imageView.setImageDrawable(mDrawable); + } + + @Override + protected void onPause() { + super.onPause(); + ImageView imageView = (ImageView) findViewById(R.id.imageview); + + mDrawable.recycle(mProvider); + if (!mProvider.isEmpty()) throw new IllegalStateException("All bitmaps not recycled"); + + mDrawable = null; + imageView.setImageDrawable(null); + + } } diff --git a/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java index f5f1f47..8f26857 100644 --- a/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java +++ b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java @@ -26,6 +26,7 @@ import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.HandlerThread; +import android.os.Process; import android.os.SystemClock; public class FrameSequenceDrawable extends Drawable implements Animatable, Runnable { @@ -36,7 +37,8 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna synchronized (sLock) { if (sDecodingThread != null) return; - sDecodingThread = new HandlerThread("FrameSequence decoding thread"); + sDecodingThread = new HandlerThread("FrameSequence decoding thread", + Process.THREAD_PRIORITY_BACKGROUND); sDecodingThread.start(); sDecodingThreadHandler = new Handler(sDecodingThread.getLooper()); } @@ -52,10 +54,35 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna public abstract void onFinished(FrameSequenceDrawable drawable); } + public static interface BitmapProvider { + /** + * Called by FrameSequenceDrawable to aquire an 8888 Bitmap with minimum dimensions. + */ + public abstract Bitmap acquireBitmap(int minWidth, int minHeight); + + /** + * Called by FrameSequenceDrawable to release a Bitmap it no longer needs. The Bitmap + * will no longer be used at all by the drawable, so it is safe to reuse elsewhere. + */ + public abstract void releaseBitmap(Bitmap bitmap); + } + + private static BitmapProvider sAllocatingBitmapProvider = new BitmapProvider() { + @Override + public Bitmap acquireBitmap(int minWidth, int minHeight) { + return Bitmap.createBitmap(minWidth, minHeight, Bitmap.Config.ARGB_8888); + } + + @Override + public void releaseBitmap(Bitmap bitmap) { + bitmap.recycle(); + } + }; + /** * Register a callback to be invoked when a FrameSequenceDrawable finishes looping. * - * @see setLoopBehavior() + * @see #setLoopBehavior(int) */ public void setOnFinishedListener(OnFinishedListener onFinishedListener) { mOnFinishedListener = onFinishedListener; @@ -94,6 +121,7 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna //Protects the fields below private final Object mLock = new Object(); + private boolean mRecycled = false; private Bitmap mFrontBitmap; private Bitmap mBackBitmap; @@ -107,6 +135,7 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna private int mLoopBehavior = LOOP_DEFAULT; private long mLastSwap; + private long mNextSwap; private int mNextFrameToDecode; private OnFinishedListener mOnFinishedListener; @@ -119,6 +148,8 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna int nextFrame; Bitmap bitmap; synchronized (mLock) { + if (mRecycled) return; + nextFrame = mNextFrameToDecode; if (nextFrame < 0) { return; @@ -131,11 +162,11 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna synchronized (mLock) { if (mNextFrameToDecode < 0 || mState != STATE_DECODING) return; - invalidateTimeMs += mLastSwap; + mNextSwap = invalidateTimeMs + mLastSwap; mState = STATE_WAITING_TO_SWAP; } - scheduleSelf(FrameSequenceDrawable.this, invalidateTimeMs); + scheduleSelf(FrameSequenceDrawable.this, mNextSwap); } }; @@ -148,17 +179,33 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna } }; + private static Bitmap acquireAndValidateBitmap(BitmapProvider bitmapProvider, + int minWidth, int minHeight) { + Bitmap bitmap = bitmapProvider.acquireBitmap(minWidth, minHeight); + + if (bitmap.getWidth() < minWidth + || bitmap.getHeight() < minHeight + || bitmap.getConfig() != Bitmap.Config.ARGB_8888) { + throw new IllegalArgumentException("Invalid bitmap provided"); + } + + return bitmap; + } + public FrameSequenceDrawable(FrameSequence frameSequence) { + this(frameSequence, sAllocatingBitmapProvider); + } + + public FrameSequenceDrawable(FrameSequence frameSequence, BitmapProvider bitmapProvider) { if (frameSequence == null) throw new IllegalArgumentException(); mFrameSequence = frameSequence; mFrameSequenceState = frameSequence.createState(); - // TODO: add callback for requesting bitmaps, to allow for reuse final int width = frameSequence.getWidth(); final int height = frameSequence.getHeight(); - mFrontBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mBackBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mFrontBitmap = acquireAndValidateBitmap(bitmapProvider, width, height); + mBackBitmap = acquireAndValidateBitmap(bitmapProvider, width, height); mSrcRect = new Rect(0, 0, width, height); mPaint = new Paint(); mPaint.setFilterBitmap(true); @@ -170,12 +217,55 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna initializeDecodingThread(); } + private void checkRecycledLocked() { + if (mRecycled) { + throw new IllegalStateException("Cannot perform operation on recycled drawable"); + } + } + + public boolean isRecycled() { + synchronized (mLock) { + return mRecycled; + } + } + + /** + * Marks the drawable as permanently recycled (and thus unusable), and releases any owned + * Bitmaps drawable to the BitmapProvider. + * + * @param bitmapProvider Provider to recieve recycled bitmaps. Must be non-null. + */ + public void recycle(BitmapProvider bitmapProvider) { + if (bitmapProvider == null) { + throw new IllegalStateException("BitmapProvider must be non-null"); + } + + Bitmap bitmapToReleaseA; + Bitmap bitmapToReleaseB; + synchronized (mLock) { + checkRecycledLocked(); + + bitmapToReleaseA = mFrontBitmap; + bitmapToReleaseB = mBackBitmap; + + mFrontBitmap = null; + mBackBitmap = null; + mRecycled = true; + } + + // For simplicity and safety, we don't recycle the state object here + + bitmapProvider.releaseBitmap(bitmapToReleaseA); + bitmapProvider.releaseBitmap(bitmapToReleaseB); + } + @Override protected void finalize() throws Throwable { try { - mFrontBitmap.recycle(); - mBackBitmap.recycle(); mFrameSequenceState.recycle(); + if (!mRecycled) { + recycle(sAllocatingBitmapProvider); + } } finally { super.finalize(); } @@ -184,6 +274,15 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna @Override public void draw(Canvas canvas) { synchronized (mLock) { + checkRecycledLocked(); + if (mState == STATE_WAITING_TO_SWAP) { + // may have failed to schedule mark ready runnable, + // so go ahead and swap if swapping is due + if (mNextSwap - SystemClock.uptimeMillis() <= 0) { + mState = STATE_READY_TO_SWAP; + } + } + if (isRunning() && mState == STATE_READY_TO_SWAP) { // Because draw has occurred, the view system is guaranteed to no longer hold a // reference to the old mFrontBitmap, so we now use it to produce the next frame @@ -197,7 +296,7 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna if (mNextFrameToDecode == mFrameSequence.getFrameCount() - 1) { mCurrentLoop++; if ((mLoopBehavior == LOOP_ONCE && mCurrentLoop == 1) || - (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) { + (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) { continueLooping = false; } } @@ -233,6 +332,7 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna public void start() { if (!isRunning()) { synchronized (mLock) { + checkRecycledLocked(); if (mState == STATE_SCHEDULED) return; // already scheduled mCurrentLoop = 0; scheduleDecodeLocked(); @@ -250,16 +350,11 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna @Override public boolean isRunning() { synchronized (mLock) { - return mNextFrameToDecode > -1; + return mNextFrameToDecode > -1 && !mRecycled; } } @Override - public void scheduleSelf(Runnable what, long when) { - super.scheduleSelf(what, when); - } - - @Override public void unscheduleSelf(Runnable what) { synchronized (mLock) { mNextFrameToDecode = -1; |