Android定時任務及循環任務基礎詳情


Android開發中,定時執行任務的3種實現方法:

一、采用Handler與線程的sleep(long)方法(不建議使用,Jva的實現方式)

二、采用Handler的postDelayed(Runnable, long)方法(最簡單的android實現)

三、采用Handler與timer及TimerTask結合的方法(比較多的任務時建議使用)

Android消息機制

首先來了解一下Android的消息處理機制

即Handlerd的運行機制,handler的運行需要底層的MessageQueue和Looper的支撐。MessageQueue(消息隊列),它的內部存儲了一些消息,以隊列的形式對外提供插入和刪除的操作(實際為單鏈表存儲)。Looper(消息循環),配合MessageQueue實現實現消息的不斷入隊和出隊工作。

一個關系圖:

這里寫圖片描述

通過Handler可以很容易將任務切換到其他線程中執行,以減少主線程的負擔,因此Handler常用來進行UI更新。這里只是簡單的進行一些概述。

當然,現在已經有更好的消息處理辦法了,了解handler和Asynctask可以更好的理解Android內部消息的處理機制。
推薦:EventBus,高度解耦,代碼簡潔明了,有興趣的可以自行參考使用。


1.采用Handle與線程的sleep(long)方法

1) 定義一個Handler類,用於處理接受到的Message。

?
1
2
3
4
5
6
Handler handler = new Handler() { 
     public void handleMessage(Message msg) { 
         // 要做的事情 
         super .handleMessage(msg); 
    
};
2) 新建一個實現Runnable接口的線程類,如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyThread implements Runnable { 
     @Override 
     public void run() { 
         // TODO Auto-generated method stub 
         while ( true ) { 
             try
                 Thread.sleep( 10000 ); // 線程暫停10秒,單位毫秒 
                 Message message = new Message(); 
                 message.what = 1
                 handler.sendMessage(message); // 發送消息 
             } catch (InterruptedException e) { 
                 e.printStackTrace(); 
            
         }  }  }

3) 在需要啟動線程的地方加入下面語句:

?
1
new Thread( new MyThread()).start();

分析:純正的java原生實現,在sleep結束后,並不能保證競爭到cpu資源,這也就導致了時間上必定>=10000的精度問題。

2.采用Handler的postDelayed(Runnable, long)方法

1)定義一個Handler類

?
1
2
3
4
5
6
7
8
9
10
11
Handler handler= new Handler(); 
 
 
Runnable runnable= new Runnable() { 
     @Override 
     public void run() { 
         // TODO Auto-generated method stub 
         //要做的事情 
         handler.postDelayed( this , 2000 ); 
    
};

2) 啟動與關閉計時器

?
1
2
handler.postDelayed(runnable, 2000 ); //每兩秒執行一次runnable. 
handler.removeCallbacks(runnable);

分析:嗯,看起蠻不錯,實現上也簡單了,和sleep想必還不會產生阻塞,注意等待和間隔的區別。

3.采用Handler與timer及TimerTask結合的方法

1) 定義定時器、定時器任務及Handler句柄

?
1
2
3
4
5
6
7
8
9
10
private final Timer timer = new Timer(); 
private TimerTask task; 
Handler handler = new Handler() { 
     @Override 
     public void handleMessage(Message msg) { 
         // TODO Auto-generated method stub 
         // 要做的事情 
         super .handleMessage(msg); 
    
};

2) 初始化計時器任務

?
1
2
3
4
5
6
7
8
9
task = new TimerTask() { 
     @Override 
     public void run() { 
         // TODO Auto-generated method stub 
         Message message = new Message(); 
         message.what = 1
         handler.sendMessage(message); 
    
}; 

3) 啟動和關閉定時器

?
1
2
timer.schedule(task, 2000 , 3000 );  
timer.cancel();

此外,Timer也可以配合runOnUiThread實現,如下

?
1
2
3
4
5
6
7
8
9
10
11
12
private TimerTask mTimerTask = new TimerTask() {
        @Override
        public void run() {
 
            runOnUiThread( new Runnable() {
                @Override
                public void run() {
                    //處理延時任務
                }
            });
        }
    };

