CountDownTimer onFinish() not called if I call cancel() when activity is pausing

Tim picture Tim · Oct 20, 2012 · Viewed 10k times · Source

I've got an activity that plays some music via a MediaPlayer. Normally when I want to stop the song, I pass the MediaPlayer to a CountDownTimer that fades the volume to zero, and then releases the MediaPlayer. This seems to work fine, except that I'm having a problem when my activity pauses, such as after touching the home button.

I'm calling cancel() on the CountDownTimer in onPause, but it seems that since the activity is pausing, the CountDownTimer never receives the message before it is destroyed (?), and thus the finish() of CountDownTimer is not called.

The result of this is that after exiting the application, the music continues to play at whatever volume was last set in the CountDownTimer, but it never finishes. So now I'm stuck out of my application with the MediaPlayer running and no way to stop it (very bad).

Is it normal for a CountDownTimer to not get called onFinish when the activity is exiting, even if I called cancel?

This is my code:

public void StopSong(boolean fadeout){
    musicPlaying = false;
    if(_player != null) {
        final int fadeTime = 1000;
        CountDownTimer timer = new CountDownTimer(fadeTime,50) {
            final private MyMusicPlayer mFadePlayer = _player;

            @Override
            public void onTick(long millisUntilFinished) {
                mFadePlayer.setFade((float)millisUntilFinished / (float)fadeTime);
                Log.d("tag", "Fade timer " + millisUntilFinished+ "ms remaining.");
            }

            @Override
            public void onFinish() {
                mFadePlayer.stop();
                mFadePlayer.release();
                Log.d("tag", "Fade timer finished, releasing resource.");
            }
        };
        timer.start();
        mFadeTimers.add(timer);
        Log.d("tag", "Song stopping, starting fade timer.");
        _player = null;
    }
}

And in onPause:

    for(CountDownTimer t : mFadeTimers){
        Log.d("tag", "Cancelling fade timer on destroy.");
        t.cancel();
    }
    mFadeTimers.clear();

If everything was working fine, I would see the "cancelling fade timer" log message, followed by "Fade timer finished, releasing", but I never get the onFinish() logcat message.

I just see this:

 tag     Song stopping, starting fade timer.
 tag     Application exiting, destroying all audio resources
 tag     Cancelling fade timer on destroy.

How can I successfully abort all my countdowntimers on exiting the activity?

Answer

Sam picture Sam · Oct 20, 2012

Take a look at the source code. You'll see that cancel() does not call onFinish(), this is just the way CountDownTimer is designed.

You can call onFinish() yourself though:

for(CountDownTimer t : mFadeTimers){
    Log.d("tag", "Cancelling fade timer on destroy."); // ought to read "in onPause", right?
    t.cancel();
    t.onFinish();
}
mFadeTimers.clear();