概要:本文通過對OPhone內置鬧鈴功能的簡單介紹,來讓開發者朋友們瞭解基於OPhone平臺下客戶/服務模式的編程模型,以及如何使用OPhone系統提供的鬧鈴喚醒功能。與此同時,本文還對PendingIntent做一些簡單的介紹,並通過實例程序來演示如何通過PendingIntent將鬧鐘應用程序和系統鬧鈴服務聯繫起來。(作者:Yang Ai'in)
一、鬧鈴功能
鬧鐘應用程序作爲人們日常常用的基本應用程序之一,其重要性不言而喻。在OPhone系統中鬧鈴服務功能不僅僅對鬧鐘應用程序服務,最重要的是可以利用該鬧鈴服務功能提供的喚醒能力來做定時器。這樣即便應用程序沒有運行或者是沒有啓動的情況下,只要其註冊過鬧鈴,那麼該鬧鈴到時間後,OPhone系統可以自動將該應用程序啓動,這就是所謂的鬧鈴“喚醒“功能。
在OPhone系統中,底層系統提供了兩種類型的時鐘,軟時鐘與硬時鐘,軟時鐘就是我們常說的Timer,硬時鐘就是RTC。系統在正常運行的情況下,Timer工作提供時間服務和鬧鈴提醒,而在系統進入睡眠狀態後,時間服務和鬧鈴提醒由RTC來負責。對於上層應用來說,我們並不需要關心是 timer還是RTC爲我們提供服務,因爲OPhone系統的Framework層把底層細節做了封裝並統一提供API。這個API他的名字就叫 AlarmManager。在OPhone系統中有意思的是對應AlarmManage有一個AlarmManagerServie服務程序,該服務程序纔是正真提供鬧鈴服務的,它主要維護應用程序註冊下來的各類鬧鈴並適時的設置即將觸發的鬧鈴給鬧鈴設備(在OPhone系統中,linux實現的設備名爲”/dev/alarm”),並且一直監聽鬧鈴設備,一旦有鬧鈴觸發或者是鬧鈴事件發生,AlarmManagerServie服務程序就會遍歷鬧鈴列表找到相應的註冊鬧鈴併發出廣播。該服務程序在系統啓動時被系統服務程序system_service啓動並初始化鬧鈴設備(/dev/alarm)。當然,在JAVA層的AlarmManagerService與Linux Alarm驅動程序接口之間還有一層封裝,那就是JNI。
AlarmManager將應用與服務分割開來後,使得應用程序開發者不用關心具體的服務,而是直接通過AlarmManager來使用這種服務。這也許就是客戶/服務模式的好處吧。AlarmManager與AlarmManagerServie之間是通過Binder來通信的,他們之間是多對一的關係。在OPhone系統中,AlarmManage提供了3個接口5種類型的鬧鈴服務:
3個API調用接口:void cancel(PendingIntent operation)
// 取消已經註冊的與參數匹配的鬧鈴
void set(int type, long triggerAtTime, PendingIntent operation)
//註冊一個新的鬧鈴
void setRepeating(int type, long triggerAtTime, long interval, PendingIntent operation)
//註冊一個重複類型的鬧鈴
void setTimeZone(String timeZone)
//設置時區
- 5種鬧鈴類型:
public static final int ELAPSED_REALTIME
//當系統進入睡眠狀態時,這種類型的鬧鈴不會喚醒系統。直到系統下次被喚醒才傳遞它,該鬧鈴所用的時間是相對時間,是從系統啓動後開始計時的,包括睡眠時間,可以通過調用SystemClock.elapsedRealtime()獲得。系統值是3 (0x00000003)。
public static final int ELAPSED_REALTIME_WAKEUP
//能喚醒系統,用法同ELAPSED_REALTIME,系統值是2 (0x00000002) 。
public static final int RTC
//當系統進入睡眠狀態時,這種類型的鬧鈴不會喚醒系統。直到系統下次被喚醒才傳遞它,該鬧鈴所用的時間是絕對時間,所用時間是UTC時間,可以通過調用 System.currentTimeMillis()獲得。系統值是1 (0x00000001) 。
public static final int RTC_WAKEUP
//能喚醒系統,用法同RTC類型,系統值爲 0 (0x00000000) 。
Public static final int POWER_OFF_WAKEUP
//能喚醒系統,它是一種關機鬧鈴,就是說設備在關機狀態下也可以喚醒系統,所以我們把它稱之爲關機鬧鈴。使用方法同RTC類型,系統值爲4(0x00000004)。
二、鬧鐘設置與提醒
我們首先通過一個直觀的UI來感受一下OPhone系統內嵌的鬧鐘程序是如何響應用戶設置和自動提醒的的:(如下4個圖所示)
(圖一)(圖二)
(圖三) (圖四)
上面4圖直觀的告訴了開發者如何使用OPhone內嵌的鬧鐘應用程序,但開發者可能更關心的是通過代碼如何實現這些功能。比如說怎麼設置一個鬧鈴,該設置哪種類型的鬧鈴,以及如何獲得鬧鈴時間已到並提醒用戶。依據這幾個問題,我們在下面的章節中逐步來介紹。
2.1 設置鬧鈴
在第一小節中,我們已經提到過OPhone系統AlarmManagerService提供了兩個設置鬧鈴的API,他們分別是:
void set(int type, long triggerAtTime, PendingIntent operation)
void setRepeating(int type, long triggerAtTime, long interval, PendingIntent operation)
在OPhone 1.5版本中又增加了一個API。
下面的程序演示瞭如何設置一個鬧鈴。
/當設置的鬧鈴觸發時,Ophone系統會廣播一個Intent,當然我們需要在創建一個新的鬧鈴
//時註冊一個鬧鈴事件接收對象AlarmReceiver ,該接收對象也可以通過在//AndroidManifest.xml中發佈,也可以在代碼中動態註冊。
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.SystemClock;
import android.os.Bundle;
import java.util.Calendar;
//創建一個PendingIntent
Intent intent = new Intent(ALARM_ALERT_ACTION);
intent.putExtra(ID, id);
intent.putExtra(TIME, atTimeInMillis);
PendingIntent sender = PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
//獲得AlarmMnager並註冊一個新鬧鈴,
//一次性鬧鈴的設置
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
am.set(AlarmManager.POWER_OFF_WAKEUP, atTimeInMillis, sender);
//重複性鬧鈴的設置
// We want the alarm to go off 30 seconds from now.
long firstTime = SystemClock.elapsedRealtime();
firstTime += 15*1000;
// Schedule the alarm!
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.POWER_OFF_WAKEUP,
firstTime, 15*1000, sender);
//在AndroidManifest.xml中註冊
<receiver android:name="AlarmReceiver">
<intent-filter>
<action android:name="com.android.alarmclock.ALARM_ALERT" />
</intent-filter>
</receiver>
//在應用程序執行過程中註冊
AlarmReceiver mAlarmReceiver;
IntentFilter filter = new IntentFilter();
filter.addAction(“com.android.alarmclock.ALARM_ALERT”);
context.registerReceiver(mAlarmReceiver, filter);
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
}
}
2.2 鬧鈴提醒
在OPhone系統中,當應用程序所設置的鬧鈴時間到了後,OPhone系統中的AlarmManagerService就會從系統底層獲取一個鬧鈴事件並從自己維護的隊列中取出其匹配的鬧鈴,然後通過其應用註冊的PendingIntent把該鬧鈴事件發送回給應用。
PendingIntent.send();
當我們的應用收到該Intent後就會啓動相應的Activity來提醒用戶鬧鈴時間到。
程序代碼如下:
package com.android.alarmclock;
import android.content.Context;
import android.content.Intent;
import android.content.BroadcastReceiver;
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
long now = System.currentTimeMillis();
int id = intent.getIntExtra(Alarms.ID, 0);
long setFor = intent.getLongExtra(Alarms.TIME, 0);
Intent fireAlarm = new Intent(context, AlarmAlert.class);
fireAlarm.putExtra(Alarms.ID, id);
fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//啓動一個新的UI對象來提醒
context.startActivity(fireAlarm);
}
}
三、PendingIntent
在前面的章節中,我們在註冊鬧鈴﹑發送鬧鈴事件的時候,有過一個重要的參數PendingIntent。這個PendingIntent可以說是 Intent的進一步封裝,他既包含了Intent的描述又是Intent行爲的執行(這種定義也許不太嚴格),如果將Intent比作成一個訂單的話,PendingIntent更像是一個下訂單的人,因爲它既要負責將訂單發出去,也要負責訂單發送後的處理,比如發送成功後要準備驗收訂單貨物,發送失敗後要重發還是取消訂單等操作。開發者可以通過調用getActivity(Context, int, Intent, int), getBroadcast(Context, int, Intent, int), getService(Context, int, Intent, int)函數來得到一個PendingIntent實例。
public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags)
通過該函數獲得的PendingIntent將會扮演一個廣播的功能,就像調用 Context.sendBroadcast()函數一樣。當系統通過它要發送一個intent時要採用廣播的形式,並且在該intent中會包含相應的 intent接收對象,當然這個對象我們可以在創建PendingIntent的時候指定,也可以通過ACTION 和CATEGORY等描述讓OPhone系統自動找到該行爲處理對象。
PendingIntent sender = PendingIntent.getBroadcast(AlarmController.this,
0, intent, 0);
Public static PendingIntent getActivity(Context, int, Intent, int)
通過該函數獲得的PendingIntent可以直接啓動新的activity, 就像調用 Context.startActivity(Intent)一樣.不過值得注意的是要想這個新的Activity不再是當前進程存在的Activity 時。我們在intent中必須使用Intent.FLAG_ACTIVITY_NEW_TASK.
實例代碼如下:
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, AlarmService.class), 0);
public static PendingIntent getService(Context context, int requestCode, Intent intent, int flags)
通過該函數獲得的PengdingIntent可以直接啓動新的Service,就像調用Context.startService()一樣。
實例代碼如下:
// Create an IntentSender that will launch our service, to be scheduled
// with the alarm manager.
mAlarmSender = PendingIntent.getService(AlarmService.this,
0, new Intent(AlarmService.this, AlarmService_Service.class), 0);
四、PendingInent與 service 在OPhone系統編程中,一個完整OPhone應用程序可以有4個需要創建的模塊,他們分別是: import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.widget.Toast; //定義一個 Service 對象 public class AlarmService_Service extends Service { NotificationManager mNM; public void onCreate() { //創建一個線程來運行Runnable Thread thr = new Thread(null, mTask, "AlarmService_Service"); thr.start(); } public void onDestroy() { } Runnable mTask = new Runnable() { public void run() { // 通常我們就可以在這裏設計長時間運行的功能, long endTime = System.currentTimeMillis() + 15*1000; while (System.currentTimeMillis() < endTime) { synchronized (mBinder) { try { mBinder.wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } // 停止Service AlarmService_Service.this.stopSelf(); } }; //在編寫Service代碼時,可以不實現onStart,onStop等函數,但一定要實現onBind函數 public IBinder onBind(Intent intent) { return mBinder; } /* 通過該對象可以與客戶端通信 */ private final IBinder mBinder = new Binder() { @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { return super.onTransact(code, data, reply, flags); } }; } 小結:本篇文章主要介紹瞭如何使用AlarmManager的定時喚醒功能,以及各種鬧鈴的含義與API使用實例,希望對讀者朋友們在OPhone應用編程中,對AlarmManager的正確使用起到拋磚引玉的作用。同時我們還引入了一個重要的概念 PendingIntent,通過對PendingIntent參數的解析,相信讀者朋友們對PendingIntent的使用有了一個基本的認識。 |