定時任務,AlarmManager使用
轉載請註明出處:http://blog.csdn.net/wei_chong_chong/article/details/51258336
專案需要:實現一個定時提醒的功能
查閱資料知道,需要使用AlarmManager
AlarmManager介紹:
AlarmManager是Android中常用的一種系統級別的提示服務,在特定的時刻為我們廣播一個指定的Intent。簡單的說就是我們設定一個時間,然後在該時間到來時,AlarmManager為我們廣播一個我們設定的Intent,通常我們使用 PendingIntent,PendingIntent可以理解為Intent的封裝包,簡單的說就是在Intent上在加個指定的動作。在使用Intent的時候,我們還需要在執行startActivity、startService或sendBroadcast才能使Intent有用。而PendingIntent的話就是將這個動作包含在內了。
定義一個PendingIntent物件。
PendingIntent pi = PendingIntent.getBroadcast(this,0,intent,0);
2、AlarmManager的常用方法有三個:
(1)set(int type,long startTime,PendingIntent pi);
該方法用於設定一次性鬧鐘,第一個引數表示鬧鐘型別,第二個引數表示鬧鐘執行時間,第三個引數表示鬧鐘響應動作。
(2)setRepeating(int type,long startTime,long intervalTime,PendingIntent pi);
該方法用於設定重複鬧鐘,第一個引數表示鬧鐘型別,第二個引數表示鬧鐘首次執行時間,第三個引數表示鬧鐘兩次執行的間隔時間,第三個引數表示鬧鐘響應動作。
(3)setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi);
該方法也用於設定重複鬧鐘,與第二個方法相似,不過其兩個鬧鐘執行的間隔時間不是固定的而已。
3、三個方法各個引數詳悉:
(1)int type: 鬧鐘的型別,常用的有5個值:AlarmManager.ELAPSED_REALTIME、 AlarmManager.ELAPSED_REALTIME_WAKEUP、AlarmManager.RTC、 AlarmManager.RTC_WAKEUP、AlarmManager.POWER_OFF_WAKEUP。
AlarmManager.ELAPSED_REALTIME表示鬧鐘在手機睡眠狀態下不可用,該狀態下鬧鐘使用相對時間(相對於系統啟動開始),狀態值為3;
AlarmManager.ELAPSED_REALTIME_WAKEUP表示鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘也使用相對時間,狀態值為2;
AlarmManager.RTC表示鬧鐘在睡眠狀態下不可用,該狀態下鬧鐘使用絕對時間,即當前系統時間,狀態值為1;
AlarmManager.RTC_WAKEUP表示鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘使用絕對時間,狀態值為0;
AlarmManager.POWER_OFF_WAKEUP表示鬧鐘在手機關機狀態下也能正常進行提示功能,所以是5個狀態中用的最多的狀態之一,該狀態下鬧鐘也是用絕對時間,狀態值為4;不過本狀態好像受SDK版本影響,某些版本並不支援;
(2)long startTime: 鬧鐘的第一次執行時間,以毫秒為單位,可以自定義時間,不過一般使用當前時間。需要注意的是,本屬性與第一個屬性(type)密切相關,如果第一個引數對 應的鬧鐘使用的是相對時間(ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那麼本屬性就得使用相對時間(相對於 系統啟動時間來說),比如當前時間就表示為:SystemClock.elapsedRealtime();如果第一個引數對應的鬧鐘使用的是絕對時間 (RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那麼本屬性就得使用絕對時間,比如當前時間就表示 為:System.currentTimeMillis()。
(3)long intervalTime:對於後兩個方法來說,存在本屬性,表示兩次鬧鐘執行的間隔時間,也是以毫秒為單位。
(4)PendingIntent pi: 綁定了鬧鐘的執行動作,比如傳送一個廣播、給出提示等等。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)方法。如果這三種方法錯用了的話,雖然不會報錯,但是看不到鬧鐘提示效果。
屬性或方法名稱 | 說明 |
ELAPSED_REALTIME | 設定鬧鐘時間,從系統啟動開 始 |
ELAPSED_REALTIME_WAKEUP | 同上,如果裝置休眠則喚醒 |
INTERVAL_DAY | 設定鬧鐘,間隔一天 |
INTERVAL_HALF_DAY | 設定鬧鐘,間隔半天 |
INTERVAL_FIFTEEN_MINUTES | 設定鬧鐘,間隔15分鐘 |
INTERVAL_HALF_HOUR | 設定鬧鐘,間隔半個小時 |
INTERVAL_HOUR | 設定鬧鐘,間隔一個小時 |
RTC | 設定鬧鐘時間從系統當前時間開(System.currentTimeMillis()) |
RTC_WAKEUP | 同上,裝置休眠則喚醒 |
set(int type,long triggerAtTime,PendingIntent operation) | 設定在某個時間執行鬧鐘 |
setRepeating(int type,long triggerAtTime,long interval PendingIntent operation) | 設定在某個時間重複執行鬧鐘 |
setInexactRepeating(int type,long triggerAtTime,long interval PendingIntent operation) | 設定在某個時間重複執行鬧鐘,但不是間隔固定時間 |
注意:
- 設定鬧鐘使用AlarmManager.set()函式,它的triggerAtTime引數,如果要用Calendar.getTimesInMillis()獲得,就必須先設定Calendar物件,例如要讓鬧鐘在當天的16:30分啟動,就要設定HOUR_OF_DAY(16)、MINUTE(30)、MILLISECOND(0),特別是HOUR_OF_DAY,我一開始誤用了HOUR,這是12進位制計時方法,HOUR_OF_DAY是24進位制計時方法。
- 針對同一個PendingIntent,AlarmManager.set()函式不能設定多個alarm。呼叫該函式時,假如已經有old alarm使用相同的PendingIntent,會先取消(cancel)old alarm,然後再設定新的alarm。如何判斷是否已經有相同的PendingIntent,請看下條。
- 取消alarm使用AlarmManager.cancel()函式,傳入引數是個PendingIntent例項。該函式會將所有跟這個PendingIntent相同的Alarm全部取消,怎麼判斷兩者是否相同,android使用的是intent.filterEquals(),具體就是判斷兩個PendingIntent的action、data、type、class和category是否完全相同。
- 在AndroidManifest.xml中靜態註冊BroadcastReceiver時,一定使用android:process=":xxx"屬性,因為SDK已註明:If the name assigned to this attribute begins with a colon (':'), a new process, private to the application, is created when it's needed and the broadcast receiver runs in that process.
-
在此討論一下process屬性,它規定了元件(activity, service, receiver等)所在的程序。
通常情況下,沒有指定這個屬性,一個應用所有的元件都執行在應用的預設程序中,程序的名字和應用的包名一致。
比如manifest的package="com.example.helloalarm",則預設程序名就是com.example.helloalarm。
<application>元素的process屬性可以為全部的元件設定一個不同的預設程序。
元件可以override這個預設的程序設定,這樣你的應用就可以是多程序的。
如果你的process屬性以一個冒號開頭,程序名會在原來的程序名之後附加冒號之後的字串作為新的程序名。當元件需要時,會自動建立這個程序。這個程序是應用私有的程序。
如果process屬性以小寫字母開頭,將會直接以屬性中的這個名字作為程序名,這是一個全域性程序,這樣的程序可以被多個不同應用中的元件共享。
使用:
AlarmManager的使用步驟
1)獲得ALarmManager例項 ALarmManager am=(ALarmManager)getSystemService(ALARM_SERVICE);
2)定義一個PendingIntent發出廣播
3)呼叫ALarmManager方法,設定定時或重複提醒
4)取消提醒:
取消alarm使用AlarmManager.cancel()函式,傳入引數是個PendingIntent例項。
該函式會將所有跟這個PendingIntent相同的Alarm全部取消,怎麼判斷兩者是否相同,android使用的是intent.filterEquals(),具體就是判斷兩個PendingIntent的action、data、type、class和category是否完全相同。
例如:
啟動提醒:
// 指定啟動AlarmActivity元件 Intent intent = new Intent(AlarmTest.this, AlarmActivity.class); intent.setAction("111111"); // 建立PendingIntent物件 PendingIntent pi = PendingIntent.getActivity( AlarmTest.this, 0, intent, 0); Calendar c = Calendar.getInstance(); // 根據使用者選擇時間來設定Calendar物件 System.out.println("hourOfDay = " + hourOfDay); System.out.println("minute = " + minute); c.set(Calendar.HOUR, hourOfDay); c.set(Calendar.MINUTE, minute); // 設定AlarmManager將在Calendar對應的時間啟動指定元件 aManager.set(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), pi);取消提醒:對應的action必須要一樣
//用於取消的
aManager= (AlarmManager)getSystemService(ALARM_SERVICE);Intent intent = new Intent(AlarmTest.this, AlarmActivity.class); intent.setAction("111111"); // 建立PendingIntent物件 PendingIntent pendingIntent = PendingIntent.getActivity( AlarmTest.this, 0, intent, 0); aManager.cancel(pendingIntent);
案例一:實現6後秒提醒一次的功能:
下面的程式碼寫在Activity或Service裡面都行
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);//獲取AlarmManager例項
int anHour = 6 * 1000 ; // 6秒
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent intent2 = new Intent(this, AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent2, 0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);//開啟提醒
定義廣播接收器:
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "收到定時廣播", 1).show();
Intent i = new Intent(context, LongRunningService.class);
context.startService(i);
}
}
在配置檔案中註冊
注意:這裡的process一定要寫,內容貌似可以隨便寫
如果設定定時器的程序被殺死之後,定時器事件就不會觸發。而在Android中,系統在需要時會自動終止後臺程序,因此在定時過程中,程序被殺死的可能性是非常之大的,特別是在一些記憶體較少的裝置中,基本上後臺程序所設定的定時器很難被觸發。
為了讓定時器在程序被終止後還能觸發,需要對上述實現做一個小的修改:在AndroidMefest.xml中如下定義廣播接收類:
<receiver android:name=".MyReceiver" android:process=":newinst">
</receiver>
<receiver android:name=".AlarmReceiver"android:process=":remote" >
</receiver>
案例二:定時提醒功能(提醒一次)。實現原理與上面的一樣,只是計算出了指定的時刻到現在的時間差
補充:Calendar的使用方法:
Calendar c=Calendar.getInstance();
c.set(Calendar.YEAR,2016);
c.set(Calendar.MONTH,04);//也可以填數字,0-11,一月為0
c.set(Calendar.DAY_OF_MONTH, 26);
c.set(Calendar.HOUR_OF_DAY, 21);
c.set(Calendar.MINUTE, 40);
c.set(Calendar.SECOND, 0);
//設定時間為 2011年6月28日19點50分0秒
//c.set(2011, 05,28, 19,50, 0);
Calendar myCal1 = Calendar.getInstance();
long nowTime = myCal1.getTimeInMillis();//這是當前的時間
Calendar myCal2 = Calendar.getInstance();
myCal2.set(Calendar.HOUR_OF_DAY,8);
myCal2.set(Calendar.MINUTE,12);
myCal2.set(2016, 03, 27, 9, 40);//日期要減去一,比如:你要設定4月幾號幾點提醒,這裡要填寫03,它預設是從0開始
long shutDownTime = myCal2.getTimeInMillis();
long d = shutDownTime - nowTime;
System.out.println(shutDownTime+"-"+nowTime+"=" +d);
Calendar myCal1 = Calendar.getInstance();
long nowTime = myCal1.getTimeInMillis();//這是當前的時間
Calendar myCal2 = Calendar.getInstance();
// myCal.set(Calendar.HOUR_OF_DAY,hour);
// myCal.set(Calendar.MINUTE,minutes);
myCal2.set(2016, 03, 27, 11, 49);
long shutDownTime = myCal2.getTimeInMillis();
Intent intent3=new Intent(this,AlarmReceiver2.class);
intent3.putExtra("time", shutDownTime-nowTime+"");
PendingIntent pi3=PendingIntent.getBroadcast(this, 0, intent3,0);
//設定一個PendingIntent物件,傳送廣播
AlarmManager am=(AlarmManager)getSystemService(ALARM_SERVICE);
//獲取AlarmManager物件
long triggerAtTime = SystemClock.elapsedRealtime() + shutDownTime-nowTime;
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime , pi3);
案例三:重複提醒功能:5秒後提醒第一次,以後每5秒提醒一次//重複提醒
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 5*1000+ SystemClock.elapsedRealtime(), 5*1000, pi3);
終極案例:設定每天8:00提醒
package activity.MyWeather;
import java.util.Calendar;
import java.util.TimeZone;
import service.Alarm_Service;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class AlarmActivity extends Activity implements OnClickListener{
private Button alarm_bt_YES;
private Button alarm_bt_quxiao;
AlarmManager am;
//PendingIntent pi;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alarm);
alarm_bt_YES = (Button) findViewById(R.id.alarm_bt_YES);
alarm_bt_quxiao = (Button) findViewById(R.id.alarm_bt_quxiao);
alarm_bt_quxiao.setOnClickListener(this);
alarm_bt_YES.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
int id = v.getId();
if(id == R.id.alarm_bt_YES){
Toast.makeText(this, "已開啟提醒", 0).show();
Intent intent=new Intent(this,Alarm_Service.class);
intent.setAction("activity.MyWeathe.alarm");
PendingIntent pi=PendingIntent.getService(this, 0, intent,0);
long firstTime = SystemClock.elapsedRealtime(); //獲取系統當前時間
long systemTime = System.currentTimeMillis();//java.lang.System.currentTimeMillis(),它返回從 UTC 1970 年 1 月 1 日午夜開始經過的毫秒數。
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.setTimeZone(TimeZone.getTimeZone("GMT+8")); // 這裡時區需要設定一下,不然會有8個小時的時間差
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.HOUR_OF_DAY, 8);//設定為8:00點提醒
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
//選擇的定時時間
long selectTime = calendar.getTimeInMillis(); //計算出設定的時間
// 如果當前時間大於設定的時間,那麼就從第二天的設定時間開始
if(systemTime > selectTime) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
selectTime = calendar.getTimeInMillis();
}
long time = selectTime - systemTime;// 計算現在時間到設定時間的時間差
long my_Time = firstTime + time;//系統 當前的時間+時間差
// 進行鬧鈴註冊
am=(AlarmManager)getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, my_Time, AlarmManager.INTERVAL_DAY, pi);
}
else if(id == R.id.alarm_bt_quxiao){
Toast.makeText(this, "已關閉提醒", 0).show();
am = (AlarmManager)getSystemService(ALARM_SERVICE);
Intent intent=new Intent(this,Alarm_Service.class);
intent.setAction("activity.MyWeathe.alarm");
PendingIntent pi=PendingIntent.getService(this, 0, intent,0);
am.cancel(pi);
}
}
}
注意在Activity中取消alarm時
一定要再重新建立所有的物件包括:Intent,PendingIntent,AlarmManager物件
am = (AlarmManager)getSystemService(ALARM_SERVICE);
Intent intent=new Intent(AlarmActivity.this,Alarm_Service.class);
intent.setAction("activity.MyWeather.alarm");
pi=PendingIntent.getService(AlarmActivity.this, 0, intent,0);
am.cancel(pi);
因為在退出該Activity時或關閉該應用程式時,該Activity會被銷燬裡面的所有變數,全域性變數都會被銷燬。。。。
而在Service裡面就不用這樣每次都建立新的物件了,直接設定為成員變數(全域性變數)就行了,因為它不會隨著程式的退出而銷燬