Android Timer琼讽、CountDownTimer洪唐、AlarmManager

一凭需、Timer TimerTask

參考Java中的Timer和TimerTask在Android中的用法

在開(kāi)發(fā)中我們有時(shí)會(huì)有這樣的需求粒蜈,即在固定的每隔一段時(shí)間執(zhí)行某一個(gè)任務(wù)枯怖。比如UI上的控件需要隨著時(shí)間改變度硝,我們可以使用Java為我們提供的計(jì)時(shí)器的工具類(lèi)塘淑,即Timer和TimerTask存捺。

Timer是一個(gè)普通的類(lèi)捌治,其中有幾個(gè)重要的方法肖油;而TimerTask則是一個(gè)抽象類(lèi)森枪,其中有一個(gè)抽象方法run()县袱,類(lèi)似線程中的run()方法式散,我們使用Timer創(chuàng)建一個(gè)他的對(duì)象,然后使用這對(duì)象的schedule方法來(lái)完成這種間隔的操作编饺。

//true 說(shuō)明這個(gè)timer以daemon方式運(yùn)行(優(yōu)先級(jí)低透且,程序結(jié)束timer也自動(dòng)結(jié)束) 
java.util.Timer timer = new java.util.Timer(true);

TimerTask task = new TimerTask() {
   public void run() {
   //每次需要執(zhí)行的代碼放到這里面石蔗。   
   }   
};

//以下是幾種調(diào)度task的方法:

//time為Date類(lèi)型:在指定時(shí)間執(zhí)行一次。
timer.schedule(task, time);

//firstTime為Date類(lèi)型,period為long日熬,表示從firstTime時(shí)刻開(kāi)始,每隔period毫秒執(zhí)行一次毕荐。
timer.schedule(task, firstTime, period);   

//delay 為long類(lèi)型:從現(xiàn)在起過(guò)delay毫秒執(zhí)行一次憎亚。
timer.schedule(task, delay);

//delay為long,period為long:從現(xiàn)在起過(guò)delay毫秒以后第美,每隔period毫秒執(zhí)行一次什往。
timer.schedule(task, delay, period);

//該任務(wù)每隔2秒更新主線程的UI(在主線程的TextView顯示最新的系統(tǒng)時(shí)間System.currentTimeMillis())别威。
package zhangphil.timertask;

import java.util.Timer;
import java.util.TimerTask;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.widget.TextView;

public class MainActivity extends Activity {

    private Timer timer;
    private TimerTask task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView tv = (TextView) findViewById(R.id.textView);

        final int WHAT = 102;
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                case WHAT:
                    tv.setText(msg.obj + "");
                    break;
                }
            }
        };

        task = new TimerTask() {
            @Override
            public void run() {
                Message message = new Message();
                message.what = WHAT;
                message.obj = System.currentTimeMillis();
                handler.sendMessage(message);
            }
        };

        timer = new Timer();
        // 參數(shù):
        // 1000,延時(shí)1秒后執(zhí)行衫樊。
        // 2000科侈,每隔2秒執(zhí)行1次task臀栈。
        timer.schedule(task, 1000, 2000);
    }

    @Override
    protected void onStop() {
        super.onStop();

        // 暫停
        // timer.cancel();
        // task.cancel();
    }
}

schedule方法有三個(gè)參數(shù)
第一個(gè)參數(shù)就是TimerTask類(lèi)型的對(duì)象姑躲,我們實(shí)現(xiàn)TimerTask的run()方法就是要周期執(zhí)行的一個(gè)任務(wù)黍析;
第二個(gè)參數(shù)有兩種類(lèi)型屎开,第一種是long類(lèi)型奄抽,表示多長(zhǎng)時(shí)間后開(kāi)始執(zhí)行额划,另一種是Date類(lèi)型俊戳,表示從那個(gè)時(shí)間后開(kāi)始執(zhí)行品抽;
第三個(gè)參數(shù)就是執(zhí)行的周期圆恤,為long類(lèi)型盆昙。

schedule方法還有一種兩個(gè)參數(shù)的執(zhí)行重載,第一個(gè)參數(shù)仍然是TimerTask焊虏,第二個(gè)表示為long的形式表示多長(zhǎng)時(shí)間后執(zhí)行一次淡喜,為Date就表示某個(gè)時(shí)間后執(zhí)行一次。

Timer就是一個(gè)線程诵闭,使用schedule方法完成對(duì)TimerTask的調(diào)度炼团,多個(gè)TimerTask可以共用一個(gè)Timer澎嚣,也就是說(shuō)Timer對(duì)象調(diào)用一次schedule方法就是創(chuàng)建了一個(gè)線程,并且調(diào)用一次schedule后TimerTask是無(wú)限制的循環(huán)下去的易桃,使用Timer的cancel()停止操作。當(dāng)然同一個(gè)Timer執(zhí)行一次cancel()方法后锌俱,所有Timer線程都被終止晤郑。

若要在TimerTask中更新主線程UI,鑒于Android編程模型不允許在非主線程中更新主線程UI贸宏,因此需要結(jié)合Android的Handler實(shí)現(xiàn)在Java的TimerTask中更新主線程UI造寝。

二、ScheduledThreadPoolExecutor

public static ExecutorService newScheduledThreadPool(int corePoolSize){
return new ThreadPoolExecutor(corePoolSize,Integer.MAX_VALUE,0L,TimeUnit.SECONDS,new DelayedWorkQueue<Runnable>());
}

ScheduledThreadPoolExecutor核心線程數(shù)量固定吭练,非核心線程數(shù)沒(méi)有限制诫龙。主要用于執(zhí)行定時(shí)任務(wù)和具有固定周期的重復(fù)任務(wù)。

參考[Java 并發(fā)專(zhuān)題 : Timer的缺陷 用ScheduledExecutorService替代](http://blog.csdn.net/lmj623565791/article/details/27109467)
**以前在項(xiàng)目中也經(jīng)常使用定時(shí)器鲫咽,比如每隔一段時(shí)間清理項(xiàng)目中的一些垃圾文件赐稽,每個(gè)一段時(shí)間進(jìn)行數(shù)據(jù)清洗;然而Timer是存在一些缺陷的浑侥,因?yàn)門(mén)imer在執(zhí)行定時(shí)任務(wù)時(shí)只會(huì)創(chuàng)建一個(gè)線程,所以如果存在多個(gè)任務(wù)晰绎,且任務(wù)時(shí)間過(guò)長(zhǎng)寓落,超過(guò)了兩個(gè)任務(wù)的間隔時(shí)間,會(huì)發(fā)生一些缺陷.**

1.定義了兩個(gè)任務(wù)荞下,預(yù)計(jì)是第一個(gè)任務(wù)1s后執(zhí)行伶选,第二個(gè)任務(wù)3s后執(zhí)行

package com.zhy.concurrency.timer;

import java.util.Timer;
import java.util.TimerTask;

public class TimerTest
{
private static long start;

public static void main(String[] args) throws Exception
{

    TimerTask task1 = new TimerTask()
    {
        @Override
        public void run()
        {

            System.out.println("task1 invoked ! "
                    + (System.currentTimeMillis() - start));
            try
            {
                Thread.sleep(3000);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }

        }
    };
    TimerTask task2 = new TimerTask()
    {
        @Override
        public void run()
        {
            System.out.println("task2 invoked ! "
                    + (System.currentTimeMillis() - start));
        }
    };
    Timer timer = new Timer();
    start = System.currentTimeMillis();
    timer.schedule(task1, 1000);
    timer.schedule(task2, 3000);

}

}

運(yùn)行結(jié)果:

task1 invoked ! 1000
task2 invoked ! 4000

task2實(shí)際上是4后才執(zhí)行,正因?yàn)門(mén)imer內(nèi)部是一個(gè)線程尖昏,而任務(wù)1所需的時(shí)間超過(guò)了兩個(gè)任務(wù)間的間隔導(dǎo)致仰税。下面使用ScheduledThreadPool解決這個(gè)問(wèn)題:

package com.zhy.concurrency.timer;

