第十一章 Android 內核驅動——Alarm


11.1  基本原理
Alarm 鬧鍾是 android 系統中在標准 RTC 驅動上開發的一個新的驅動,提供了一個定時器 用於把設備從睡眠狀態喚醒,當然因為它是依賴 RTC 驅動的,所以它同時還可以為系統提 供一個掉電下還能運行的實時時鍾。 
當系統斷電時,主板上的 rtc 芯片將繼續維持系統的時間,這樣保證再次開機后系統的時間 不會錯誤。當系統開始時,內核從 RTC 中讀取時間來初始化系統時間,關機時便又將系統 時間寫回到 rtc 中,關機階段將有主板上另外的電池來供應 rtc 計時。Android 中的 Alarm 在設備處於睡眠模式時仍保持活躍,它可以設置來喚醒設備。

 

傲游截圖20160315135326  
上圖為android系統中 alarm 和 rtc 驅動的框架。Alarm依賴於rtc 驅動框架,但它不是一個 rtc 驅動,主要還是實現定時鬧鍾的功能。相關源代碼在 kernel/drivers/rtc/alarm.c 和 drivers/rtc/alarm_dev.c。 
其中 alarm.c 文件實現的是所有 alarm 設備的通用性操作,它創建了一個設備 class,而
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
alarm_dev.c 則創建具體的 alarm 設備,注冊到該設備 class 中。 alarm.c 還實現了與 interface.c 的接口,即建立了與具體 rtc 驅動和rtc 芯片的聯系。 alarm_dev.c在 alarm.c 基礎包裝了一層, 主要是實現了標准的 miscdevice 接口,提供給應用層調用。 
可以這樣概括:alarm.c 實現的是機制和框架,alarm_dev.c 則是實現符合這個框架的設備驅 動,alarm_dev.c 相當於在底層硬件 rtc 鬧鍾功能的基礎上虛擬了多個軟件鬧鍾。
11.2  關鍵數據結構
 alarm 定義在 include/linux/android_alarm.h 中。 struct alarm {       struct rb_node             node;       enum android_alarm_type  type;       ktime_t                     softexpires; //最早的到期時間       ktime_t                     expires; //絕對到期時間       void (*function)(struct alarm *); //當到期時系統回調該函數 }; 
這個結構體代表 alarm 設備,所有的 alarm 設備按照它們過期時間的先后被組織成一 個紅黑樹,alarm.node 即紅黑樹的節點,alarm 設備通過這個變量插入紅黑樹。 alarm.type 是類型,android 中一共定義了如下 5 種類型,在現在的系統中每種類型只有一個設備。 
enum android_alarm_type {       /* return code bit numbers or set alarm arg */       ANDROID_ALARM_RTC_WAKEUP,       ANDROID_ALARM_RTC,       ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP,       ANDROID_ALARM_ELAPSED_REALTIME,       ANDROID_ALARM_SYSTEMTIME,       ANDROID_ALARM_TYPE_COUNT,       /* return code bit numbers */       /* ANDROID_ALARM_TIME_CHANGE = 16 */ }; 
 alarm_queue struct alarm_queue {       struct rb_root    alarms; //紅黑樹的根       struct rb_node   *first; //指向第一個 alarm device,即最早到時的       struct hrtimer    timer; //內核定時器,android 利用它來確定 alarm 過期時間       ktime_t             delta; //是一個計算 elasped realtime 的修正值       bool                stopped;       ktime_t             stopped_time; }; 
這個結構體用於將前面的 struct alarm 表示的設備組織成紅黑樹。它是基於內核定時器 來實現 alarm 的到期鬧鈴的。
11.3  關鍵代碼分析
 alarm_dev.c
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
該文件依賴於 alarm.c 提供的框架,實現了與應用層交互的功能,具體說就是暴露出 miscdevice 的設備接口。Alarm_dev.c 定義了幾個全局變量: 
每種類型一個 alarm 設備,android 目前創建了 5 個 alarm 設備。 static struct alarm alarms[ANDROID_ALARM_TYPE_COUNT]; 
wake lock 鎖,當加鎖時,阻止系統進 suspend 狀態。 static struct wake_lock alarm_wake_lock; 
標志位,alarm 設備是否被打開。 static int alarm_opened; 
標志位,alarm 設備是否就緒。所謂就緒是指該 alarm 設備的鬧鈴時間到達,但原本等待在 該 alarm 設備上的進程還未喚醒,一旦喚醒,該標志清零。 static uint32_t alarm_pending; 
標志位,表示 alarm 設備是否 enabled,表示該設備設置了鬧鈴時間(並且鬧鈴時間還未到) , 一旦鬧鈴時間到了,該標志清零。 static uint32_t alarm_enabled; 
標志位,表示原先等待該 alarm 的進程被喚醒了(它們等待的 alarm 到時了)。 static uint32_t wait_pending;         該文件提供的主要函數有: 1,模塊初始化和 exit 函數:alarm_dev_init 和 alarm_dev_exit 2,模塊 miscdevice 標准接口函數:alarm_open、alarm_release 和 alarm_ioctl 3, alarm 定時時間到時候的回調函數:alarm_triggered 
alarm_dev_init 初始化函數調用 misc_register 注冊一個 miscdevice。 static int __init alarm_dev_init(void){  int err;  int i; 
err = misc_register(&alarm_device);  if (err)   return err; 
for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++)   alarm_init(&alarms[i], i, alarm_triggered);  wake_lock_init(&alarm_wake_lock, WAKE_LOCK_SUSPEND, "alarm"); 
return 0; } 
該設備稱為 alarm_device,定義如下: static struct miscdevice alarm_device = {  .minor = MISC_DYNAMIC_MINOR,  .name = "alarm",  .fops = &alarm_fops, }; 
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
對應的 file operations 為 alarm_fops,定義為: static const struct file_operations alarm_fops = {  .owner = THIS_MODULE,  .unlocked_ioctl = alarm_ioctl,  .open = alarm_open,  .release = alarm_release, }; 
然后為每個 alarm device 調用 alarm_init 初始化,這個函數代碼在 alarm.c 中,如下: void alarm_init(struct alarm *alarm,                    enum android_alarm_type type,                     void (*function)(struct alarm *)){  RB_CLEAR_NODE(&alarm->node);  alarm->type = type;  alarm->function = function;  pr_alarm(FLOW, "created alarm, type %d, func %pF\n", type, function); } 
就是初始化 alarm 結構體,設置其回調函數為 alarm_triggered。最后調用 wake_lock_init 初 始化 alarm_wake_lock,它是 suspend 型的。alarm_triggered 是回調函數,當定時鬧鈴的時間 到了,alarm_timer_triggered 函數會調用該函數(詳細請看 alarm.c 的 alarm_timer_triggered 函數)。 
static void alarm_triggered(struct alarm *alarm){  unsigned long flags;  uint32_t alarm_type_mask = 1U << alarm->type; 
pr_alarm(INT, "alarm_triggered type %d\n", alarm->type);  spin_lock_irqsave(&alarm_slock, flags);  if (alarm_enabled & alarm_type_mask) {   wake_lock_timeout(&alarm_wake_lock, 5 * HZ);   alarm_enabled &= ~alarm_type_mask;   alarm_pending |= alarm_type_mask;   wake_up(&alarm_wait_queue);  }  spin_unlock_irqrestore(&alarm_slock, flags); } 
這個函數里調用 wake_lock_timeout 對全局 alarm_wake_lock(超時鎖,超時時間是 5 秒) 加鎖,禁止對應的 alarm 設備。喚醒所有等待在該 alarm 設備上的進程。這時,如果 AP 層 呼叫 ioctl(fd, ANDROID_ALARM_WAIT),會返回表示等到 alarm 的返回值(這個會在 AlarmManagerSevice.java 中細述)。 
alarm_ioctl 定義了以下命令: ANDROID_ALARM_CLEAR 清除 alarm,即 deactivate 這個 alarm ANDROID_ALARM_SET_OLD 設置 alarm 鬧鈴時間 ANDROID_ALARM_SET 同上 ANDROID_ALARM_SET_AND_WAIT_OLD 設置 alarm 鬧鈴時間並等待這個 alarm ANDROID_ALARM_SET_AND_WAIT 同上 ANDROID_ALARM_WAIT 等待 alarm ANDROID_ALARM_SET_RTC 設置 RTC 時間 ANDROID_ALARM_GET_TIME 讀取 alarm 時間,根據 alarm 類型又分四種情況
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
 
 alarm.c 該文件完成主要功能有:  創建一個 alarm class,所有 alarm 設備都屬於這個類;  注冊了 platform driver,提供 suspend 和 resume 支持;  實 現 了 一 系 列 函 數 , 包 括 alarm_init , alarm_start_range , alarm_cancel , alarm_timer_triggered 函數等。 
Alarm.c 的初始化函數 alarm_driver_init 如下: static int __init alarm_driver_init(void){  int err;  int i; 
for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) {   hrtimer_init(&alarms[i].timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);   alarms[i].timer.function = alarm_timer_triggered;  }  hrtimer_init(&alarms[ANDROID_ALARM_SYSTEMTIME].timer,        CLOCK_MONOTONIC, HRTIMER_MODE_ABS);  alarms[ANDROID_ALARM_SYSTEMTIME].timer.function = alarm_timer_triggered;    err = platform_driver_register(&alarm_driver);  if (err < 0)   goto err1;  wake_lock_init(&alarm_rtc_wake_lock, WAKE_LOCK_SUSPEND, "alarm_rtc");  rtc_alarm_interface.class = rtc_class;  err = class_interface_register(&rtc_alarm_interface);  if (err < 0)   goto err2; 
return 0; 
err2:  wake_lock_destroy(&alarm_rtc_wake_lock);  platform_driver_unregister(&alarm_driver); err1:  return err; } 
該函數初始化 5 個 alarm device 相關聯的 hrtimer 定時器,設置 hrtimer 定時器的回調函數為 alarm_timer_triggered 函數,再注冊一個 plateform driver 和 class interface。 如果設置了鬧 鈴時間,則內核通過 hrtimer 定時器來跟蹤是否到時間,到時后會觸發調用 hrtimer 的處理 函數 alarm_timer_triggered。alarm_timer_triggered 的 code 如下: 
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
static enum hrtimer_restart alarm_timer_triggered(struct hrtimer *timer){  struct alarm_queue *base;  struct alarm *alarm;  unsigned long flags;  ktime_t now; 
spin_lock_irqsave(&alarm_slock, flags); 
base = container_of(timer, struct alarm_queue, timer);  now = base->stopped ? base->stopped_time : hrtimer_cb_get_time(timer);  now = ktime_sub(now, base->delta); 
pr_alarm(INT, "alarm_timer_triggered type %d at %lld\n",   base - alarms, ktime_to_ns(now)); 
while (base->first) {   alarm = container_of(base->first, struct alarm, node);   if (alarm->softexpires.tv64 > now.tv64) {    pr_alarm(FLOW, "don't call alarm, %pF, %lld (s %lld)\n",     alarm->function, ktime_to_ns(alarm->expires),     ktime_to_ns(alarm->softexpires));    break;   }   base->first = rb_next(&alarm->node);   rb_erase(&alarm->node, &base->alarms);   RB_CLEAR_NODE(&alarm->node);   pr_alarm(CALL, "call alarm, type %d, func %pF, %lld (s %lld)\n",    alarm->type, alarm->function,    ktime_to_ns(alarm->expires),    ktime_to_ns(alarm->softexpires));   spin_unlock_irqrestore(&alarm_slock, flags);   alarm->function(alarm);   spin_lock_irqsave(&alarm_slock, flags);  }  if (!base->first)   pr_alarm(FLOW, "no more alarms of type %d\n", base - alarms);  update_timer_locked(base, true);  spin_unlock_irqrestore(&alarm_slock, flags);  return HRTIMER_NORESTART; } 
它會輪詢紅黑樹中的所有 alarm 節點,符合條件的節點會執行 alarm.function(alarm),指向 alarm_dev.c 的 alarm_triggered 函數。因為我們在執行 alarm_dev.c 的 alarm_init 時,把每個 alarm 節點的 function 設置成了 alarm_triggered。 
請注意這兩個函數的區別,alarm_triggered 和 alarm_timer_triggered。前者是 rtc 芯片的 alarm 中斷的回調函數,后者是 android alarm_queue->timer 到時的回調函數。 
上面說到,alarm_driver_init 注冊了一個類接口 class_interface_register(&rtc_alarm_interface) 
rtc_alarm_interface 的 code 如下: static struct class_interface rtc_alarm_interface = {  .add_dev = &rtc_alarm_add_device,  .remove_dev = &rtc_alarm_remove_device, }; 
在 rtc_alarm_add_device 中,注冊了一個 rtc 中斷 rtc_irq_register(rtc,&alarm_rtc_task)
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
static int rtc_alarm_add_device(struct device *dev,                                       struct class_interface *class_intf){  int err;  struct rtc_device *rtc = to_rtc_device(dev); 
mutex_lock(&alarm_setrtc_mutex); 
if (alarm_rtc_dev) {   err = -EBUSY;   goto err1;  } 
alarm_platform_dev = platform_device_register_simple("alarm", -1, NULL, 0);  if (IS_ERR(alarm_platform_dev)) {   err = PTR_ERR(alarm_platform_dev);   goto err2;  }  err = rtc_irq_register(rtc, &alarm_rtc_task);  if (err)   goto err3;  alarm_rtc_dev = rtc;  pr_alarm(INIT_STATUS, "using rtc device, %s, for alarms", rtc->name);  mutex_unlock(&alarm_setrtc_mutex); 
return 0; 
err3:  platform_device_unregister(alarm_platform_dev); err2: err1:  mutex_unlock(&alarm_setrtc_mutex);  return err; } 
中斷的回調函數為 alarm_triggered_func: static struct rtc_task alarm_rtc_task = {  .func = alarm_triggered_func }; 
static void alarm_triggered_func(void *p){  struct rtc_device *rtc = alarm_rtc_dev;  if (!(rtc->irq_data & RTC_AF))   return;  pr_alarm(INT, "rtc alarm triggered\n");  wake_lock_timeout(&alarm_rtc_wake_lock, 1 * HZ); }          當硬件 rtc chip 的 alarm 中斷發生時,系統會調用 alarm_triggered_func 函數。 alarm_triggered_func 的功能很簡單,wake_lock_timeout 鎖住 alarm_rtc_wake_lock 1 秒。因 為這時,alarm 會進入 alarm_resume,lock 住 alarm_rtc_wake_lock 以防止 alarm 在此時進入 suspend。 
 AlarmManager.java,該文件提供的接口主要有:  設置鬧鍾 public void set(int type, long triggerAtTime, PendingIntent operation);  設置周期鬧鍾。 public void setRepeating(int type, long triggerAtTime, long interval,PendingIntent operation);  取消鬧鍾 public void cancel(PendingIntent operation);
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
上面 3 個函數分別會呼叫到 AlarmManagerSevice.java 以下三個函數: public void set(int type, long triggerAtTime, PendingIntent operation); public void setRepeating(int type, long triggerAtTime,                               long interval,PendingIntent operation); public void remove(PendingIntent operation); 
AlarmManagerSevice.java 通過 JNI 機制可以呼叫 com_android_server_AlarmManagerService.cpp 透出的幾個接口。 
 AlarmManagerSevice.java 有關接口的 code 如下: private native int init(); private native void close(int fd); private native void set(int fd, int type, long seconds, long nanoseconds); private native int waitForAlarm(int fd); private native int setKernelTimezone(int fd, int minuteswest); 
 com_android_server_AlarmManagerService.cpp 有關接口的對應 code 如下: static JNINativeMethod sMethods[] = {   /* name, signature, funcPtr */ {"init", "()I", (void*)android_server_AlarmManagerService_init}, {"close", "(I)V", (void*)android_server_AlarmManagerService_close}, {"set", "(IIJJ)V", (void*)android_server_AlarmManagerService_set}, {"waitForAlarm", "(I)I", (void*)android_server_AlarmManagerService_waitForAlarm}, {"setKernelTimezone", "(II)I", (void*)android_server_AlarmManagerService_setKernelTimezone}, }; 