分析:timer.schedule(task, 2000, 3000);意思是在2秒后執行第一次,之后每3000秒在執行一次。timer不保證精確度且在無法喚醒cpu,不適合后台任務的定時。

采用AlarmManger實現長期精確的定時任務

AlarmManager的常用方法有三個:

set(int type,long startTime,PendingIntent pi);//一次性setExact(int type, long triggerAtMillis, PendingIntent operation)//一次性的精確版setRepeating(int type,long startTime,long intervalTime,PendingIntent
pi);//精確重復setInexactRepeating(int type,long startTime,long
intervalTime,PendingIntent pi);//非精確,降低功耗

type表示鬧鍾類型,startTime表示鬧鍾第一次執行時間,long intervalTime表示間隔時間,PendingIntent表示鬧鍾響應動作


對以上各個參數的詳細解釋
鬧鍾的類型:

AlarmManager.ELAPSED_REALTIME:休眠后停止,相對開機時間AlarmManager.ELAPSED_REALTIME_WAKEUP:休眠狀態仍可喚醒cpu繼續工作,相對開機時間AlarmManager.RTC:同1,但時間相對於絕對時間AlarmManager.RTC_WAKEUP:同2,但時間相對於絕對時間AlarmManager.POWER_OFF_WAKEUP:關機后依舊可用,相對於絕對時間

startTime:
鬧鍾的第一次執行時間,以毫秒為單位,一般使用當前時間。

SystemClock.elapsedRealtime():系統開機至今所經歷時間的毫秒數System.currentTimeMillis():1970 年 1 月 1 日 0 點至今所經歷時間的毫秒數

intervalTime:執行時間間隔。

PendingIntent :
PendingIntent用於描述Intent及其最終的行為.,這里用於獲取定時任務的執行動作。

利用AlarmManger+Service+BarocastReceiver實現5s一次打印操作

服務類:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<code> public class HorizonService extends Service {
     @Override
     public IBinder onBind(Intent intent) {
         return null ;
     }
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         new Thread( new Runnable() {
             @Override
             public void run() {
                 Log.d( "TAG" , "打印時間: " + new Date().
                         toString());
             }
         }).start();
         AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
         int five = 5000 ; // 這是5s
         long triggerAtTime = SystemClock.elapsedRealtime() + five;
         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);
     }
}</code>

廣播接受器

?
1
2
3
4
5
6
7
<code> public class AlarmReceiver extends BroadcastReceiver {
     @Override
     public void onReceive(Context context, Intent intent) {
         Intent i = new Intent(context, HorizonService. class );
         context.startService(i);
     }
}</code>

啟動定時任務:

?
1
2
<code>Intent intent = new Intent( this ,HorizonService. class );
startService(intent);</code>

效果Demo:
這里寫圖片描述

本例通過廣播接收器和服務的循環調用實現了無限循環的效果,當然,你也可以直接利用setRepeating實現同樣的效果。

注意:不要忘了在manifest文件中注冊服務和廣播接收器。

AlarmManager的取消方法:AlarmManger.cancel();

分析:該方式可喚醒cpu甚至實現精確定時,適用於配合service在后台執行一些長期的定時行為。

前言

項目中總是會因為各種需求添加各種定時任務,所以就打算小結一下Android中如何實現定時任務,下面的解決方案的案例大部分都已在實際項目中實踐,特此列出供需要的朋友參考,如果有什么使用不當或者存在什么問題,歡迎留言指出!直接上干貨!

解決方案

普通線程sleep的方式實現定時任務

