鬧鐘已經(jīng)學(xué)過(guò)一段時(shí)間了,但是對(duì)它了解的不是很多,由于最近開(kāi)發(fā)的一個(gè)小應(yīng)用會(huì)用到這個(gè)功能,所以重新學(xué)習(xí)了一下,以便能在以后忘記的時(shí)候記起來(lái),也方便其他人學(xué)習(xí)
實(shí)現(xiàn)鬧鐘有很多中方式,比如可以使用Handler+Timer(需依賴(lài)應(yīng)用程序生命周期),AlarmManager等,而我們需要時(shí)間服務(wù)不依賴(lài)應(yīng)用程序而存在,即應(yīng)用程序啟動(dòng)服務(wù),但是即使關(guān)閉應(yīng)用程序,時(shí)間服務(wù)依然運(yùn)行,這就需要使用AlarmManager了
?
首先需要了解一下鬧鐘需要用得到的知識(shí)點(diǎn)
1、實(shí)現(xiàn)鬧鐘需要用到AlarmManager來(lái)實(shí)現(xiàn),這個(gè)類(lèi)實(shí)現(xiàn)系統(tǒng)警告服務(wù),可以設(shè)定一個(gè)時(shí)間來(lái)完成指定的事情,只要在程序中設(shè)置了警報(bào)服務(wù),就可以通過(guò)調(diào)用onReceive()方法執(zhí)行你要做的事情,即使是待機(jī)狀態(tài),也不會(huì)影響運(yùn)行。
可以通過(guò)Context.getSystemService()方法來(lái)獲得該服務(wù)。
獲得AlarmManager對(duì)象代碼
AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
?
2、常量
?
int | ELAPSED_REALTIME | Alarm time in?SystemClock.elapsedRealtime() ?(time since boot, including sleep). |
int | ELAPSED_REALTIME_WAKEUP | Alarm time in?SystemClock.elapsedRealtime() ?(time since boot, including sleep), which will wake up the device when it goes off. |
long | INTERVAL_DAY | |
long | INTERVAL_FIFTEEN_MINUTES | Available inexact recurrence intervals recognized by?setInexactRepeating(int, long, long, PendingIntent)
|
long | INTERVAL_HALF_DAY | |
long | INTERVAL_HALF_HOUR | |
long | INTERVAL_HOUR | |
int | RTC | Alarm time in?System.currentTimeMillis() ?(wall clock time in UTC). |
int | RTC_WAKEUP | Alarm time in?System.currentTimeMillis() ?(wall clock time in UTC), which will wake up the device when it goes off. |
?
AlarmManager.RTC,硬件鬧鐘,不喚醒手機(jī)(也可能是其它設(shè)備)休眠;當(dāng)手機(jī)休眠時(shí)不發(fā)射鬧鐘。
AlarmManager.RTC_WAKEUP,硬件鬧鐘,當(dāng)鬧鐘發(fā)躰時(shí)喚醒手機(jī)休眠;
AlarmManager.ELAPSED_REALTIME,真實(shí)時(shí)間流逝鬧鐘,不喚醒手機(jī)休眠;當(dāng)手機(jī)休眠時(shí)不發(fā)射鬧鐘。
AlarmManager.ELAPSED_REALTIME_WAKEUP,真實(shí)時(shí)間流逝鬧鐘,當(dāng)鬧鐘發(fā)躰時(shí)喚醒手機(jī)休眠;
?
RTC鬧鐘和ELAPSED_REALTIME最大的差別就是前者可以通過(guò)修改手機(jī)時(shí)間觸發(fā)鬧鐘事件,后者要通過(guò)真實(shí)時(shí)間的流逝,即使在休眠狀態(tài),時(shí)間也會(huì)被計(jì)算。
?
?
3、Intent與PendingIntent的區(qū)別及含義
這里少不了用這兩個(gè)東西,有點(diǎn)經(jīng)驗(yàn)的都明白是個(gè)什么東西,但是他倆的具體區(qū)別確有時(shí)候搞不清楚,所幸放到一起,以便以后方便查詢(xún)
Intent是意圖的意思,它是一個(gè)馬上執(zhí)行的東西,比如Activity跳轉(zhuǎn),執(zhí)行
Intent intent = new Intent();
intent.setClass(Test1.this, Test2.class);
startActivity(intent);
那么Intent會(huì)馬上從Test1跳轉(zhuǎn)到Test2
?
PendingIntent中Pending意思為即將發(fā)生的,待定。也就是不確定什么時(shí)候會(huì)發(fā)生,可能是未來(lái)的某個(gè)時(shí)間發(fā)生,可以理解為一中延遲的Intent,但是它一般會(huì)由別的程序觸發(fā)。
?
Intent intent = new Intent(this, Test2.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
?
PendingIntent可以封裝Activity,BroadcastReceiver,Service,與Intent有區(qū)別的是PendingIntent可以脫離應(yīng)用程序而存在
?
其他參考:
http://wayfarer.iteye.com/blog/586159
http://gundumw100.iteye.com/blog/825537
http://blog.sina.com.cn/s/blog_5da93c8f0100u7pb.html
http://www.eoeandroid.com/forum.php?mod=viewthread&tid=68063
?
4、公共方法
?
public void?cancel?(PendingIntent?operation)
取消警報(bào),可以是任何類(lèi)型(2中的那4中類(lèi)型),比如取消鬧鐘
?
public void?set?(int type, long triggerAtTime,?PendingIntent?operation)
設(shè)置警報(bào)
參數(shù):
第一個(gè)參數(shù):One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC or RTC_WAKEUP.
第二個(gè)參數(shù):Time the alarm should go off, using the appropriate clock (depending on the alarm type).
第三個(gè)參數(shù):Action to perform when the alarm goes off; typically comes from?IntentSender.getBroadcast()
.
go off這里是睡眠的意思
?
public void?setInexactRepeating?(int type, long triggerAtTime, long interval,?PendingIntent?operation)
設(shè)置不精準(zhǔn)重復(fù)周期
不精準(zhǔn)重復(fù)周期的意思是:比如有很多類(lèi)似的警報(bào),那么當(dāng)設(shè)置的類(lèi)型范圍比較大的時(shí)候,這些警報(bào)就會(huì)合并為一個(gè)警報(bào),這樣可以不用每次都執(zhí)行警報(bào),是一種節(jié)能型
參數(shù)的意思可以參見(jiàn)setRepeating部分
?
public void?setRepeating?(int type, long triggerAtTime, long interval,?PendingIntent?operation)
設(shè)置精準(zhǔn)重復(fù)周期
這里有四個(gè)參數(shù)
第一個(gè)參數(shù):即警報(bào)的類(lèi)型,一般取值是AlarmManager.RTC和AlarmManager.RTC_WAKEUP,如果為RTC表示為一個(gè)正常的定時(shí)器,如果是RTC_WAKEUP則除了有定時(shí)器外還可以有震動(dòng)或者響鈴。另外就是RTC在手機(jī)睡眠的時(shí)候不發(fā)射警報(bào),而RTC_WAKEUP則在睡眠的時(shí)候也會(huì)發(fā)射警報(bào)
第二個(gè)參數(shù):第一次運(yùn)行時(shí)要等待的時(shí)間,也就是執(zhí)行延遲時(shí)間,單位是毫秒。這個(gè)參數(shù)有些難理解,有問(wèn)題歡迎奧手們糾正
我的理解:
假如設(shè)置為System.currentTimeMillis()+(10*1000),表示系統(tǒng)會(huì)在設(shè)置警報(bào)10秒后第一次發(fā)送警報(bào),也就是第一次響鈴。這樣警報(bào)服務(wù)會(huì)在10秒后進(jìn)入休眠狀態(tài),但是它依賴(lài)于第一個(gè)參數(shù)的類(lèi)型
英文原意:Time the alarm should first go off, using the appropriate clock (depending on the alarm type).
第三個(gè)參數(shù):表示執(zhí)行的時(shí)間間隔,單位是毫秒,也就是每過(guò)多久發(fā)射一次警報(bào),一般都是以天為單位
第四個(gè)參數(shù):一個(gè)PendingIntent對(duì)象,即到時(shí)間后要執(zhí)行的操作
public void?setTime?(long millis)
設(shè)置系統(tǒng)時(shí)間,需要權(quán)限(android.permission.SET_TIME)
?
public void?setTimeZone?(String?timeZone)
設(shè)置系統(tǒng)默認(rèn)時(shí)區(qū),需要權(quán)限(android.permission.SET_TIME_ZONE)
?
一、一個(gè)鬧鐘例子
這個(gè)例子在網(wǎng)上都可以查得到,但是具體來(lái)源已經(jīng)找不到了,所以就直接采用了
這里需要注意的是TimePickerDialog中的五個(gè)參數(shù)含義,可以參考:
http://blog.csdn.net/yang_hui1986527/article/details/6839342
先看圖
下面的是第一次執(zhí)行時(shí),由于設(shè)置的第一延遲執(zhí)行時(shí)間是System.currentTimeMillis(),所以會(huì)馬上執(zhí)行
下面的是過(guò)了一分鐘后執(zhí)行的效果,因?yàn)樵O(shè)置的周期是1分鐘,不過(guò)一般設(shè)置的周期都是以天數(shù)為準(zhǔn)的?
源代碼
?
package com.loulijun.demo2; import java.util.Calendar; import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.TimePickerDialog; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.TimePicker; public class Demo2Activity extends Activity { private TextView tv = null; private Button setTime,cancelTime; private Calendar c = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tv = (TextView)findViewById(R.id.tv); setTime = (Button)findViewById(R.id.setAlarm); cancelTime = (Button)findViewById(R.id.cancelAlarm); //得到日歷實(shí)例,主要是為了下面的獲取時(shí)間 c = Calendar.getInstance(); setTime.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View arg0) { c.setTimeInMillis(System.currentTimeMillis()); int hour = c.get(Calendar.HOUR_OF_DAY); int minute = c.get(Calendar.MINUTE); new TimePickerDialog(Demo2Activity.this, minute, new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { //是設(shè)置日歷的時(shí)間,主要是讓日歷的年月日和當(dāng)前同步 c.setTimeInMillis(System.currentTimeMillis()); //設(shè)置小時(shí)分鐘,秒和毫秒都設(shè)置為0 c.set(Calendar.HOUR_OF_DAY, hourOfDay); c.set(Calendar.MINUTE, minute); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); Intent intent = new Intent(Demo2Activity.this, AlarmReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(Demo2Activity.this, 0, intent, 0); //得到AlarmManager實(shí)例 AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE); //根據(jù)當(dāng)前時(shí)間預(yù)設(shè)一個(gè)警報(bào) am.set(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), pi); /** * 第一個(gè)參數(shù)是警報(bào)類(lèi)型;第二個(gè)參數(shù)是第一次執(zhí)行的延遲時(shí)間,可以延遲,也可以馬上執(zhí)行;第三個(gè)參數(shù)是重復(fù)周期為一天 * 這句話(huà)的意思是設(shè)置鬧鈴重復(fù)周期,也就是執(zhí)行警報(bào)的間隔時(shí)間 */ // am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+(60*1000), // (24*60*60*1000), pi); am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+(10*1000), 30000, pi); String msg = "設(shè)置鬧鐘時(shí)間為"+format(hourOfDay)+":"+format(minute); tv.setText(msg); } }, hour, minute, true).show(); //上面的TimePickerDialog中的5個(gè)參數(shù)參考:http://blog.csdn.net/yang_hui1986527/article/details/6839342 } }); cancelTime.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(Demo2Activity.this, AlarmReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(Demo2Activity.this, 0, intent, 0); AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE); //取消警報(bào) am.cancel(pi); tv.setText("鬧鐘取消"); } }); } private String format(int x) { String s = ""+x; if(s.length() == 1) s = "0"+s; return s; } }
?廣播服務(wù):
?
package com.loulijun.demo2; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.media.MediaPlayer; import android.widget.Toast; public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent arg1) { Toast.makeText(context, "鬧鐘時(shí)間到", Toast.LENGTH_SHORT).show(); } }?
這里需要在A(yíng)ndroidManifest.xml中注冊(cè)廣播
?
<receiver android:name=".AlarmReceiver" android:process=":remote"/>
? ---------------------------華麗的分割線(xiàn)--------------------------------------------------------------------
?
下面再看一個(gè)例子,其實(shí)這個(gè)例子是基于上面修改的,增加了用SharedPreferences來(lái)保存時(shí)間參數(shù),如果到了時(shí)間則播放音樂(lè),播放音樂(lè)是在一個(gè)Service中,用戶(hù)可以在取消鬧鈴的時(shí)候取消音樂(lè)
?
源代碼
?
package com.loulijun.demo2; import java.util.Calendar; import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.TimePickerDialog; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.TimePicker; public class Demo2Activity extends Activity { private TextView tv = null; private Button setTime,cancelTime; private Calendar c = null; private SharedPreferences sp; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tv = (TextView)findViewById(R.id.tv); setTime = (Button)findViewById(R.id.setAlarm); cancelTime = (Button)findViewById(R.id.cancelAlarm); //該配置文件用于存放當(dāng)前設(shè)置的鬧鐘時(shí)間信息 sp = getSharedPreferences("alarm_record", Activity.MODE_PRIVATE); //得到日歷實(shí)例,主要是為了下面的獲取時(shí)間 c = Calendar.getInstance(); setTime.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View arg0) { c.setTimeInMillis(System.currentTimeMillis()); int hour = c.get(Calendar.HOUR_OF_DAY); int minute = c.get(Calendar.MINUTE); new TimePickerDialog(Demo2Activity.this, minute, new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { //是設(shè)置日歷的時(shí)間,主要是讓日歷的年月日和當(dāng)前同步 c.setTimeInMillis(System.currentTimeMillis()); //設(shè)置小時(shí)分鐘,秒和毫秒都設(shè)置為0 c.set(Calendar.HOUR_OF_DAY, hourOfDay); c.set(Calendar.MINUTE, minute); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); Intent intent = new Intent(Demo2Activity.this, AlarmReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(Demo2Activity.this, 0, intent, 0); //得到AlarmManager實(shí)例 AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE); //根據(jù)當(dāng)前時(shí)間預(yù)設(shè)一個(gè)警報(bào) am.set(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), pi); /** * 第一個(gè)參數(shù)是警報(bào)類(lèi)型;第二個(gè)參數(shù)是第一次執(zhí)行的延遲時(shí)間,可以延遲,也可以馬上執(zhí)行;第三個(gè)參數(shù)是重復(fù)周期為一天 * 這句話(huà)的意思是設(shè)置鬧鈴重復(fù)周期,也就是執(zhí)行警報(bào)的間隔時(shí)間 */ // am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+(60*1000), // (24*60*60*1000), pi); am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+(10*1000), 30000, pi); String msg = hourOfDay+":"+minute; tv.setText("當(dāng)前設(shè)置的鬧鐘時(shí)間:"+msg); sp.edit().putString(msg, msg).commit(); } }, hour, minute, true).show(); //上面的TimePickerDialog中的5個(gè)參數(shù)參考:http://blog.csdn.net/yang_hui1986527/article/details/6839342 } }); cancelTime.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(Demo2Activity.this, AlarmReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(Demo2Activity.this, 0, intent, 0); AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE); //取消警報(bào) am.cancel(pi); tv.setText("鬧鐘取消"); //取消鬧鐘的同時(shí)取消音樂(lè) stopService(new Intent("com.loulijun.demo2.MUSIC")); } }); } }
?然后是廣播接收器
?
package com.loulijun.demo2; import java.util.Calendar; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.widget.Toast; public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent arg1) { //獲取配置文件中的信息,如果相同則播放音樂(lè) SharedPreferences sp = context.getSharedPreferences("alarm_record", Activity.MODE_PRIVATE); String hour = String.valueOf(Calendar.getInstance().get(Calendar.HOUR_OF_DAY)); String minute = String.valueOf(Calendar.getInstance().get(Calendar.MINUTE)); String time = sp.getString(hour+":"+minute, null); if(time!=null) { Toast.makeText(context, "鬧鐘時(shí)間到", Toast.LENGTH_SHORT).show(); //啟動(dòng)Service播放音樂(lè) context.startService(new Intent("com.loulijun.demo2.MUSIC")); } } }
?然后是播放音樂(lè)的服務(wù)
?
package com.loulijun.demo2; import android.app.Service; import android.content.Intent; import android.media.MediaPlayer; import android.os.IBinder; public class MusicService extends Service { private MediaPlayer player; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } public void onStart(Intent intent, int startId) { super.onStart(intent, startId); player = MediaPlayer.create(this, R.raw.test); player.start(); } public void onDestroy() { super.onDestroy(); player.stop(); } }
?需要在A(yíng)ndroidManifest.xml中增加如下的信息,一個(gè)是注冊(cè)廣播,一個(gè)是注冊(cè)服務(wù)
?
<receiver android:name=".AlarmReceiver" android:process=":remote"/> <service android:name=".MusicService"> <intent-filter> <action android:name="com.loulijun.demo2.MUSIC"> </action> <category android:name="android.intent.category.default"/> </intent-filter> </service>?
?
其他參考:
http://www.cnblogs.com/tara/archive/2011/06/09/2076043.html
開(kāi)機(jī)啟動(dòng)Service:http://www.eoeandroid.com/forum.php?mod=viewthread&tid=8195
http://www.eoeandroid.com/forum.php?mod=viewthread&tid=109957
AlarmUI到內(nèi)核執(zhí)行流程:http://www.cnblogs.com/Hwangroid/archive/2011/10/13.html
http://our2848884.blog.163.com/blog/static/14685483420114225055804/
-
本文附件下載:
- Demo2.zip (2.4 MB)