當 AP 呼叫 AlarmManager.java 的 set 或 setRepeating 函數時,最終會呼叫 com_android_server_AlarmManagerService.cpp 的 static void android_server_AlarmManagerService_set (JNIEnv* env, jobject obj,                              jint fd, jint type, jlong seconds, jlong nanoseconds) 在此函數中,會執行 ioctl(fd, ANDROID_ALARM_SET(type), &ts); 然后會呼叫到 alarm-dev.c 中 alarm_ioctl 中,接着 alarm-dev.c 會往它的紅黑樹中增加一個 alarm 節點。 
在 AlarmManagerService 開始的時候,會啟動一個 AlarmThread。在這個 AlarmThread 中有一 個 while 循環去執行 waitForAlarm 這個動作,這個函數最終通過 JNI 機制呼叫到 com_android_server_AlarmManagerService.cpp 的 static jint android_server_AlarmManagerService_waitForAlarm(JNIEnv* env,                                                               jobject obj, jint fd){ #if HAVE_ANDROID_OS  int result = 0;    do  {   result = ioctl(fd, ANDROID_ALARM_WAIT);  } while (result < 0 && errno == EINTR);    if (result < 0) {         LOGE("Unable to wait on alarm: %s\n", strerror(errno));         return 0;     }     
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
    return result; #endif }         從 code 中可以看到,實際上它是在不斷地執行 ioctl (fd,ANDROID_ALARM_WAIT),上面說 到,當鬧鍾到期時,alarm.c 中的 alarm_timer_triggered 函數會調用 alarm_triggered,這時, AP 層在呼叫 ioctl ( fd,ANDROID_ALARM_WAIT)時,會返回表示等到 alarm 的返回值。 
所以當鬧鍾到期時,AlarmThread 的 waitForAlarm 會返回一個值。接着通過執行 triggerAlarmsLocked,把幾種類型的鬧鍾列表中符合要求的 alarm 添加到 triggerList 中,然后 用 alarm.operation.send 發送消息,調起小鬧鍾程序。 
AlarmThread 的 code 如下: private class AlarmThread extends Thread {  public AlarmThread() {   super("AlarmManager");  } 
public void run() {   while (true)   {    int result = waitForAlarm(mDescriptor);                     ArrayList<Alarm> triggerList = new ArrayList<Alarm>();                     if ((result & TIME_CHANGED_MASK) != 0) {     remove(mTimeTickSender);     mClockReceiver.scheduleTimeTickEvent();     Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);     intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);     mContext.sendBroadcast(intent);    }      synchronized (mLock) {     final long nowRTC = System.currentTimeMillis();     final long nowELAPSED = SystemClock.elapsedRealtime();     if (localLOGV) Slog.v(      TAG, "Checking for alarms... rtc=" + nowRTC      + ", elapsed=" + nowELAPSED); 
    if ((result & RTC_WAKEUP_MASK) != 0)      triggerAlarmsLocked(mRtcWakeupAlarms, triggerList, nowRTC);                          if ((result & RTC_MASK) != 0)      triggerAlarmsLocked(mRtcAlarms, triggerList, nowRTC);                          if ((result & ELAPSED_REALTIME_WAKEUP_MASK) != 0)      triggerAlarmsLocked(mElapsedRealtimeWakeupAlarms,            triggerList, nowELAPSED);                          if ((result & ELAPSED_REALTIME_MASK) != 0)      triggerAlarmsLocked(mElapsedRealtimeAlarms,            triggerList, nowELAPSED);                          // now trigger the alarms     Iterator<Alarm> it = triggerList.iterator();     while (it.hasNext()) {      Alarm alarm = it.next();      try {       if (localLOGV) Slog.v(TAG, "sending alarm " + alarm);       alarm.operation.send(mContext, 0,
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
       mBackgroundIntent.putExtra(         Intent.EXTRA_ALARM_COUNT, alarm.count),        mResultReceiver, mHandler);                                     // we have an active broadcast so stay awake.        if (mBroadcastRefCount == 0) {        mWakeLock.acquire();       }       mBroadcastRefCount++;                                     BroadcastStats bs = getStatsLocked(alarm.operation);       if (bs.nesting == 0) {        bs.startTime = nowELAPSED;       } else {        bs.nesting++;       }       if (alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP        || alarm.type == AlarmManager.RTC_WAKEUP) {        bs.numWakeup++;        ActivityManagerNative.noteWakeupAlarm(                alarm.operation);       }      } catch (PendingIntent.CanceledException e) {       if (alarm.repeatInterval > 0) {        remove(alarm.operation);       }      } catch (RuntimeException e) {       Slog.w(TAG, "Failure sending alarm.", e);      }     }    }   }  } }
11.4  接口
Android 中,Alarm 的操作通過 AlarmManager 來處理,AlarmManager 系統服務的具體實現 在:frameworks/base/services/java/com/android/server/AlarmManagerServic.java 文件中。應用 程序中可以通過 getSystemService 獲得其系統服務,如下所示: AlarmManager alarms = (AlarmManager)getSystemService(Context.ALARM_SERVICE); 
為了創建一個新的 Alarm,使用 set 方法並指定一個 Alarm 類型、觸發時間和在 Alarm 觸發 時要調用的 Intent。如果你設定的 Alarm 發生在過去,那么它將立即觸發。 
這里有 4 種 Alarm 類型。你的選擇將決定你在 set 方法中傳遞的時間值代表什么,是特定的 時間或者是時間流逝: 
 RTC_WAKEUP:在指定的時刻(設置 Alarm 的時候),喚醒設備來觸發 Intent。  RTC:在一個顯式的時間觸發 Intent,但不喚醒設備。  ELAPSED_REALTIME:從設備啟動后,如果流逝的時間達到總時間,那么觸發 Intent,但 不喚醒設備。流逝的時間包括設備睡眠的任何時間。注意一點的是,時間流逝的計算點 是自從它最后一次啟動算起。  ELAPSED_REALTIME_WAKEUP:從設備啟動后,達到流逝的總時間后,如果需要將喚醒 設備並觸發 Intent。
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
這 4 種 Alarm 類型詳情請參考 frameworks/base/core/java/android/app/AlarmManager.java。
11.5  實例
最后,請看一個 Alarm 的實例: 1、 建立一個 AlarmReceiver 繼承入 BroadcastReceiver,並在 AndroidManifest.xml 聲明 Public static class AlarmReceiver extends BroadcastReceiver {   @Override   Public void onReceive (Context context, Intent intent) {    Toast.makeText(context, “時間到”, Toast.LENGTH_LONG).show();   } } 
2、 建立 Intent 和 PendingIntent,來調用目標組件。  Intent it = new Intent(this, AlarmReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, 0); 
3、 設置鬧鍾 獲取鬧鍾管理的實例: AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE); 
設置單次鬧鍾:  am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (5*1000), pi); 
設置周期鬧鍾:  am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (10*1000), (24*60*60*1000), pi); 


免責聲明!

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



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