CountDownTimer空指针(NULL)与Cancel无效的问题

时间:2022-05-11 21:28:01

1.先看源码

  • API=20 的CountDownTimer源码
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package android.os;

import android.util.Log;

/**
* Schedule a countdown until a time in the future, with
* regular notifications on intervals along the way.
*
* Example of showing a 30 second countdown in a text field:
*
* <pre class="prettyprint">
* new CountDownTimer(30000, 1000) {
*
* public void onTick(long millisUntilFinished) {
* mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
* }
*
* public void onFinish() {
* mTextField.setText("done!");
* }
* }.start();
* </pre>
*
* The calls to {@link #onTick(long)} are synchronized to this object so that
* one call to {@link #onTick(long)} won't ever occur before the previous
* callback is complete. This is only relevant when the implementation of
* {@link #onTick(long)} takes an amount of time to execute that is significant
* compared to the countdown interval.
*/

public abstract class CountDownTimer {

/**
* Millis since epoch when alarm should stop.
*/

private final long mMillisInFuture;

/**
* The interval in millis that the user receives callbacks
*/

private final long mCountdownInterval;

private long mStopTimeInFuture;

/**
* @param millisInFuture The number of millis in the future from the call
* to {@link #start()} until the countdown is done and {@link #onFinish()}
* is called.
* @param countDownInterval The interval along the way to receive
* {@link #onTick(long)} callbacks.
*/

public CountDownTimer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}

/**
* Cancel the countdown.
*/

public final void cancel() {
mHandler.removeMessages(MSG);
}

/**
* Start the countdown.
*/

public synchronized final CountDownTimer start() {
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}


/**
* Callback fired on regular interval.
* @param millisUntilFinished The amount of time until finished.
*/

public abstract void onTick(long millisUntilFinished);

/**
* Callback fired when the time is up.
*/

public abstract void onFinish();


private static final int MSG = 1;


// handles counting down
private Handler mHandler = new Handler() {

@Override
public void handleMessage(Message msg) {

synchronized (CountDownTimer.this) {
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();

if (millisLeft <= 0) {
onFinish();
} else if (millisLeft < mCountdownInterval) {
// no tick, just delay until done
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);

// take into account user's onTick taking time to execute
long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();

// special case: user's onTick took more than interval to
// complete, skip to next interval
while (delay < 0) delay += mCountdownInterval;

sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
}
  • API=21,也就是5.0以上的系统源码
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package android.os;

/**
* Schedule a countdown until a time in the future, with
* regular notifications on intervals along the way.
*
* Example of showing a 30 second countdown in a text field:
*
* <pre class="prettyprint">
* new CountDownTimer(30000, 1000) {
*
* public void onTick(long millisUntilFinished) {
* mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
* }
*
* public void onFinish() {
* mTextField.setText("done!");
* }
* }.start();
* </pre>
*
* The calls to {@link #onTick(long)} are synchronized to this object so that
* one call to {@link #onTick(long)} won't ever occur before the previous
* callback is complete. This is only relevant when the implementation of
* {@link #onTick(long)} takes an amount of time to execute that is significant
* compared to the countdown interval.
*/

public abstract class CountDownTimer {

/**
* Millis since epoch when alarm should stop.
*/

private final long mMillisInFuture;

/**
* The interval in millis that the user receives callbacks
*/

private final long mCountdownInterval;

private long mStopTimeInFuture;

/**
* boolean representing if the timer was cancelled
*/

private boolean mCancelled = false;

/**
* @param millisInFuture The number of millis in the future from the call
* to {@link #start()} until the countdown is done and {@link #onFinish()}
* is called.
* @param countDownInterval The interval along the way to receive
* {@link #onTick(long)} callbacks.
*/

public CountDownTimer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}

/**
* Cancel the countdown.
*/

public synchronized final void cancel() {
mCancelled = true;
mHandler.removeMessages(MSG);
}

/**
* Start the countdown.
*/

public synchronized final CountDownTimer start() {
mCancelled = false;
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}


/**
* Callback fired on regular interval.
* @param millisUntilFinished The amount of time until finished.
*/

public abstract void onTick(long millisUntilFinished);

/**
* Callback fired when the time is up.
*/

public abstract void onFinish();


private static final int MSG = 1;


// handles counting down
private Handler mHandler = new Handler() {

@Override
public void handleMessage(Message msg) {

synchronized (CountDownTimer.this) {
if (mCancelled) {
return;
}

final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();

if (millisLeft <= 0) {
onFinish();
} else if (millisLeft < mCountdownInterval) {
// no tick, just delay until done
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);

// take into account user's onTick taking time to execute
long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();

// special case: user's onTick took more than interval to
// complete, skip to next interval
while (delay < 0) delay += mCountdownInterval;

sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
}

我们会发现,5.0以上的源码里面多出private boolean mCancelled = false;仔细看源码,当在5.0以下的时候,调用cancel() 方法时,并没有取消Handler的进行。然而,在5.0以上看到这句代码,当mCanclelled=true;时直接跳出了Handler,不再执行倒计时。

    /**
* Cancel the countdown.
*/

public synchronized final void cancel() {
mCancelled = true;
mHandler.removeMessages(MSG);
}

if (mCancelled) {
return;
}

有些朋友会问,为什么还要mHandler.removeMessages(MSG)
这个想想也知道,ActivityFragment都已经被销毁了,你还继续走Handler,会造成Handler的内存泄漏的问题。可自行测试。

2.空指针异常

本人在测试过程中,在倒计时的过程中。关闭了当前Fragment,或者Activity时。会报NULL异常,可以自行测试喔。
可想而知原因,当然是FragmentActivity都已经被销毁了,界面里面初始化的控件肯定是被销毁了。当然为空了(NULL)。你那边还在调用Handler,肯定会挂掉了。
那我们解决方法:

private class TimeCount extends CountDownTimer {

public TimeCount(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}

@Override
public void onTick(long millisUntilFinished) {
//如果是Fragment 就判断getActivity() 是否为NULL
//如果是Activity 就判断!activity.isFinishing() 是否为NULL
if (getActivity() != null) {
//todo
btnSecurityCode.setClickable(false);
btnSecurityCode.setText(millisUntilFinished / 1000 + "秒后可重发");
}
}

@Override
public void onFinish() {
btnSecurityCode.setText("重新获取");
btnSecurityCode.setClickable(true);
}
}
@Override
public void onDestroy() {
timeCount.cancel();
super.onDestroy();
}

主要的代码我已经贴出来了,那我就再贴DEMO地址了。自己研究一波