1. 程式人生 > >定時任務,AlarmManager使用

定時任務,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)
設定在某個時間重複執行鬧鐘,但不是間隔固定時間

注意:

  1. 設定鬧鐘使用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進位制計時方法。
  2. 針對同一個PendingIntent,AlarmManager.set()函式不能設定多個alarm。呼叫該函式時,假如已經有old alarm使用相同的PendingIntent,會先取消(cancel)old alarm,然後再設定新的alarm。如何判斷是否已經有相同的PendingIntent,請看下條。
  3. 取消alarm使用AlarmManager.cancel()函式,傳入引數是個PendingIntent例項。該函式會將所有跟這個PendingIntent相同的Alarm全部取消,怎麼判斷兩者是否相同,android使用的是intent.filterEquals(),具體就是判斷兩個PendingIntent的action、data、type、class和category是否完全相同。
  4. 在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.
  5. 在此討論一下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裡面就不用這樣每次都建立新的物件了,直接設定為成員變數(全域性變數)就行了,因為它不會隨著程式的退出而銷燬