From 72d6e4facb1abd81809fdaddbe42f41ad885189c Mon Sep 17 00:00:00 2001 From: John Reck Date: Fri, 21 Nov 2014 14:10:10 -0800 Subject: [PATCH] It's super critical to call nStart Bug: 18204974 Even if we are canceling or ending an animation nStart() *must* be called, otherwise the native-side listener is not attached (lazy-attached for JNI cyclic reference reasons), and then Animator::callOnFinishedListener() no-ops as there's no listener set Add a lifecycle verifier to ensure that nStart is always called on animators that are attached and get finished Change-Id: Ibc345b5be97b6d3f95a11c361ebe020d030fd3b6 --- .../java/android/view/RenderNodeAnimator.java | 21 ++++++++++++++----- core/jni/android_view_RenderNodeAnimator.cpp | 20 +++++++++++++++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java index b95f9a4cab526..7feca3076a766 100644 --- a/core/java/android/view/RenderNodeAnimator.java +++ b/core/java/android/view/RenderNodeAnimator.java @@ -189,9 +189,6 @@ public class RenderNodeAnimator extends Animator { } private void doStart() { - mState = STATE_RUNNING; - nStart(mNativePtr.get(), this); - // Alpha is a special snowflake that has the canonical value stored // in mTransformationInfo instead of in RenderNode, so we need to update // it with the final value here. @@ -201,7 +198,7 @@ public class RenderNodeAnimator extends Animator { mViewTarget.mTransformationInfo.mAlpha = mFinalValue; } - notifyStartListeners(); + moveToRunningState(); if (mViewTarget != null) { // Kick off a frame to start the process @@ -209,6 +206,12 @@ public class RenderNodeAnimator extends Animator { } } + private void moveToRunningState() { + mState = STATE_RUNNING; + nStart(mNativePtr.get(), this); + notifyStartListeners(); + } + private void notifyStartListeners() { final ArrayList listeners = cloneListeners(); final int numListeners = listeners == null ? 0 : listeners.size(); @@ -222,7 +225,7 @@ public class RenderNodeAnimator extends Animator { if (mState != STATE_PREPARE && mState != STATE_FINISHED) { if (mState == STATE_DELAYED) { getHelper().removeDelayedAnimation(this); - notifyStartListeners(); + moveToRunningState(); } nEnd(mNativePtr.get()); @@ -242,7 +245,15 @@ public class RenderNodeAnimator extends Animator { @Override public void end() { if (mState != STATE_FINISHED) { + if (mState < STATE_RUNNING) { + getHelper().removeDelayedAnimation(this); + doStart(); + } nEnd(mNativePtr.get()); + if (mViewTarget != null) { + // Kick off a frame to flush the state change + mViewTarget.invalidateViewProperty(true, false); + } } } diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp index 84b7913a54628..311882daf1160 100644 --- a/core/jni/android_view_RenderNodeAnimator.cpp +++ b/core/jni/android_view_RenderNodeAnimator.cpp @@ -45,6 +45,15 @@ static JNIEnv* getEnv(JavaVM* vm) { return env; } +class AnimationListenerLifecycleChecker : public AnimationListener { +public: + virtual void onAnimationFinished(BaseRenderNodeAnimator* animator) { + LOG_ALWAYS_FATAL("Lifecycle failure, nStart(%p) wasn't called", animator); + } +}; + +static AnimationListenerLifecycleChecker sLifecycleChecker; + class AnimationListenerBridge : public AnimationListener { public: // This holds a strong reference to a Java WeakReference object. This avoids @@ -100,6 +109,7 @@ static jlong createAnimator(JNIEnv* env, jobject clazz, jint propertyRaw, jfloat finalValue) { RenderPropertyAnimator::RenderProperty property = toRenderProperty(propertyRaw); BaseRenderNodeAnimator* animator = new RenderPropertyAnimator(property, finalValue); + animator->setListener(&sLifecycleChecker); return reinterpret_cast( animator ); } @@ -107,6 +117,7 @@ static jlong createCanvasPropertyFloatAnimator(JNIEnv* env, jobject clazz, jlong canvasPropertyPtr, jfloat finalValue) { CanvasPropertyPrimitive* canvasProperty = reinterpret_cast(canvasPropertyPtr); BaseRenderNodeAnimator* animator = new CanvasPropertyPrimitiveAnimator(canvasProperty, finalValue); + animator->setListener(&sLifecycleChecker); return reinterpret_cast( animator ); } @@ -117,12 +128,14 @@ static jlong createCanvasPropertyPaintAnimator(JNIEnv* env, jobject clazz, CanvasPropertyPaintAnimator::PaintField paintField = toPaintField(paintFieldRaw); BaseRenderNodeAnimator* animator = new CanvasPropertyPaintAnimator( canvasProperty, paintField, finalValue); + animator->setListener(&sLifecycleChecker); return reinterpret_cast( animator ); } static jlong createRevealAnimator(JNIEnv* env, jobject clazz, jint centerX, jint centerY, jfloat startRadius, jfloat endRadius) { BaseRenderNodeAnimator* animator = new RevealAnimator(centerX, centerY, startRadius, endRadius); + animator->setListener(&sLifecycleChecker); return reinterpret_cast( animator ); } @@ -166,9 +179,7 @@ static void setAllowRunningAsync(JNIEnv* env, jobject clazz, jlong animatorPtr, static void start(JNIEnv* env, jobject clazz, jlong animatorPtr, jobject finishListener) { BaseRenderNodeAnimator* animator = reinterpret_cast(animatorPtr); - if (finishListener) { - animator->setListener(new AnimationListenerBridge(env, finishListener)); - } + animator->setListener(new AnimationListenerBridge(env, finishListener)); animator->start(); } @@ -211,6 +222,9 @@ static JNINativeMethod gMethods[] = { LOG_FATAL_IF(! var, "Unable to find method " methodName); int register_android_view_RenderNodeAnimator(JNIEnv* env) { +#ifdef USE_OPENGL_RENDERER + sLifecycleChecker.incStrong(0); +#endif FIND_CLASS(gRenderNodeAnimatorClassInfo.clazz, kClassPathName); gRenderNodeAnimatorClassInfo.clazz = jclass(env->NewGlobalRef(gRenderNodeAnimatorClassInfo.clazz));