如何在 AnimatorListener.onAnimationEnd 中调试和解决 NPE

How to debug and resolve an NPE in AnimatorListener.onAnimationEnd

如果这很容易解决,请原谅。我对调试和解决这样的情况真的很陌生。我真的不确定从哪里开始追踪这个问题并确定所涉及的架构。它偶尔会发生,看起来更像是一种竞争条件,取决于我在上一个动画结束后尝试重新启动动画的速度。非常感谢任何指点并学习一点。

08-08 09:26:01.410 30626-30626/com.myapp.myappname E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.myapp.myappname, PID: 30626
java.lang.NullPointerException: Attempt to invoke interface method 'void android.animation.Animator$AnimatorListener.onAnimationEnd(android.animation.Animator)' on a null object reference
at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1239)
at android.animation.ValueAnimator.cancel(ValueAnimator.java:1140)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:974)
at android.animation.ValueAnimator.animationFrame(ValueAnimator.java:1384)
at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1427)
at android.animation.ValueAnimator$AnimationHandler.doAnimationFrame(ValueAnimator.java:759)
at android.animation.ValueAnimator$AnimationHandler.run(ValueAnimator.java:801)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:920)
at android.view.Choreographer.doCallbacks(Choreographer.java:695)
at android.view.Choreographer.doFrame(Choreographer.java:628)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:906)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7229)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

这里是一些关于导致 NPE 的机制的参考代码。本质上,我有一个开始和停止按钮来注册和注销光传感器。当注册并发生灯光事件时,我允许通过锁一次调用一个并启动 handler/runnable。当动画 ends/completes 释放锁以在灯光事件处理程序中进行另一次执行。

我看到 NPE 偶尔发生的地方是当我在发出先前的 STOP 后按下 START 时。它很少发生,恰好在 STOP 发生和 START 再次发生的边界。

// on startup of app, prepare runnable
runnableAlpha = new Runnable()
{
    @Override
    public void run()
    {
        try
        {
            final float newAlpha = (.10f * (mLastLightValue / (mLightSensor.getMaximumRange() / 100)));
            final ObjectAnimator oa = ObjectAnimator.ofFloat(mMyAppRatingBar, "alpha", mMyAppRatingBarLastAlpha, newAlpha);

            oa.addListener(new AnimatorListener()
            {
                @Override
                public void onAnimationStart(Animator animation)
                {
                }

                @Override
                public void onAnimationEnd(Animator animation)
                {
                    try
                    {
                        oa.addListener(null);
                        oa.addUpdateListener(null);
                        mMyAppRatingBarLastAlpha = newAlpha;
                    }
                    catch (Exception e1)
                    {
                        // do nothing
                    }
                    finally
                    {
                        try
                        {
                            mTweenLock.release();
                        }
                        catch (Exception e1)
                        {
                            // do nothing
                        }
                    }
                }

                @Override
                public void onAnimationCancel(Animator animation)
                {
                    try
                    {
                        oa.addListener(null);
                        oa.addUpdateListener(null);
                        mMyAppRatingBarLastAlpha = newAlpha;
                    }
                    catch (Exception e1)
                    {
                        // do nothing
                    }
                    finally
                    {
                        try
                        {
                            mTweenLock.release();
                        }
                        catch (Exception e1)
                        {
                            // do nothing
                        }
                    }
                }

                @Override
                public void onAnimationRepeat(Animator animation)
                {
                }
            });

            oa.setInterpolator(new DecelerateInterpolator());
            oa.setDuration(1000);
            oa.start();

            try
            {
                if (mAlphaHandler != null)
                {
                    mAlphaHandler.removeCallbacks(runnableAlpha);
                }
            }
            catch (Exception e1)
            {
                // do nothing
            }
            finally
            {
                mAlphaHandler = null;
            }
        }
        catch(Exception e)
        {
            try
            {
                mTweenLock.release();
            }
            catch (Exception e1)
            {
                // do nothing
            }
        }
    }
}


// wire up handler and runnable only if light sensor registered, light event triggered, no current lock(prior light event being handled)
public void onSensorChanged(SensorEvent event)
{
    if(!mTweenLock.tryAcquire())
    {
        return;
    }

    try
    {
        mAlphaHandler = new Handler();
        mLastLightValue = event.values[0];

        mAlphaHandler.postDelayed(runnableAlpha, 0);
    }
    catch(Exception e)
    {
        mAlphaHandler = null;
        mTweenLock.release();
    }
}

// when we press START button in app, fire up the light sensor to begin work
mSensorManager = (SensorManager) mContext.getSystemService(mContext.SENSOR_SERVICE);
mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);

if(mLightSensor == null)
{
    mSensorManager = null;
    return;
}

mSensorManager.unregisterListener(this, mLightSensor);  // clear out prior if existent
mSensorManager.registerListener(this, mLightSensor, SensorManager.SENSOR_DELAY_UI);

// when we press STOP button in app, we force teardown of everything
try
{
    if(mAlphaHandler != null)
    {
        mAlphaHandler.removeCallbacksAndMessages(null);
        mAlphaHandler = null;
        runnableAlpha = null;
    }
}
catch(Exception e1)
{
    // do nothing
}

问题原来是 .onAnimationCancel() 被间歇性地调用,其中 'oa' 被设置为 null 并且 .onAnimationEnd() 随后在 .onAnimationCancel() 之后被调用,这就是架构的工作原理。调用 .onAnimationEnd 时,'oa' 在那一点上为 NULL,导致此 NPE。由于 .onAnimationEnd() 保证在这两种情况下都会被调用,因此 .onAnimationCancel() 被拉出以解决 NPE。**