import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolExecutorTest
{
private static long start;

public static void main(String[] args)
{
    /**
     * 使用工廠方法初始化一個(gè)ScheduledThreadPool
     */
    ScheduledExecutorService newScheduledThreadPool = Executors
            .newScheduledThreadPool(2);
    
    TimerTask task1 = new TimerTask()
    {
        @Override
        public void run()
        {
            try
            {

                System.out.println("task1 invoked ! "
                        + (System.currentTimeMillis() - start));
                Thread.sleep(3000);
            } catch (Exception e)
            {
                e.printStackTrace();
            }

        }
    };

    TimerTask task2 = new TimerTask()
    {
        @Override
        public void run()
        {
            System.out.println("task2 invoked ! "
                    + (System.currentTimeMillis() - start));
        }
    };
    start = System.currentTimeMillis();
    newScheduledThreadPool.schedule(task1, 1000, TimeUnit.MILLISECONDS);
    newScheduledThreadPool.schedule(task2, 3000, TimeUnit.MILLISECONDS);
}

}

2.如果TimerTask拋出RuntimeException,Timer會(huì)停止所有任務(wù)的運(yùn)行:

package com.zhy.concurrency.timer;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class ScheduledThreadPoolDemo01
{

public static void main(String[] args) throws InterruptedException
{

    final TimerTask task1 = new TimerTask()
    {

        @Override
        public void run()
        {
            throw new RuntimeException();
        }
    };

    final TimerTask task2 = new TimerTask()
    {

        @Override
        public void run()
        {
            System.out.println("task2 invoked!");
        }
    };
    
    Timer timer = new Timer();
    timer.schedule(task1, 100);
    timer.scheduleAtFixedRate(task2, new Date(), 1000);
    
    

}

}

上面有兩個(gè)任務(wù)抽诉,任務(wù)1拋出一個(gè)運(yùn)行時(shí)的異常陨簇,任務(wù)2周期性的執(zhí)行某個(gè)操作,輸出結(jié)果:

task2 invoked!
Exception in thread "Timer-0" java.lang.RuntimeException
at com.zhy.concurrency.timer.ScheduledThreadPoolDemo01$1.run(ScheduledThreadPoolDemo01.java:24)
at java.util.TimerThread.mainLoop(Timer.java:512)
at java.util.TimerThread.run(Timer.java:462)

由于任務(wù)1的一次迹淌,任務(wù)2也停止運(yùn)行了河绽。。唉窃。下面使用ScheduledExecutorService解決這個(gè)問(wèn)題:

package com.zhy.concurrency.timer;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolDemo01
{

public static void main(String[] args) throws InterruptedException
{

    final TimerTask task1 = new TimerTask()
    {

        @Override
        public void run()
        {
            throw new RuntimeException();
        }
    };

    final TimerTask task2 = new TimerTask()
    {

        @Override
        public void run()
        {
            System.out.println("task2 invoked!");
        }
    };
    
    
    
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
    pool.schedule(task1, 100, TimeUnit.MILLISECONDS);
    pool.scheduleAtFixedRate(task2, 0 , 1000, TimeUnit.MILLISECONDS);

}

}

代碼基本一致耙饰,但是ScheduledExecutorService可以保證,task1出現(xiàn)異常時(shí)纹份,不影響task2的運(yùn)行:

task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!

