summaryrefslogtreecommitdiffstats
path: root/framesequence
diff options
context:
space:
mode:
authorChris Craik <ccraik@google.com>2014-04-15 18:04:58 -0700
committerChris Craik <ccraik@google.com>2014-04-15 18:29:21 -0700
commit3105099a73d4fea3408ea0cf6b358fff77dc8b67 (patch)
treee675f1be3e18ab10c6d85b4c9d1ba0aebafb23d6 /framesequence
parent9d34bc31927f47e91ba85980d4d146593cbbe1a8 (diff)
downloadandroid_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.java84
-rw-r--r--framesequence/src/android/support/rastermill/FrameSequenceDrawable.java127
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;