Android 定時任務的8種實現方法
功能分析
功能描述
每隔5秒,打印一句,我愛你中國。
環境分析
我們知道,Android中分主線程(UI線程)和子線程,子線程無法操作UI的改變,我們目前不考慮UI問題,也不考慮線程通信問題,就考慮有多少方法可以實現上述功能。
功能分解
每需要實現一個功能的時候,我們在寫代碼前分析一下實現功能所需要的步驟,這樣有利於我們用更清晰明了的邏輯來實現功能。根據功能描述,要實現此功能,可以拆分為兩步:
- 打印 "我愛你中國" 這句話。
打印代碼不需要討論,我們就是用System.out.println("我愛你中國");這句就好。
- 每隔5秒的實現。
每隔5秒,需要通知打印的代碼執行,所以我們需要實現一個帶有定時發送指令或者定時可以執行邏輯的功能。
方案制定
基於上面的分析,所以我們需要在Android、java中找到可以帶有定時邏輯的相關API,從原生java SDK到Android SDK自帶API來一一列舉:
以下是Java SDK
- while循環+sleep
- 遞歸+sleep
- Timer、TimerTask
- ScheduledExecutorService(帶有定時任務的線程池)
//以下是Android SDK
- Handler循環發消息
- Handler的postDelayed方法
- BroadcastReceiver循環自發廣播
- AlarmManger+BroadcastReceiver定時發送廣播
功能實現
經過上面的分析,我們已經制定了8種實現方案,接下來我們依次寫出實現代碼並分析優缺點。
while循環+sleep
- 代碼實現
while(true){ System.out.println("我愛你中國"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } }
- 分析
優點:代碼簡單,邏輯清晰
缺點:1、sleep會阻塞線程,也有被打斷的風險
2、while(true)下面的代碼無法執行,野路子
適用場景:間隔時間較短(秒級的),循環執行次數較少(有退出機制或者用for循環),執行邏輯簡單
遞歸+sleep
- 代碼實現
public void print() { System.out.println("我愛你中國" ); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } print();//遞歸調用自身 }
- 分析
優點:代碼簡單,邏輯清晰
缺點:1、sleep會阻塞線程,也有被打斷的風險
2、遞歸次數過多會出現java.lang.StackOverflowError堆棧溢出的異常,所以嚴格來說不算定時任務
適用場景:間隔時間較短(秒級的),循環執行次數較少(有退出機制或者用for循環)
Timer、TimerTask
- 代碼實現
public class TimerTest { static class MyTimerTask extends TimerTask { public void run() { System.out.println("我愛你中國"); } } public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new MyTimerTask(), 0, 5000); } }
- 分析
優點:代碼正宗,沒有野路子,純正的定時任務,純java SDK,單獨線程執行,時間周期可以長些
缺點:1、Timer還有一個傳入Date的方法,此方法是基於絕對時間,系統時間改變對應的時間也會改變
2、Timer線程不捕獲異常,執行過程中出現異常就會終止Timer任務
3、手機關屏之后,CPU進入休眠,Timer無法喚醒CPU(據說)
適用場景:簡單的定時任務,對於時間要求不是很精確,執行過程中處理好異常的捕獲。可用timer.cancel()取消
ScheduledExecutorService
- 代碼實現
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("我愛你中國"); } },0,5,TimeUnit.SECONDS);
- 分析
優點:ScheduledExecutorService是一個線程池,多任務處理時效率高,基於相對時間,看起來高大上。
缺點:1、取消時需要打斷線程池的運行
2、和外界的通信不太好處理
3、Android中適用度不高(android中會用AlarmManger+Service+BarocastReceiver替代)
適用場景:適合那種長期、定期執行的任務
Handler循環發消息
- 代碼實現
Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); System.out.println("我愛你中國"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } handler.sendEmptyMessage(0); } }; //調用 handler.sendEmptyMessage(0);
- 分析
優點:純Android SDK,邏輯清晰
缺點:這種循環執行不適用於復雜任務,不適用於多次循環的,偽定時,sleep缺點上面說過了,Handler依賴其所在線程
適用場景:單純這么寫是沒有適用場景,Handler就老老實實的實現消息分發吧
Handler的postDelayed方法
- 代碼實現
Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); System.out.println("我愛你中國"); handler.sendEmptyMessageDelayed(0,5000); } }; //調用 handler.sendEmptyMessageDelayed(0,5000);
- 分析
上面以上,沒啥分析的
BroadcastReceiver循環自發廣播
- 代碼實現
public static final String TEST_ACTION = "XXX.XXX.XXX" + "_TEST_ACTION"; BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (TEST_ACTION.equals(action)) { System.out.println("我愛你中國"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } context.sendBroadcast(intent); } } }; //調用 IntentFilter filter = new IntentFilter(); filter.addAction(TEST_ACTION); registerReceiver(receiver, filter); //取消 unregisterReceiver(receiver);
- 分析
優點:Android SDK的代碼,廣播監聽機制
缺點:單純這么寫就是缺點
適用場景:BroadcastReceiver適用於接受廣播,執行一定的邏輯,可以根據注冊方式不同適用不同的場景。
AlarmManger+BroadcastReceiver
- 代碼實現
public class MyService extends Service { int TIME_INTERVAL = 5000; // 這是5s PendingIntent pendingIntent; AlarmManager alarmManager; @Override public void onCreate() { super.onCreate(); IntentFilter intentFilter = new IntentFilter(TEST_ACTION); registerReceiver(receiver, intentFilter); alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); Intent intent = new Intent(); intent.setAction(TEST_ACTION); pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//6.0低電量模式需要使用該方法觸發定時任務 alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//4.4以上 需要使用該方法精確執行時間 alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent); } else {//4。4一下 使用老方法 alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent); } } @Override public void onDestroy() { super.onDestroy(); unregisterReceiver(receiver); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } public static final String TEST_ACTION = "XXX.XXX.XXX" + "_TEST_ACTION"; BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (TEST_ACTION.equals(action)) { System.out.println("我愛你中國"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent); } } } }; }
- 分析
優點:這是標准的Android觸發定時任務的方式,依賴的是Android系統服務,有效喚醒
缺點:1、都說標准了,哪還有啥缺點(AlarmManager喚醒type字段可以區分是按照手機開機時間還是系統時間,系統時間受系統時間修改的影響,同時也有不喚醒CPU的type)
2、4.4以下,4.4到6.0,6.0以上這個類變動的較多,需要區分版本使用。
適用場景:Android應用中常見的定時任務,鬧鍾等等場景。
以上!
作者:AnonyPer
鏈接:https://www.jianshu.com/p/67328d9d7b65
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。