#####三苟跪、AlarmManager
>Java的Timer類(lèi)可以用來(lái)計(jì)劃需要循環(huán)執(zhí)行的任務(wù)廷痘。簡(jiǎn)單的說(shuō),一個(gè)Timer內(nèi)部封裝裝了“一個(gè)Thread”和“一個(gè)TimerTask隊(duì)列”件已,這個(gè)隊(duì)列按照一定的方式將任務(wù)排隊(duì)處理笋额。封裝的Thread在Timer的構(gòu)造方法調(diào)用時(shí)被啟動(dòng),這個(gè)Thread的run方法按照條件去循環(huán)這個(gè)TimerTask隊(duì)列拨齐,然后調(diào)用TimerTask的run方法鳞陨。
但是,**如果CPU進(jìn)入了休眠狀態(tài)瞻惋,那么這個(gè)thread將會(huì)因?yàn)槭PU時(shí)間片而阻塞厦滤,從而造成我們需要的定時(shí)任務(wù)失效**。上述定時(shí)任務(wù)失效的場(chǎng)景分析:假設(shè)定時(shí)任務(wù)的條件是到了時(shí)間xx:yy才能執(zhí)行歼狼,但由于cpu休眠造成線程阻塞的關(guān)系掏导,當(dāng)前系統(tǒng)時(shí)間超過(guò)了這個(gè)時(shí)間,即便CPU從終端中恢復(fù)了羽峰,那么由于條件不滿(mǎn)足趟咆,定時(shí)任務(wù)在這一次自然就失效了。
解決方案是:它**需要用WakeLock讓CPU 保持喚醒狀態(tài)梅屉。那么問(wèn)題就來(lái)了值纱,這樣會(huì)大量消耗手機(jī)電量(CPU喚醒和屏幕喚醒不是同一概念)**,大大減短手機(jī)待機(jī)時(shí)間坯汤。這種方式不能滿(mǎn)足我們的需求虐唠。
注:TimerTask實(shí)現(xiàn)Runnable接口,但它的run方法只是簡(jiǎn)單的在Timer中封裝的Thread中被調(diào)用惰聂,并未將其放在其它線程中執(zhí)行疆偿。也就是說(shuō)timer是單線程執(zhí)行的,那么問(wèn)題來(lái)了搓幌,為何要這么費(fèi)勁的封裝一個(gè)Runnable接口又不進(jìn)行多線程調(diào)用杆故?我的答案是,老外只是想要表示它是可執(zhí)行的方法溉愁。
關(guān)于休眠处铛,可以參考
[Android 關(guān)于休眠引發(fā)的幾個(gè)血案](http://www.ithtw.com/437.html)
[Android手機(jī)休眠后時(shí)間不準(zhǔn)確的解決方案](http://blog.csdn.net/t12x3456/article/details/7826811)
AlarmManager是Android 系統(tǒng)封裝的用于管理RTC的模塊,RTC(Real Time Clock) 是一個(gè)獨(dú)立的硬件時(shí)鐘拐揭,可以在 CPU 休眠時(shí)正常運(yùn)行罢缸,在預(yù)設(shè)的時(shí)間到達(dá)時(shí),通過(guò)中斷喚醒CPU投队。這意味著枫疆,如果我們用 AlarmManager 來(lái)定時(shí)執(zhí)行任務(wù),CPU 可以正常的休眠敷鸦,只有在需要運(yùn)行任務(wù)時(shí)醒來(lái)一段很短的時(shí)間息楔。

以下參考[Android基礎(chǔ)入門(mén)教程——10.5 AlarmManager(鬧鐘服務(wù))](http://blog.csdn.net/coder_pig/article/details/49423531)

**核心流程如下:**
* `AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); `
獲得系統(tǒng)提供的AlarmManager服務(wù)的對(duì)象
* Intent設(shè)置要啟動(dòng)的組件: 
`Intent intent = new Intent(MainActivity.this, ClockActivity.class);`
* PendingIntent對(duì)象設(shè)置動(dòng)作,啟動(dòng)的是Activity還是Service,又或者是廣播! 
`PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0, intent, 0);`
* 調(diào)用AlarmManager的set( )方法設(shè)置單次鬧鐘的鬧鐘類(lèi)型,啟動(dòng)時(shí)間以及PendingIntent對(duì)象! 
`alarmManager.set(AlarmManager.RTC_WAKEUP,c.getTimeInMillis(), pi);`

//10秒鐘后執(zhí)行一個(gè)任務(wù)
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);

**相關(guān)方法:**
* set(int type,long startTime,PendingIntent pi):一次性鬧鐘
* setRepeating(int type寝贡,long startTime,long intervalTime值依,PendingIntent pi): 
重復(fù)性鬧鐘,和3有區(qū)別,3鬧鐘間隔時(shí)間不固定
* setInexactRepeating(int type圃泡,long startTime,long intervalTime,PendingIntent pi): 
重復(fù)性鬧鐘愿险,時(shí)間不固定
* cancel(PendingIntent pi):取消AlarmManager的定時(shí)服務(wù)
* getNextAlarmClock():得到下一個(gè)鬧鐘颇蜡,返回值A(chǔ)larmManager.AlarmClockInfo
* setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) 
和set方法類(lèi)似,這個(gè)鬧鐘運(yùn)行在系統(tǒng)處于低電模式時(shí)有效
* setExact(int type, long triggerAtMillis, PendingIntent operation): 
在規(guī)定的時(shí)間精確的執(zhí)行鬧鐘辆亏,比set方法設(shè)置的精度更高
* setTime(long millis):設(shè)置系統(tǒng)墻上的時(shí)間
* setTimeZone(String timeZone):設(shè)置系統(tǒng)持續(xù)的默認(rèn)時(shí)區(qū)
* setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation): 
設(shè)置一個(gè)鬧鐘在給定的時(shí)間窗觸發(fā)风秤。類(lèi)似于set,該方法允許應(yīng)用程序精確地控制操作系統(tǒng)調(diào) 整鬧鐘觸發(fā)時(shí)間的程度扮叨。

**關(guān)鍵參數(shù)講解:**
* Type(鬧鐘類(lèi)型): 
有五個(gè)可選值: 
  * AlarmManager.ELAPSED_REALTIME: 
鬧鐘在手機(jī)睡眠狀態(tài)下不可用缤弦,該狀態(tài)下鬧鐘使用相對(duì)時(shí)間(相對(duì)于系統(tǒng)啟動(dòng)開(kāi)始),狀態(tài)值為3; 
  * AlarmManager.ELAPSED_REALTIME_WAKEUP 
鬧鐘在睡眠狀態(tài)下會(huì)喚醒系統(tǒng)并執(zhí)行提示功能彻磁,該狀態(tài)下鬧鐘也使用相對(duì)時(shí)間碍沐,狀態(tài)值為2; 
  * AlarmManager.RTC 
鬧鐘在睡眠狀態(tài)下不可用衷蜓,該狀態(tài)下鬧鐘使用絕對(duì)時(shí)間累提,即當(dāng)前系統(tǒng)時(shí)間,狀態(tài)值為1磁浇; 
  * AlarmManager.RTC_WAKEUP 
表示鬧鐘在睡眠狀態(tài)下會(huì)喚醒系統(tǒng)并執(zhí)行提示功能斋陪,該狀態(tài)下鬧鐘使用絕對(duì)時(shí)間,狀態(tài)值為0; 
  * AlarmManager.POWER_OFF_WAKEUP 
表示鬧鐘在手機(jī)關(guān)機(jī)狀態(tài)下也能正常進(jìn)行提示功能扯夭,所以是5個(gè)狀態(tài)中用的最多的狀態(tài)之一,該狀態(tài)下鬧鐘也是用絕對(duì)時(shí)間鞍匾,狀態(tài)值為4交洗;不過(guò)本狀態(tài)好像受SDK版本影響,某些版本并不支持橡淑;
* startTime:鬧鐘的第一次執(zhí)行時(shí)間构拳,以毫秒為單位,可以自定義時(shí)間梁棠,不過(guò)一般使用當(dāng)前時(shí)間置森。 需要注意的是,本屬性與第一個(gè)屬性(type)密切相關(guān),如果第一個(gè)參數(shù)對(duì)應(yīng)的鬧鐘使用的是相對(duì)時(shí)間 (ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那么本屬性就得使用相對(duì)時(shí)間相對(duì)于系統(tǒng)啟動(dòng)時(shí)間來(lái)說(shuō)),比如當(dāng)前時(shí)間就表示為:SystemClock.elapsedRealtime()符糊; 如果第一個(gè)參數(shù)對(duì)應(yīng)的鬧鐘使用的是絕對(duì)時(shí)間(RTC凫海、RTC_WAKEUP、POWER_OFF_WAKEUP), 那么本屬性就得使用絕對(duì)時(shí)間男娄,比如當(dāng)前時(shí)間就表示 為:System.currentTimeMillis()行贪。
* intervalTime:表示兩次鬧鐘執(zhí)行的間隔時(shí)間,也是以毫秒為單位.
* PendingIntent:綁定了鬧鐘的執(zhí)行動(dòng)作漾稀,比如發(fā)送一個(gè)廣播、給出提示等等建瘫。 
PendingIntent是Intent的封裝類(lèi)崭捍。需要注意的是,
  * 如果是通過(guò)啟動(dòng)服務(wù)來(lái)實(shí)現(xiàn)鬧鐘提示的話(huà)啰脚,PendingIntent對(duì)象的獲取就應(yīng)該采用Pending.getService (Context c,int i,Intent intent,int j)方法殷蛇;
  * 如果是通過(guò)廣播來(lái)實(shí)現(xiàn)鬧鐘提示的話(huà),PendingIntent對(duì)象的獲取就應(yīng)該采用 PendingIntent.getBroadcast (Context c,int i,Intent intent,int j)方法橄浓;
  * 如果是采用Activity的方式來(lái)實(shí)現(xiàn)鬧鐘提示的話(huà)粒梦,PendingIntent對(duì)象的獲取就應(yīng)該采用 
PendingIntent.getActivity(Context c,int i,Intent intent,int j)方法。

如果這三種方法錯(cuò)用了的話(huà)贮配,雖然不會(huì)報(bào)錯(cuò)谍倦,但是看不到鬧鐘提示效果。

下面是一個(gè)每隔一小時(shí)就會(huì)在后臺(tái)執(zhí)行定時(shí)任務(wù)的服務(wù)泪勒。

public class LongRunningService extends Service{
public IBinder onBind(Intent intent){
return null;
}

public int onStartCommand(Intent intent, int flags, int startId){
new Thread(new Runnable(){
public void run(){
Log.d("LongRunningService","executed at"+new Date().toString());
}
}).start();
}
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 60 * 60 * 1000;
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent i = new Intent(this,AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this,0,i,0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi);
return super.onStartCommand(intent,flags,startId);
}

public class AlarmReceiver extends BroadcastReceiver{
public void onReceive(Context context, Intent intent){
Intent i = new Intent(context, LongRunningService.class);
context.startService(i);
}
}

public class MainActivity extends Activity{
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Intent intent = new Intent(this,LongRunningService);
startService(intent);
}
}

#####四昼蛀、CountDownTimer
參考
[Android實(shí)現(xiàn)倒計(jì)時(shí)之使用CountDownTimer](http://blog.csdn.net/qq_20785431/article/details/51571300)
[[Android] CountDownTimer 簡(jiǎn)單用法與源碼解析](http://blog.qiji.tech/archives/7485)
>在開(kāi)發(fā)中會(huì)經(jīng)常用到倒計(jì)時(shí)這個(gè)功能,包括給手機(jī)發(fā)送驗(yàn)證碼等等圆存,之前我的做法都是使用Handler + Timer +TimerTask來(lái)實(shí)現(xiàn)叼旋,現(xiàn)在發(fā)現(xiàn)了這個(gè)類(lèi),果斷拋棄之前的做法沦辙,相信還是有很多人和我一樣一開(kāi)始不知道Android已經(jīng)幫我們封裝好了一個(gè)叫CountDownTimer的類(lèi)夫植。`CountDownTimer timer = new CountDownTimer(10000,1000);`以毫秒為單位,第一個(gè)參數(shù)是指從開(kāi)始調(diào)用start()方法到倒計(jì)時(shí)完成的時(shí)候onFinish()方法被調(diào)用這段時(shí)間的毫秒數(shù)油讯,也就是倒計(jì)時(shí)總的時(shí)間详民;第二個(gè)參數(shù)表示間隔多少毫秒調(diào)用一次 onTick方法,例如間隔1000毫秒陌兑。在調(diào)用的時(shí)候直接使用`timer.start();`

//共有4個(gè)方法沈跨,前兩個(gè)抽象方法需要重寫(xiě)
public abstract void onTick(long millisUntilFinished);//固定間隔調(diào)用
public abstract void onFinish();//倒計(jì)時(shí)完成時(shí)被調(diào)用
public synchronized final void cancel();//取消倒計(jì)時(shí),當(dāng)再次啟動(dòng)會(huì)重新開(kāi)始倒計(jì)時(shí)
public synchronized final CountDownTimer start();//啟動(dòng)倒計(jì)時(shí)
//該類(lèi)的成員變量與成員函數(shù)均較少兔综,功能實(shí)現(xiàn)的關(guān)鍵部分在于 mHandler饿凛,下面看 mHandler 的源碼:
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);
  }
}

}
};


