注:本篇文章內容爲複製的,由於時間過久,文章地址忘記了,特注非原創
一、Timer TimerTask
參考Java中的Timer和TimerTask在Android中的用法
在開發中我們有時會有這樣的需求,即在固定的每隔一段時間執行某一個任務。比如UI上的控件需要隨着時間改變,我們可以使用Java爲我們提供的計時器的工具類,即Timer和TimerTask。
Timer是一個普通的類,其中有幾個重要的方法;而TimerTask則是一個抽象類,其中有一個抽象方法run(),類似線程中的run()方法,我們使用Timer創建一個他的對象,然後使用這對象的schedule方法來完成這種間隔的操作。
//true 說明這個timer以daemon方式運行(優先級低,程序結束timer也自動結束)
java.util.Timer timer = new java.util.Timer(true);
TimerTask task = new TimerTask() {
public void run() {
//每次需要執行的代碼放到這裏面。
}
};
//以下是幾種調度task的方法:
//time爲Date類型:在指定時間執行一次。
timer.schedule(task, time);
//firstTime爲Date類型,period爲long,表示從firstTime時刻開始,每隔period毫秒執行一次。
timer.schedule(task, firstTime, period);
//delay 爲long類型:從現在起過delay毫秒執行一次。
timer.schedule(task, delay);
//delay爲long,period爲long:從現在起過delay毫秒以後,每隔period毫秒執行一次。
timer.schedule(task, delay, period);
//該任務每隔2秒更新主線程的UI(在主線程的TextView顯示最新的系統時間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();
// 參數:
// 1000,延時1秒後執行。
// 2000,每隔2秒執行1次task。
timer.schedule(task, 1000, 2000);
}
@Override
protected void onStop() {
super.onStop();
// 暫停
// timer.cancel();
// task.cancel();
}
}
schedule方法有三個參數
第一個參數就是TimerTask類型的對象,我們實現TimerTask的run()方法就是要週期執行的一個任務;
第二個參數有兩種類型,第一種是long類型,表示多長時間後開始執行,另一種是Date類型,表示從那個時間後開始執行;
第三個參數就是執行的週期,爲long類型。
schedule方法還有一種兩個參數的執行重載,第一個參數仍然是TimerTask,第二個表示爲long的形式表示多長時間後執行一次,爲Date就表示某個時間後執行一次。
Timer就是一個線程,使用schedule方法完成對TimerTask的調度,多個TimerTask可以共用一個Timer,也就是說Timer對象調用一次schedule方法就是創建了一個線程,並且調用一次schedule後TimerTask是無限制的循環下去的,使用Timer的cancel()停止操作。當然同一個Timer執行一次cancel()方法後,所有Timer線程都被終止。
若要在TimerTask中更新主線程UI,鑑於Android編程模型不允許在非主線程中更新主線程UI,因此需要結合Android的Handler實現在Java的TimerTask中更新主線程UI。
二、ScheduledThreadPoolExecutor
public static ExecutorService newScheduledThreadPool(int corePoolSize){
return new ThreadPoolExecutor(corePoolSize,Integer.MAX_VALUE,0L,TimeUnit.SECONDS,new DelayedWorkQueue<Runnable>());
}
ScheduledThreadPoolExecutor核心線程數量固定,非核心線程數沒有限制。主要用於執行定時任務和具有固定週期的重複任務。
參考Java 併發專題 : Timer的缺陷 用ScheduledExecutorService替代
以前在項目中也經常使用定時器,比如每隔一段時間清理項目中的一些垃圾文件,每個一段時間進行數據清洗;然而Timer是存在一些缺陷的,因爲Timer在執行定時任務時只會創建一個線程,所以如果存在多個任務,且任務時間過長,超過了兩個任務的間隔時間,會發生一些缺陷.
1.定義了兩個任務,預計是第一個任務1s後執行,第二個任務3s後執行
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);
}
}
運行結果:
task1 invoked ! 1000
task2 invoked ! 4000
task2實際上是4後才執行,正因爲Timer內部是一個線程,而任務1所需的時間超過了兩個任務間的間隔導致。下面使用ScheduledThreadPool解決這個問題:
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)
{
/**
* 使用工廠方法初始化一個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會停止所有任務的運行:
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);
}
}
上面有兩個任務,任務1拋出一個運行時的異常,任務2週期性的執行某個操作,輸出結果:
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)
由於任務1的一次,任務2也停止運行了。。。下面使用ScheduledExecutorService解決這個問題:
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出現異常時,不影響task2的運行:
task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!
三、AlarmManager
Java的Timer類可以用來計劃需要循環執行的任務。簡單的說,一個Timer內部封裝裝了“一個Thread”和“一個TimerTask隊列”,這個隊列按照一定的方式將任務排隊處理。封裝的Thread在Timer的構造方法調用時被啓動,這個Thread的run方法按照條件去循環這個TimerTask隊列,然後調用TimerTask的run方法。
但是,如果CPU進入了休眠狀態,那麼這個thread將會因爲失去CPU時間片而阻塞,從而造成我們需要的定時任務失效。上述定時任務失效的場景分析:假設定時任務的條件是到了時間xx:yy才能執行,但由於cpu休眠造成線程阻塞的關係,當前系統時間超過了這個時間,即便CPU從終端中恢復了,那麼由於條件不滿足,定時任務在這一次自然就失效了。
解決方案是:它需要用WakeLock讓CPU 保持喚醒狀態。那麼問題就來了,這樣會大量消耗手機電量(CPU喚醒和屏幕喚醒不是同一概念),大大減短手機待機時間。這種方式不能滿足我們的需求。
注:TimerTask實現Runnable接口,但它的run方法只是簡單的在Timer中封裝的Thread中被調用,並未將其放在其它線程中執行。也就是說timer是單線程執行的,那麼問題來了,爲何要這麼費勁的封裝一個Runnable接口又不進行多線程調用?我的答案是,老外只是想要表示它是可執行的方法。
關於休眠,可以參考
Android 關於休眠引發的幾個血案
Android手機休眠後時間不準確的解決方案
AlarmManager是Android 系統封裝的用於管理RTC的模塊,RTC(Real Time Clock) 是一個獨立的硬件時鐘,可以在 CPU 休眠時正常運行,在預設的時間到達時,通過中斷喚醒CPU。這意味着,如果我們用 AlarmManager 來定時執行任務,CPU 可以正常的休眠,只有在需要運行任務時醒來一段很短的時間。
以下參考Android基礎入門教程——10.5 AlarmManager(鬧鐘服務)
核心流程如下:
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
獲得系統提供的AlarmManager服務的對象
Intent設置要啓動的組件:
Intent intent = new Intent(MainActivity.this, ClockActivity.class);
PendingIntent對象設置動作,啓動的是Activity還是Service,又或者是廣播!
PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0, intent, 0);
調用AlarmManager的set( )方法設置單次鬧鐘的鬧鐘類型,啓動時間以及PendingIntent對象!
alarmManager.set(AlarmManager.RTC_WAKEUP,c.getTimeInMillis(), pi);
//10秒鐘後執行一個任務
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);
相關方法:
set(int type,long startTime,PendingIntent pi):一次性鬧鐘
setRepeating(int type,long startTime,long intervalTime,PendingIntent pi):
重複性鬧鐘,和3有區別,3鬧鐘間隔時間不固定
setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi):
重複性鬧鐘,時間不固定
cancel(PendingIntent pi):取消AlarmManager的定時服務
getNextAlarmClock():得到下一個鬧鐘,返回值AlarmManager.AlarmClockInfo
setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)
和set方法類似,這個鬧鐘運行在系統處於低電模式時有效
setExact(int type, long triggerAtMillis, PendingIntent operation):
在規定的時間精確的執行鬧鐘,比set方法設置的精度更高
setTime(long millis):設置系統牆上的時間
setTimeZone(String timeZone):設置系統持續的默認時區
setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation):
設置一個鬧鐘在給定的時間窗觸發。類似於set,該方法允許應用程序精確地控制操作系統調 整鬧鐘觸發時間的程度。
關鍵參數講解:
Type(鬧鐘類型):
有五個可選值:
AlarmManager.ELAPSED_REALTIME:
鬧鐘在手機睡眠狀態下不可用,該狀態下鬧鐘使用相對時間(相對於系統啓動開始),狀態值爲3;
AlarmManager.ELAPSED_REALTIME_WAKEUP
鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘也使用相對時間,狀態值爲2;
AlarmManager.RTC
鬧鐘在睡眠狀態下不可用,該狀態下鬧鐘使用絕對時間,即當前系統時間,狀態值爲1;
AlarmManager.RTC_WAKEUP
表示鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘使用絕對時間,狀態值爲0;
AlarmManager.POWER_OFF_WAKEUP
表示鬧鐘在手機關機狀態下也能正常進行提示功能,所以是5個狀態中用的最多的狀態之一,該狀態下鬧鐘也是用絕對時間,狀態值爲4;不過本狀態好像受SDK版本影響,某些版本並不支持;
startTime:鬧鐘的第一次執行時間,以毫秒爲單位,可以自定義時間,不過一般使用當前時間。 需要注意的是,本屬性與第一個屬性(type)密切相關,如果第一個參數對應的鬧鐘使用的是相對時間 (ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那麼本屬性就得使用相對時間相對於系統啓動時間來說),比如當前時間就表示爲:SystemClock.elapsedRealtime(); 如果第一個參數對應的鬧鐘使用的是絕對時間(RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),
那麼本屬性就得使用絕對時間,比如當前時間就表示 爲:System.currentTimeMillis()。
intervalTime:表示兩次鬧鐘執行的間隔時間,也是以毫秒爲單位.
PendingIntent:綁定了鬧鐘的執行動作,比如發送一個廣播、給出提示等等。
PendingIntent是Intent的封裝類。需要注意的是,
如果是通過啓動服務來實現鬧鐘提示的話,PendingIntent對象的獲取就應該採用Pending.getService (Context c,int i,Intent intent,int j)方法;
如果是通過廣播來實現鬧鐘提示的話,PendingIntent對象的獲取就應該採用 PendingIntent.getBroadcast (Context c,int i,Intent intent,int j)方法;
如果是採用Activity的方式來實現鬧鐘提示的話,PendingIntent對象的獲取就應該採用
PendingIntent.getActivity(Context c,int i,Intent intent,int j)方法。
如果這三種方法錯用了的話,雖然不會報錯,但是看不到鬧鐘提示效果。
下面是一個每隔一小時就會在後臺執行定時任務的服務。
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實現倒計時之使用CountDownTimer
[Android] CountDownTimer 簡單用法與源碼解析
在開發中會經常用到倒計時這個功能,包括給手機發送驗證碼等等,之前我的做法都是使用Handler + Timer +TimerTask來實現,現在發現了這個類,果斷拋棄之前的做法,相信還是有很多人和我一樣一開始不知道Android已經幫我們封裝好了一個叫CountDownTimer的類。CountDownTimer timer = new CountDownTimer(10000,1000);以毫秒爲單位,第一個參數是指從開始調用start()方法到倒計時完成的時候onFinish()方法被調用這段時間的毫秒數,也就是倒計時總的時間;第二個參數表示間隔多少毫秒調用一次
onTick方法,例如間隔1000毫秒。在調用的時候直接使用timer.start();
//共有4個方法,前兩個抽象方法需要重寫
public abstract void onTick(long millisUntilFinished);//固定間隔調用
public abstract void onFinish();//倒計時完成時被調用
public synchronized final void cancel();//取消倒計時,當再次啓動會重新開始倒計時
public synchronized final CountDownTimer start();//啓動倒計時
//該類的成員變量與成員函數均較少,功能實現的關鍵部分在於 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);
}
}
}
};
內部流程
看一個使用例子:
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);
}
/**
* 取消倒計時
* @param v
*/
public void oncancel(View v) {
timer.cancel();
}
/**
* 開始倒計時
* @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) + "秒後可重發");
}
@Override
public void onFinish() {
mTvShow.setEnabled(true);
mTvShow.setText("獲取驗證碼");
}
};
}
五、new Handler().postDelayed()
該方法就是利用我們常說的消息處理器。該方法原理就是在主線程中創建一個Handler消息處理器,然後利用其中的一個postDelayed(Runnable r, long delayMillis)方法,該方法第一個參數需要傳入一個Runnable接口,並實現run()方法,第二個參數就是延遲多少時間將run()方法中的代碼通過一個消息發送給消息隊列,然後在主線程中執行這個消息中的代碼,即是run方法中的代碼,從而實現在主線程中更新界面UI。
貼代碼吧:
new Handler().postDelayed(new Runnable() {//在當前線程(也即主線程中)開啓一個消息處理器,並在3秒後在主線程中執行,從而來更新UI
@Override
public void run() {
//有關更新UI的代碼
}
}, 3000);//3秒後發送