安卓項目實戰之:倒計時類CountDownTimer的使用詳解

前言

在Android應用中幾乎都能看到,閃屏頁或者歡迎頁右上角有一個 “倒計時 + 跳過” 的功能,還有就是獲得驗證碼的倒計時功能,Android 實現倒計時的方式有多種,Handler 延時發送 Message,Timer 和 TimerTask 配合使用,使用 CountDownTimer 類等。相比而言,經過系統封裝的 CountDownTimer 算是使用起來最爲方便的方式之一。

CountDownTimer的使用示例

// 參數1:倒計時的總時間,單位爲毫秒
// 參數2:每次遞減的時間,單位也爲毫秒
private CountDownTimer timer = new CountDownTimer(10000, 1000) { 
    @Override 
    public void onTick(long millisUntilFinished) { 
        // 每隔參數二指定的毫秒數就會回調一次該方法,直到剩餘毫秒數millisUntilFinished減去參數2設置的每次遞減毫秒數
           的結果小於參數2設置的每次遞減毫秒數時,onTick方法就不會被回調了,會直接回調onFinish方法。
        vertifyView.setText((millisUntilFinished / 1000) + "秒後可重發"); 
    } 
    @Override 
    public void onFinish() { 
        // 倒計時完成之後,會回調該方法
        vertifyView.setEnabled(true); 
        vertifyView.setText("獲取驗證碼"); 
    }
}; 
// 開始執行倒計時
timer.start();

CountDownTimer使用過程中的2個問題

1.開始時間不準確以及不能倒計時到0
2.使用過程中有可能會出現內存泄漏

2個問題產生的原因分析以及解決方案

開始時間不準確以及不能倒計時到0

執行下面的代碼:

CountDownTimer timer = new CountDownTimer(5000, 1000){ 
    @Override 
    public void onTick(long millisUntilFinished) { 
        Log.d("onTick", millisUntilFinished + "");
        mSampleTv.setText((int)millisUntilFinished / 1000 + "s"); 
    } 
    @Override 
    public void onFinish() { 
    
    } 
}; 
timer.start();

我們發現TextView並不是以5,4,3,2,1,0這樣來顯示的,而是以4,3,2,1來顯示,開始竟然是4不是5,同樣結束也不是0而是1。
控制檯打印的日誌信息如下:

09-26 22:09:01.429 22197-22197/com.yifeng.sample D/onTick: 4985 
09-26 22:09:02.430 22197-22197/com.yifeng.sample D/onTick: 3984 
09-26 22:09:03.432 22197-22197/com.yifeng.sample D/onTick: 2982 
09-26 22:09:04.434 22197-22197/com.yifeng.sample D/onTick: 1981

經過對源碼的分析,我們發現CountDownTimer 在內部也是藉助 Handler 實現的,並且CountDownTimer 的內部實現比我們理想的計算更加精準,將 start() 方法到 handleMessage() 方法間的這段代碼執行的極短暫時間消耗也充分考慮在內(這裏其實主要考慮的是 Message 隊列的排隊時間),而這也就是爲什麼log日誌中顯示的非整秒倒計時的原因。
對於倒計時不能到0的原因其實就是因爲1981-1000=981<1000,從而在1981之後沒有再次回調onTick方法,而是直接回調了onFinish方法。
知道了原因後,我們就可以通過延長時間的方式來補上這個誤差,至於補多少,一般建議小於1000的任意值即可,但是儘量不要取太小,比如我們可以取600,那麼代碼就變成如下:

CountDownTimer timer = new CountDownTimer(5000 + 600, 1000){ 
    @Override 
    public void onTick(long m) { 
        Log.d("onTick", m + "");
        mSampleTv.setText((int)m / 1000 + "s"); 
    } 
    @Override 
    public void onFinish() { 
    
    } 
}; 
timer.start();

對於倒計時不能到0的問題,有的朋友可能會說是否可以在onFinis()方法中直接設置TextView的值爲0秒呢,告訴你,我試過不起作用,不相信大家可以自己去試,那麼有沒有其他方式來解決呢,當然有,同樣還是在總時間上動手腳,我們可以在總時間上加1000毫秒,然後在顯示的時候再減去這1秒,代碼如下:

CountDownTimer timer = new CountDownTimer(5000 + 1600, 1000){ 
    @Override 
    public void onTick(long m) { 
        Log.d("onTick", millisUntilFinished + "");
        mSampleTv.setText(((int)m / 1000 -1)+ "s"); 
    } 
    @Override 
    public void onFinish() { 
    
    } 
}; 
timer.start();

可以看到這次顯示的結果即爲:5s,4s,3s,2s,1s,0s。

使用過程中有可能會出現內存泄漏

從源碼中我們可以看出,CountDownTimer的內部實現是採用Handler機制,通過sendMessageDelayed延遲發送一條message到主線程的looper中,然後在自身中收到之後判斷剩餘時間,併發出相關回調,然後再次發出message的方式。
這樣的方式其實是有一定弊端的,那就是如果在Activity或者Fragment被回收時並未調用CountDownTimer的cancel()方法結束自己,這個時候CountDownTimer的Handler方法中如果判斷到當前的時間未走完,那麼會繼續調用sendMessageDelayed(obtainMessage(MSG), delay);,從而觸發onTick方法的執行,當回調了Activity或者fragment中CountDownTimer的onTick方法時,Activity或者Fragment已經被系統回收,從而裏面的變量被設置爲Null,此時如果再次調用mSampleTv.setText((int)m / 1000 + "s");,mSampleTv爲空,也就會報空指針,同時,CountDownTimer中的Handler方法還在繼續執行,這一塊空間始終無法被系統回收也就造成了內存泄漏問題。
因此爲了避免空指針和內存泄露,我們要注意以下幾點:
1,在CountDownTimer的onTick方法中記得判空

activity中 
if(!activity.isFinishing()){ 
    //doing something... 
} 

fragment中 
if(getActivity()!=null){ 
    //doing something... 
}

2,在配合DialogFragment使用時,如果在onFinish()方法調用了 dismiss()方法讓彈框消失,記得 判斷getFragmentManager是否爲空

    @Override
    public void onFinish() {
        if(getFragmentManager()!=null){
            dismiss();
        }
    }

3,在使用CountDownTimer時,在宿主Activity或fragment生命週期結束的時候,記得調用timer.cancle()方法

@Override 
public void onDestroy() { 
    if(timer!=null){ 
        timer.cancel(); 
        timer = null;
    } 
super.onDestroy(); }

使用示例

public class ZpTimerActivity extends Activity {
 
    private CountDownTimer mTimer;
 
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        setContentView(R.layout.activity_timer);
        initView();
    }
 
    private void initView() {
        if (mTimer == null) {
            mTimer = new CountDownTimer((long) (5 * 1000), 1000) {
 
                @Override
                public void onTick(long millisUntilFinished) {
                    if (!ZpTimerActivity.this.isFinishing()) {
                        int remainTime = (int) (millisUntilFinished / 1000L);
                        Log.e("zpan","======remainTime=====" + remainTime);
                    }
                }
 
                @Override
                public void onFinish() {
                    Log.e("zpan","======onFinish=====");
                }
            };
        }
        mTimer.start();
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mTimer != null) {
            mTimer.cancel();
            mTimer = null;
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章