![內(nèi)部流程](http://upload-images.jianshu.io/upload_images/2354823-46005a2dd429b052.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
看一個(gè)使用例子:

package com.per.countdowntimer;

import android.app.Activity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity {
private TextView mTvShow;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mTvShow = (TextView) findViewById(R.id.show);
}

/**
 * 取消倒計(jì)時(shí)
 * @param v
 */
public void oncancel(View v) {
    timer.cancel();
}

/**
 * 開(kāi)始倒計(jì)時(shí)
 * @param v
 */
public void restart(View v) {
    timer.start();
}

private CountDownTimer timer = new CountDownTimer(10000, 1000) {

    @Override
    public void onTick(long millisUntilFinished) {
        mTvShow.setText((millisUntilFinished / 1000) + "秒后可重發(fā)");
    }

    @Override
    public void onFinish() {
        mTvShow.setEnabled(true);
        mTvShow.setText("獲取驗(yàn)證碼");
    }
};

}

#####五、new Handler().postDelayed()
該方法就是利用我們常說(shuō)的消息處理器软驰。該方法原理就是在主線程中創(chuàng)建一個(gè)Handler消息處理器涧窒,然后利用其中的一個(gè)postDelayed(Runnable r, long delayMillis)方法,該方法第一個(gè)參數(shù)需要傳入一個(gè)Runnable接口锭亏,并實(shí)現(xiàn)run()方法纠吴,第二個(gè)參數(shù)就是延遲多少時(shí)間將run()方法中的代碼通過(guò)一個(gè)消息發(fā)送給消息隊(duì)列,然后在主線程中執(zhí)行這個(gè)消息中的代碼慧瘤,即是run方法中的代碼呜象,從而實(shí)現(xiàn)在主線程中更新界面UI膳凝。
貼代碼吧:

new Handler().postDelayed(new Runnable() {//在當(dāng)前線程(也即主線程中)開(kāi)啟一個(gè)消息處理器,并在3秒后在主線程中執(zhí)行恭陡,從而來(lái)更新UI
@Override
public void run() {
//有關(guān)更新UI的代碼
}
}, 3000);//3秒后發(fā)送

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蹬音,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子休玩,更是在濱河造成了極大的恐慌著淆,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拴疤,死亡現(xiàn)場(chǎng)離奇詭異永部,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)呐矾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)苔埋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蜒犯,你說(shuō)我怎么就攤上這事组橄。” “怎么了罚随?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵玉工,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我淘菩,道長(zhǎng)遵班,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任潮改,我火速辦了婚禮狭郑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘汇在。我一直安慰自己翰萨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布趾疚。 她就那樣靜靜地躺著缨历,像睡著了一般以蕴。 火紅的嫁衣襯著肌膚如雪糙麦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,255評(píng)論 1 308
  • 那天丛肮,我揣著相機(jī)與錄音赡磅,去河邊找鬼。 笑死宝与,一個(gè)胖子當(dāng)著我的面吹牛焚廊,可吹牛的內(nèi)容都是我干的冶匹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼咆瘟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嚼隘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起袒餐,我...
    開(kāi)封第一講書(shū)人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤飞蛹,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后灸眼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體卧檐,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年焰宣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霉囚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匕积,死狀恐怖盈罐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闸天,我是刑警寧澤暖呕,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站苞氮,受9級(jí)特大地震影響湾揽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笼吟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一库物、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贷帮,春花似錦戚揭、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至锄禽,卻和暖如春潜必,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沃但。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工磁滚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓垂攘,卻偏偏與公主長(zhǎng)得像维雇,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晒他,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容