創建一個thread,然后讓它在while循環里一直運行着,通過sleep方法來達到定時任務的效果,這是最常見的,可以快速簡單地實現。但是這是java中的實現方式,不建議使用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<code>    public class ThreadTask { 
     public static void main(String[] args) { 
         final long timeInterval = 1000
         Runnable runnable = new Runnable() { 
             public void run() { 
                 while ( true ) { 
                     System.out.println( "execute task" ); 
                     try
                         Thread.sleep(timeInterval); 
                     } catch (InterruptedException e) { 
                         e.printStackTrace(); 
                    
                
            
         }; 
         Thread thread = new Thread(runnable); 
         thread.start(); 
    
} </code>

Timer實現定時任務

和普通線程+sleep(long)+Handler的方式比,優勢在於

可以控制TimerTask的啟動和取消第一次執行任務時可以指定delay的時間。

在實現時,Timer類調度任務,TimerTask則是通過在run()方法里實現具體任務(然后通過Handler與線程協同工作,接收線程的消息來更新主UI線程的內容)。

Timer實例可以調度多任務,它是線程安全的。當Timer的構造器被調用時,它創建了一個線程,這個線程可以用來調度任務。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<code>   /**
     * start Timer
     */
     protected synchronized void startPlayerTimer() {
         stopPlayerTimer();
         if (playTimer == null ) {
             playTimer = new PlayerTimer();
             Timer m_musictask = new Timer();
             m_musictask.schedule(playTimer, 5000 , 5000 );
         }
     }
 
     /**
      * stop Timer
      */
     protected synchronized void stopPlayerTimer() {
         try {
             if (playTimer != null ) {
                 playTimer.cancel();
                 playTimer = null ;
             }
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 
     public class PlayerTimer extends TimerTask {
 
         public PlayerTimer() {
         }
 
         public void run() {
             //execute task
         }
     }</code>

然而Timer是存在一些缺陷的

Timer在執行定時任務時只會創建一個線程,所以如果存在多個任務,且任務時間過長,超過了兩個任務的間隔時間,會發生一些缺陷如果Timer調度的某個TimerTask拋出異常,Timer會停止所有任務的運行Timer執行周期任務時依賴系統時間,修改系統時間容易導致任務被掛起(如果當前時間小於執行時間)

注意:

Android中的Timer和java中的Timer還是有區別的,但是大體調用方式差不多Android中需要根據頁面的生命周期和顯隱來控制Timer的啟動和取消
java中Timer:


這里寫圖片描述


Android中的Timer:


這里寫圖片描述

ScheduledExecutorService實現定時任務

ScheduledExecutorService是從JDK1.5做為並發工具類被引進的,存在於java.util.concurrent,這是最理想的定時任務實現方式。
相比於上面兩個方法,它有以下好處:

相比於Timer的單線程,它是通過線程池的方式來執行任務的,所以可以支持多個任務並發執行 ,而且彌補了上面所說的Timer的缺陷可以很靈活的去設定第一次執行任務delay時間 提供了良好的約定,以便設定執行的時間間隔

簡例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<code> public class ScheduledExecutorServiceTask
     public static void main(String[] args)
    
         final TimerTask task = new TimerTask() 
        
             @Override 
             public void run() 
            
                //execute task 
            
         };  
         ScheduledExecutorService pool = Executors.newScheduledThreadPool( 1 ); 
         pool.scheduleAtFixedRate(task, 0 , 1000 , TimeUnit.MILLISECONDS); 
 
    
}</code>

實際案例背景:

應用中多個頁面涉及比賽信息展示(包括比賽狀態,比賽結果),因此需要實時更新這些數據

思路分析:

多頁面多接口刷新
就是每個需要刷新的頁面基於自身需求基於特定接口定時刷新每個頁面要維護一個定時器,然后基於頁面的生命周期和顯隱進行定時器的開啟和關閉(保證資源合理釋放)而且這里的刷新涉及到是刷新局部數據還是整體數據,刷新整體數據效率會比較低,顯得非常笨重 多頁面單接口刷新
接口給出一組需要實時進行刷新的比賽數據信息,客戶端基於id進行統一過濾匹配通過單例封裝統一定時刷新回調接口(注意內存泄露的問題,頁面銷毀時關閉ScheduledExecutorService )需要刷新的item統一調用,入口唯一,方便維護管理,擴展性好局部刷新,效率高

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<code> public class PollingStateMachine implements INetCallback {
 
     private static volatile PollingStateMachine instance = null ;
     private ScheduledExecutorService pool;
     public static final int TYPE_MATCH = 1 ;
 
     private Map matchMap = new HashMap<>();
     private List<weakreference<view>> list = new ArrayList<>();
     private Handler handler;
 
     // private constructor suppresses
     private PollingStateMachine() {
         defineHandler();
         pool = Executors.newSingleThreadScheduledExecutor();
         pool.scheduleAtFixedRate( new Runnable() {
             @Override
             public void run() {
                 doTasks();
             }
         }, 0 , 10 , TimeUnit.SECONDS);
     }
 
     private void doTasks() {
         ThreadPoolUtils.execute( new PollRunnable( this ));
     }
 
     public static PollingStateMachine getInstance() {
         // if already inited, no need to get lock everytime
         if (instance == null ) {
             synchronized (PollingStateMachine. class ) {
                 if (instance == null ) {
                     instance = new PollingStateMachine();
                 }
             }
         }
         return instance;
     }
     public <view extends = "" view= "" > void subscibeMatch(VIEW view, OnViewRefreshStatus onViewRefreshStatus) {
         subscibe(TYPE_MATCH,view,onViewRefreshStatus);
     }
 
     private <view extends = "" view= "" > void subscibe( int type, VIEW view, OnViewRefreshStatus onViewRefreshStatus) {
         view.setTag(onViewRefreshStatus);
         if (type == TYPE_MATCH) {
             onViewRefreshStatus.update(view, matchMap);
         }
         for (WeakReference<view> viewSoftReference : list) {
             View textView = viewSoftReference.get();
             if (textView == view) {
                 return ;
             }
         }
         WeakReference<view> viewSoftReference = new WeakReference<view>(view);
         list.add(viewSoftReference);
     }
 
     public void updateView( final int type) {
         Iterator<weakreference<view>> iterator = list.iterator();
         while (iterator.hasNext()) {
             WeakReference<view> next = iterator.next();
             final View view = next.get();
             if (view == null ) {
                 iterator.remove();
                 continue ;
             }
             Object tag = view.getTag();
             if (tag == null || !(tag instanceof OnViewRefreshStatus)) {
                 continue ;
             }
             final OnViewRefreshStatus onViewRefreshStatus = (OnViewRefreshStatus) tag;
             handler.post( new Runnable() {
                 @Override
                 public void run() {
                     if (type == TYPE_MATCH) {
                         onViewRefreshStatus.update(view, matchMap);
                     }
                 }
             });
 
         }
     }
 
     public void clear() {
         pool.shutdown();
         instance = null ;
     }
 
     private Handler defineHandler() {
         if (handler == null && Looper.myLooper() == Looper.getMainLooper()) {
             handler = new Handler();
         }
         return handler;
     }
 
     @Override
     public void onNetCallback( int type, Map msg) {
         if (type == TYPE_MATCH) {
             matchMap=msg;
         }
         updateView(type);
     }
}</view></weakreference<view></view></view></view></view></view></weakreference<view></code>

需要刷新的item調用

?
1
2
3
4
5
6
<code>PollingStateMachine.getInstance().subscibeMatch(tvScore, new OnViewRefreshStatus<scoreitem, textview= "" >(matchViewItem.getMatchID()) {
             @Override
             public void OnViewRefreshStatus(TextView view, ScoreItem scoreItem) {
                 //刷新處理
                 }
         });</scoreitem,></code>

網絡數據回調接口

?
1
2
<code> public interface INetCallback {
     void onNetCallback( int type,Map msg);}</code>

刷新回調接口:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<code> public abstract class OnViewRefreshStatus<value, extends = "" view= "" > {
     private static final String TAG = OnViewRefreshStatus. class .getSimpleName();
     private long key;
 
     public OnViewRefreshStatus( long key) {
         this .key = key;
     }
 
     public long getKey() {
         return key;
     }
 
     public void update( final VIEW view, Map< long , value= "" > map) {
         final VALUE value = map.get(key);
 
         if (value == null ) {
             return ;
         }
         OnViewRefreshStatus(view, value);
 
     }
 
 
     public abstract void OnViewRefreshStatus(VIEW view, VALUE value);
 
}</ long ,></value,></code>

Handler實現定時任務

通過Handler延遲發送消息的形式實現定時任務。

這里通過一個定時發送socket心跳包的案例來介紹如何通過Handler完成定時任務

案例背景

由於移動設備的網絡的復雜性,經常會出現網絡斷開,如果沒有心跳包的檢測, 客戶端只會在需要發送數據的時候才知道自己已經斷線,會延誤,甚至丟失服務器發送過來的數據。 所以需要提供心跳檢測

下面的代碼只是用來說明大體實現流程

WebSocket初始化成功后,就准備發送心跳包每隔30s發送一次心跳創建的時候初始化Handler,銷毀的時候移除Handler消息隊列

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<code> private static final long HEART_BEAT_RATE = 30 * 1000 ; //目前心跳檢測頻率為30s
private Handler mHandler;
private Runnable heartBeatRunnable = new Runnable() {
         @Override
         public void run() {
             // excute task
             mHandler.postDelayed( this , HEART_BEAT_RATE);
         }
     };
public void onCreate() {
      //初始化Handler
     }
 
//初始化成功后,就准備發送心跳包
public void onConnected() {
         mHandler.removeCallbacks(heartBeatRunnable);
         mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);
     }
 
public void onDestroy() {
         mHandler.removeCallbacks(heartBeatRunnable)
     }</code>

注意:這里開啟的runnable會在這個handler所依附線程中運行,如果handler是在UI線程中創建的,那么postDelayed的runnable自然也依附在主線程中。

AlarmManager實現精確定時操作

我們使用Timer或者handler的時候會發現,delay時間並沒有那么准。如果我們需要一個嚴格准時的定時操作,那么就要用到AlarmManager,AlarmManager對象配合Intent使用,可以定時的開啟一個Activity,發送一個BroadCast,或者開啟一個Service.

案例背景

在比賽開始前半個小時本地定時推送關注比賽的提醒信息

AndroidManifest.xml中聲明一個全局廣播接收器

?
1
2
3
4
5
<code>  <receiver android:exported= "false" android:name= ".receiver.AlarmReceiver" >
             <intent-filter>
                 
             </action></intent-filter>
         </receiver></code>

接收到action為IntentConst.Action.matchRemind的廣播就展示比賽
提醒的Notification

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<code> public class AlarmReceiver extends BroadcastReceiver {
     private static final String TAG = "AlarmReceiver" ;
 
     @Override
     public void onReceive(Context context, Intent intent) {
         if (intent == null ) {
             return ;
         }
         String action = intent.getAction();
         if (TextUtils.equals(action, IntentConst.Action.matchRemind)) {
             //通知比賽開始
             long matchID = intent.getLongExtra(Net.Param.ID, 0 );
             showNotificationRemindMe(context, matchID);
         }
 
     }
}</code>

AlarmUtil提供設定鬧鈴和取消鬧鈴的兩個方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<code> public class AlarmUtil {
     private static final String TAG = "AlarmUtil" ;
 
     public static void controlAlarm(Context context, long startTime, long matchId, Intent nextIntent) {
         AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         PendingIntent pendingIntent = PendingIntent.getBroadcast(context, ( int ) matchId, nextIntent,
                 PendingIntent.FLAG_ONE_SHOT);
         alarmManager.set(AlarmManager.RTC_WAKEUP, startTime, pendingIntent);
     }
 
     public static void cancelAlarm(Context context, String action, long matchId) {
         Intent intent = new Intent(action);
         PendingIntent sender = PendingIntent.getBroadcast(
                 context, ( int ) matchId, intent, 0 );
 
         // And cancel the alarm.
         AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         alarmManager.cancel(sender);
     }}</code>

關注比賽的時候,會設置intent的action為IntentConst.Action.matchRemind,會根據比賽的開始時間提前半小時設定鬧鈴時間,取消關注比賽同時取消鬧鈴

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<code>   if (isSetAlarm) {
             long start_tm = matchInfo.getStart_tm();
             long warmTime = start_tm - 30 * 60 ;
 
             Intent intent = new Intent(IntentConst.Action.matchRemind);
             intent.putExtra(Net.Param.ID, matchInfo.getId());
             AlarmUtil.controlAlarm(context, warmTime * 1000 ,matchInfo.getId(), intent);
 
             Gson gson = new Gson();
             String json = gson.toJson(matchInfo);
             SportDao.getInstance(context).insertMatchFollow(eventID, matchInfo.getId(), json, matchInfo.getType());
         } else {
             AlarmUtil.cancelAlarm(context, IntentConst.Action.matchRemind,matchInfo.getId());
             SportDao.getInstance(context).deleteMatchFollowByID(matchInfo.getId());
         }</code>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM