Fixes for animationEnd in AnimatedImageDrawable

Bug: 63908092
Test: Manual

If the animation is running on the render thread, post a message (in
JNI) to call the listener.

Use a weak reference in the native listener so that it does not create
a cycle.

Don't add a listener if it's already in the list. Remove the native
listener when the Java listeners are all removed.

Call onAnimationEnd when the animation is stopped manually.

Change-Id: I5dc070089fb1cf399ac3316292592f614f2738f0
This commit is contained in:
Leon Scroggins III
2018-01-26 11:03:54 -05:00
parent ce9bcc4977
commit beaf5d919f
4 changed files with 85 additions and 24 deletions

View File

@@ -26,10 +26,11 @@
#include <SkPictureRecorder.h>
#include <hwui/AnimatedImageDrawable.h>
#include <hwui/Canvas.h>
#include <utils/Looper.h>
using namespace android;
static jmethodID gAnimatedImageDrawable_postOnAnimationEndMethodID;
static jmethodID gAnimatedImageDrawable_onAnimationEndMethodID;
// Note: jpostProcess holds a handle to the ImageDecoder.
static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
@@ -123,9 +124,9 @@ static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlo
return drawable->start();
}
static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
static jboolean AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
drawable->stop();
return drawable->stop();
}
// Java's LOOP_INFINITE relies on this being the same.
@@ -137,33 +138,63 @@ static void AnimatedImageDrawable_nSetLoopCount(JNIEnv* env, jobject /*clazz*/,
drawable->setRepetitionCount(loopCount);
}
class JniAnimationEndListener : public OnAnimationEndListener {
class InvokeListener : public MessageHandler {
public:
JniAnimationEndListener(JNIEnv* env, jobject javaObject) {
InvokeListener(JNIEnv* env, jobject javaObject) {
LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJvm) != JNI_OK);
mJavaObject = env->NewGlobalRef(javaObject);
// Hold a weak reference to break a cycle that would prevent GC.
mWeakRef = env->NewWeakGlobalRef(javaObject);
}
~JniAnimationEndListener() override {
~InvokeListener() override {
auto* env = get_env_or_die(mJvm);
env->DeleteGlobalRef(mJavaObject);
env->DeleteWeakGlobalRef(mWeakRef);
}
void onAnimationEnd() override {
virtual void handleMessage(const Message&) override {
auto* env = get_env_or_die(mJvm);
env->CallVoidMethod(mJavaObject, gAnimatedImageDrawable_postOnAnimationEndMethodID);
jobject localRef = env->NewLocalRef(mWeakRef);
if (localRef) {
env->CallVoidMethod(localRef, gAnimatedImageDrawable_onAnimationEndMethodID);
}
}
private:
JavaVM* mJvm;
jobject mJavaObject;
jweak mWeakRef;
};
class JniAnimationEndListener : public OnAnimationEndListener {
public:
JniAnimationEndListener(sp<Looper>&& looper, JNIEnv* env, jobject javaObject) {
mListener = new InvokeListener(env, javaObject);
mLooper = std::move(looper);
}
void onAnimationEnd() override { mLooper->sendMessage(mListener, 0); }
private:
sp<InvokeListener> mListener;
sp<Looper> mLooper;
};
static void AnimatedImageDrawable_nSetOnAnimationEndListener(JNIEnv* env, jobject /*clazz*/,
jlong nativePtr, jobject jdrawable) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
drawable->setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener>(
new JniAnimationEndListener(env, jdrawable)));
if (!jdrawable) {
drawable->setOnAnimationEndListener(nullptr);
} else {
sp<Looper> looper = Looper::getForThread();
if (!looper.get()) {
doThrowISE(env,
"Must set AnimatedImageDrawable's AnimationCallback on a thread with a "
"looper!");
return;
}
drawable->setOnAnimationEndListener(
std::make_unique<JniAnimationEndListener>(std::move(looper), env, jdrawable));
}
}
static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
@@ -186,7 +217,7 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
{ "nSetColorFilter", "(JJ)V", (void*) AnimatedImageDrawable_nSetColorFilter },
{ "nIsRunning", "(J)Z", (void*) AnimatedImageDrawable_nIsRunning },
{ "nStart", "(J)Z", (void*) AnimatedImageDrawable_nStart },
{ "nStop", "(J)V", (void*) AnimatedImageDrawable_nStop },
{ "nStop", "(J)Z", (void*) AnimatedImageDrawable_nStop },
{ "nSetLoopCount", "(JI)V", (void*) AnimatedImageDrawable_nSetLoopCount },
{ "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener },
{ "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize },
@@ -195,7 +226,7 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
jclass animatedImageDrawable_class = FindClassOrDie(env, "android/graphics/drawable/AnimatedImageDrawable");
gAnimatedImageDrawable_postOnAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "postOnAnimationEnd", "()V");
gAnimatedImageDrawable_onAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "onAnimationEnd", "()V");
return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable",
gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods));

View File

@@ -348,7 +348,9 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
if (mState == null) {
throw new IllegalStateException("called stop on empty AnimatedImageDrawable");
}
nStop(mState.mNativePtr);
if (nStop(mState.mNativePtr)) {
postOnAnimationEnd();
}
}
// Animatable2 overrides
@@ -365,21 +367,31 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
nSetOnAnimationEndListener(mState.mNativePtr, this);
}
mAnimationCallbacks.add(callback);
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
}
@Override
public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
if (callback == null || mAnimationCallbacks == null) {
if (callback == null || mAnimationCallbacks == null
|| !mAnimationCallbacks.remove(callback)) {
return false;
}
return mAnimationCallbacks.remove(callback);
if (mAnimationCallbacks.isEmpty()) {
clearAnimationCallbacks();
}
return true;
}
@Override
public void clearAnimationCallbacks() {
mAnimationCallbacks = null;
if (mAnimationCallbacks != null) {
mAnimationCallbacks = null;
nSetOnAnimationEndListener(mState.mNativePtr, null);
}
}
private void postOnAnimationStart() {
@@ -413,6 +425,21 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
return mHandler;
}
/**
* Called by JNI.
*
* The JNI code has already posted this to the thread that created the
* callback, so no need to post.
*/
@SuppressWarnings("unused")
private void onAnimationEnd() {
if (mAnimationCallbacks != null) {
for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
callback.onAnimationEnd(this);
}
}
}
private static native long nCreate(long nativeImageDecoder,
@Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
@@ -432,7 +459,7 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
@FastNative
private static native boolean nStart(long nativePtr);
@FastNative
private static native void nStop(long nativePtr);
private static native boolean nStop(long nativePtr);
@FastNative
private static native void nSetLoopCount(long nativePtr, int loopCount);
// Pass the drawable down to native so it can call onAnimationEnd.

View File

@@ -48,8 +48,10 @@ bool AnimatedImageDrawable::start() {
return true;
}
void AnimatedImageDrawable::stop() {
bool AnimatedImageDrawable::stop() {
bool wasRunning = mRunning;
mRunning = false;
return wasRunning;
}
bool AnimatedImageDrawable::isRunning() {
@@ -180,7 +182,6 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
if (finalFrame) {
if (mEndListener) {
mEndListener->onAnimationEnd();
mEndListener = nullptr;
}
}
}

View File

@@ -68,7 +68,9 @@ public:
// Returns true if the animation was started; false otherwise (e.g. it was
// already running)
bool start();
void stop();
// Returns true if the animation was stopped; false otherwise (e.g. it was
// already stopped)
bool stop();
bool isRunning();
void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); }