日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

alarm

 老匹夫 2016-01-16

關于時間,我們還知之甚少。

不知道是誰發(fā)現(xiàn)了時間這個 NB 的概念! 有了時間,使得我們可以知道從一個事件到另外一個事件,中間經歷了多久。時間參考系的建立,使得我們可以即使在不同的地點也可以取得同步,完成協(xié)作。

每個系統(tǒng)都要維持一個時鐘系統(tǒng),一方面維持自身的秩序,另一方面和外界取得一致。

Linux 和 Android 都不例外。在手機上我們需要時間系統(tǒng)提供什么樣的服務呢?

  • 允許我設置時間/同步時間。
  • 告訴我現(xiàn)在是什么時間。
  • 告訴我系統(tǒng)運行了多長時間。
  • 允許我設置特定時間提醒。

先來看看設置特定時間提醒這個功能。

這個特定時間,有兩個參考系:

  • RTC
  • ELAPSED_REALTIME

RTC 指得就是當前時間,UTC時間,java api System.currentTimeMillis() 返回的時間,通過這個時間我們知道現(xiàn)在是幾年幾月幾日幾時幾分幾秒。

ELAPSED_REALTIME 指的是過去的時間,從開機開始過去了多久, java api SystemClock.elapsedRealtime() 返回的時間, 通過這個時間我們知道系統(tǒng)運行了多久。

由于手機系統(tǒng)會有 “休眠” 狀態(tài),特定時間提醒這個服務在 “休眠” 狀態(tài)可以有兩種選擇,喚醒手機提醒,或者不喚醒手機,等待手機被其他原因喚醒后再提醒。針對這個特性,又添加了兩種類型:

  • RTC_WAKEUP
  • ELAPSED_REALTIME_WAKEUP

RTC_WAKEUP 基于 UTC 時間,喚醒手機進行提醒,RTC 默認不會喚醒手機。

ELAPSED_REALTIME_WAKEUP 類似的,基于開機過去時間,喚醒手機進行提醒。ELAPSED_REALTIME 不會。

先看下在 app layer 如何設置特定時間提醒:

packages/apps/DeskClock/src/com/android/deskclock/alarms/AlarmStateManager.java
1
2
 AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
 am.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);

獲取 AlarmManager ,設置在 RTC 時間 timeInMillis 喚醒并觸發(fā) pendingIntent 提醒。

我們知道 AlarmManager 是 AlarmManagerService 的代理,它最后會 IPC到 AlarmManagerService 調用相關的接口。

framework/base/core/java/android/app/AlarmManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
 public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
     setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, operation, null);
 }

 private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
         PendingIntent operation, WorkSource workSource) {
...
  try {
         mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation,
                 workSource);
     } catch (RemoteException ex) {
     }
}

到 AlarmManagerService :

framework/base/core/java/android/app/AlarmManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
 @Override
    public void set(int type, long triggerAtTime, long windowLength, long interval,
            PendingIntent operation, WorkSource workSource) {
        if (workSource != null) {
            mContext.enforceCallingPermission(
                    android.Manifest.permission.UPDATE_DEVICE_STATS,
                    "AlarmManager.set");
        }

        set(type, triggerAtTime, windowLength, interval, operation, false, workSource);
    }
->
 public void set(int type, long triggerAtTime, long windowLength, long interval,
            PendingIntent operation, boolean isStandalone, WorkSource workSource) {
...
 setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
                    interval, operation, isStandalone, true, workSource);
}

->

 private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
            long maxWhen, long interval, PendingIntent operation, boolean isStandalone,
            boolean doValidate, WorkSource workSource) {
...
rescheduleKernelAlarmsLocked();
}

->

  private void rescheduleKernelAlarmsLocked() {
        // Schedule the next upcoming wakeup alarm.  If there is a deliverable batch
        // prior to that which contains no wakeups, we schedule that as well.
        if (mAlarmBatches.size() > 0) {
            final Batch firstWakeup = findFirstWakeupBatchLocked();
            final Batch firstBatch = mAlarmBatches.get(0);
            if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
                mNextWakeup = firstWakeup.start;
                setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
            }
            if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) {
                mNextNonWakeup = firstBatch.start;
                setLocked(ELAPSED_REALTIME, firstBatch.start);
            }
        }
    }

->

 private void setLocked(int type, long when)
    {
          if (mDescriptor != -1)
        {
            // The kernel never triggers alarms with negative wakeup times
            // so we ensure they are positive.
            long alarmSeconds, alarmNanoseconds;
            if (when < 0) {
                alarmSeconds = 0;
                alarmNanoseconds = 0;
            } else {
                alarmSeconds = when / 1000;
                alarmNanoseconds = (when % 1000) * 1000 * 1000;
            }

            set(mDescriptor, type, alarmSeconds, alarmNanoseconds);
    }

->
private native void set(int fd, int type, long seconds, long nanoseconds);

native 方法:

frameworks/base/services/jni/com_android_server_AlarmManagerService.cpp
1
2
3
4
5
6
7
8
9
10
11
12
static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jint fd, jint type, jlong seconds, jlong nanoseconds)
{
    struct timespec ts;
    ts.tv_sec = seconds;
    ts.tv_nsec = nanoseconds;

    int result = ioctl(fd, ANDROID_ALARM_SET(type), &ts);
    if (result < 0)
    {
        ALOGE("Unable to set alarm to %lld.%09lld: %s\n", seconds, nanoseconds, strerror(errno));
    }
}

fd 從哪里來的?AlarmManagerService 初始化來的:

frameworks/services/java/com/android/server/AlarmManagerService.java
1
2
3
4
5
6
 public AlarmManagerService(Context context) {
        mContext = context;
        mDescriptor = init();
        mNextWakeup = mNextNonWakeup = 0;
        ...
    }

init 是 native 方法:

frameworks/base/services/jni/com_android_server_AlarmManagerService.cpp
1
2
3
4
static jint android_server_AlarmManagerService_init(JNIEnv* env, jobject obj)
{
    return open("/dev/alarm", O_RDWR);
}

在設下 alarm 之后, AlarmManagerService 啟動了一個 AlarmThread 來等待 alarm 的事件。

frameworks/services/java/com/android/server/AlarmManagerService.java
1
2
3
4
5
6
7
8
9
10
 private final AlarmThread mWaitThread = new AlarmThread();

 public AlarmManagerService(Context context) {
         ...
         if (mDescriptor != -1) {
            mWaitThread.start();
        } else {
            Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
        }
    }

這個 AlarmThread 就會循環(huán)等待 alarm 事件!

frameworks/services/java/com/android/server/AlarmManagerService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  private class AlarmThread extends Thread
    {
        public AlarmThread()
        {
            super("AlarmManager");
        }

        public void run()
        {
            ArrayList<Alarm> triggerList = new ArrayList<Alarm>();

            while (true)
            {
                int result = waitForAlarm(mDescriptor);
                ...
            }
        }
    }

等到設定的時間到了的時候, AlarmManagerService 就會收到消息,發(fā)送當初設定的 PendingIntent.

這樣就滿足了設定特定時間提醒的功能。

對于像鐘表這樣的程序,就需要一種機制,幾乎是時時的告訴,當前是什么時間,而且要每一秒都要更新。這個需求怎么滿足呢?

Android 的設計中有一個 Intent 是標識這種時間改變的,但是不是每秒,是每分鐘啊 ?。?/p>

Intent.java
1
2
3
4
5
6
7
8
9
10
11
12
/**  
 * Broadcast Action: The current time has changed.  Sent every
 * minute.  You can <em>not</em> receive this through components declared
 * in manifests, only by explicitly registering for it with
 * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
 * Context.registerReceiver()}.
 *
 * <p class="note">This is a protected intent that can only be sent
 * by the system.
 */
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_TIME_TICK = "android.intent.action.TIME_TICK";

你只要注冊了這個 Intent , 每分鐘開始的時候都會受到這個 Intent。AlarmManagerService 是如何提供這個服務的呢?

在 AlarmManagerService 的構造函數(shù)中會創(chuàng)建一個 ClockReceiver, 并在 scheduleTimeTickEvent 中調用native set 方法設置一個一分鐘后的時間提醒,設置的 PendingIntent 就是 ACTION_TIME_TICK 這個 Intent。

而且,這個 ClockReceiver 還注冊了ACTION_TIME_TICK 的監(jiān)聽。一分鐘后它自己也會收到 ACTION_TIME_TICK,收到之后,它又調用了一次 scheduleTimeTickEvent,設定了下一分鐘的提醒。如是,每分鐘都會可以收到這個提醒了!

frameworks/services/java/com/android/server/AlarmManagerService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  public AlarmManagerService(Context context) {
        ...

        mTimeTickSender = PendingIntent.getBroadcastAsUser(context, 0,
                new Intent(Intent.ACTION_TIME_TICK).addFlags(
                        Intent.FLAG_RECEIVER_REGISTERED_ONLY
                        | Intent.FLAG_RECEIVER_FOREGROUND), 0,
                        UserHandle.ALL);
        // now that we have initied the driver schedule the alarm
        mClockReceiver= new ClockReceiver();
        mClockReceiver.scheduleTimeTickEvent();
        ...

    }

   class ClockReceiver extends BroadcastReceiver {
        public ClockReceiver() {
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_TIME_TICK);
            filter.addAction(Intent.ACTION_DATE_CHANGED);
            mContext.registerReceiver(this, filter);
        }
       public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) {
                scheduleTimeTickEvent();
        }

      public void scheduleTimeTickEvent() {
            final long currentTime = System.currentTimeMillis();
            final long nextTime = 60000 * ((currentTime / 60000) + 1);

            // Schedule this event for the amount of time that it would take to get to
            // the top of the next minute.
            final long tickEventDelay = nextTime - currentTime;

            final WorkSource workSource = null; // Let system take blame for time tick events.
            set(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
                    0, mTimeTickSender, true, workSource);
        }
    }

可是這樣的話,每秒鐘的提醒它肯定滿足不了,那么時鐘是如何實現(xiàn)秒針的現(xiàn)實的呢?

packages/apps/DeskClock/src/com/android/deskclock/AnalogClock.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    protected void onAttachedToWindow() {
     ...
     // tick the seconds
     post(mClockTick);
     ...
    }

    private final Runnable mClockTick = new Runnable () {

        @Override
        public void run() {
            onTimeChanged();
            invalidate();
            AnalogClock.this.postDelayed(mClockTick, 1000);
        }
    };

在創(chuàng)建的時候 post 一個 Runnable, 在 Runnable 中的 run 方法中 又設定了在一秒鐘之后,再 post 這個 Runnable。這樣每秒鐘都會執(zhí)行 Runnable 一次,進行重新繪制。

DeskClock 中的 widget 插件和應用里面第二個 TAB 中的數(shù)字時鐘都是使用 TextClockTextClock 也是監(jiān)聽 ACTION_TIME_TICK 來完成每分鐘的更新的。參考代碼:

  • frameworks/base/core/java/android/widget/TextClock.java

Statusbar 上的 Clock 也是監(jiān)聽 ACTION_TIME_TICK 來完成每分鐘的更新的。參考代碼:

  • frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java

Keyguard 上的 Clock 顯示也是使用的 TextClock 同上 。

對于分鐘這個精度的時間顯示都可以使用監(jiān)聽 ACTION_TIME_TICK 的方式來完成。但是收到這個 Intent 的時候并沒有將當前的時間傳遞過來,所以還是需要另外的接口來完成獲取當前準確時間的需求。

獲取當前 UTC 時間是通過 System.currentTimeMillis() 來完成的。

libcore/luni/src/main/java/java/lang/System.java
1
2
3
4
5
6
7
8
9
10
11
12
/** 
 * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC.
 *
 * <p>This method always returns UTC times, regardless of the system's time zone.
 * This is often called "Unix time" or "epoch time".
 * Use a {@link java.text.DateFormat} instance to format this time for display to a human.
 *
 * <p>This method shouldn't be used for measuring timeouts or
 * other elapsed time measurements, as changing the system time can affect
 * the results. Use {@link #nanoTime} for that.
 */
public static native long currentTimeMillis();

為什么定義 1970.1.1 開始呢?因為那大概是Unix誕生的時間。

這個 native 方法調用 gettimeofday 來完成的:

libcore/luni/src/main/native/java_lang_System.cpp
1
2
3
4
5
6
static jlong System_currentTimeMillis(JNIEnv*, jclass) {
    timeval now;
    gettimeofday(&now, NULL);
    jlong when = now.tv_sec * 1000LL + now.tv_usec / 1000;
    return when;
}

在 Linux shell 環(huán)境下輸入 man gettimeofday 獲取更多信息:

libcore/luni/src/main/native/java_lang_System.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
GETTIMEOFDAY(2)                                      Linux Programmer's Manual                                     GETTIMEOFDAY(2)

NAME
       gettimeofday, settimeofday - get / set time

SYNOPSIS
       #include <sys/time.h>

       int gettimeofday(struct timeval *tv, struct timezone *tz);
       int settimeofday(const struct timeval *tv, const struct timezone *tz);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       settimeofday(): _BSD_SOURCE

DESCRIPTION
       The  functions  gettimeofday()  and  settimeofday()  can  get and set the time as well as a timezone.  The tv argument is a
       struct timeval (as specified in <sys/time.h>):

           struct timeval {
               time_t      tv_sec;     /* seconds */
               suseconds_t tv_usec;    /* microseconds */
           };

       and gives the number of seconds and microseconds since the Epoch (see time(2)).  The tz argument is a struct timezone:

           struct timezone {
               int tz_minuteswest;     /* minutes west of Greenwich */
               int tz_dsttime;         /* type of DST correction */
           };

       If either tv or tz is NULL, the corresponding structure is not set or returned.

timeval 帶有兩個成員, tv_sec 保存秒數(shù),tv_usec 保存微秒(1/1000000 秒).可以看到上面 timeval 到毫秒的轉化 :

libcore/luni/src/main/native/java_lang_System.cpp
1
jlong when = now.tv_sec * 1000LL + now.tv_usec / 1000;

gettimeofday的實現(xiàn)是經 libc 進入內核,實際上是一個 system call 。詳情參考 kernel 的代碼。

關于 libc 和 內核的實現(xiàn)關系,我需要學習后再來討論,可以參考下 user space and libc interface

獲取系統(tǒng)已經運行的時間有幾個不同的 API , 都在 SystemClock.java 中:

  • elapsedRealtime () //Returns milliseconds since boot, including time spent in sleep.
  • elapsedRealtimeNanos () //Returns nanoseconds since boot, including time spent in sleep.
  • uptimeMillis () //Returns milliseconds since boot, not counting time spent in deep sleep.

elapsedRealtime()elapsedRealtimeNanos() 包含系統(tǒng) sleep 的時間,而 uptimeMillis() 不包含 sleep 的時間。參考 SystemClock

elapsedRealtime() api 用的比較多,Android 系統(tǒng)里面 Settings->About phone->Status->Uptime 顯示的就是這個時間。Settings->Battery 顯示的時間也是用這個時間計算出來的(減去上一次 unplug 的時間)。

來看下 elapsedRealtime 的底層實現(xiàn):

SystemClock.java
1
 native public static long elapsedRealtime();

在 SystemClock 里用的是 native 的方法。

frameworks/base/core/jni/android_os_SystemClock.cpp
1
2
3
4
5
6
7
8
/*
 * native public static long elapsedRealtime();
 */
static jlong android_os_SystemClock_elapsedRealtime(JNIEnv* env,
        jobject clazz)
{
    return (jlong)elapsedRealtime();
}

cpp 中調用了libutils 中的方法 elapsedRealtime().

system/core/libutils/SystemClock.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
 * native public static long elapsedRealtime();
 */
int64_t elapsedRealtime()
{
    return nanoseconds_to_milliseconds(elapsedRealtimeNano());
}

->

/*
 * native public static long elapsedRealtimeNano();
 */
int64_t elapsedRealtimeNano()
{
...
    static int s_fd = -1;

    if (s_fd == -1) {
        int fd = open("/dev/alarm", O_RDONLY);
        if (android_atomic_cmpxchg(-1, fd, &s_fd)) {
            close(fd);
        }
    }

    result = ioctl(s_fd,
            ANDROID_ALARM_GET_TIME(ANDROID_ALARM_ELAPSED_REALTIME), &ts);

    if (result == 0) {
        timestamp = seconds_to_nanoseconds(ts.tv_sec) + ts.tv_nsec;
        checkTimeStamps(timestamp, &prevTimestamp, &prevMethod, METHOD_IOCTL);
        return timestamp;
    }
...
}

和鬧鐘的提醒功能相似,elapsedRealtimeNano() 也是通過 /dev/alarm 的 ioctl 來完成的,具體的實現(xiàn)要參看 driver 的代碼了。

好了,獲取當前時間(UTC)通過 kernel system call 完成, 獲取系統(tǒng)運行時間通過 ioctl /dev/alarm 來完成。

Alarm/Clock 系統(tǒng)還需要提供的一個功能是設置/同步時間。由于手機芯片中缺少一個斷電之后能繼續(xù)維持時間的模塊,所以,拔掉電池之后再開機之后時間會出現(xiàn)偏差(如果沒有同步或手動設置時間)。

在 Android 系統(tǒng)中, Settings-> Date & Time –> 提供了設置時間和同步時間的UI 接口。 Date 和 Time 的設置都是使用 AlarmManagerService 提供的 setTime 接口。

AlarmManagerService.java
1
2
3
4
5
6
7
public void setTime(long millis) {
    mContext.enforceCallingOrSelfPermission(
            "android.permission.SET_TIME",
            "setTime");  //檢查 permission

    SystemClock.setCurrentTimeMillis(millis);
}

AlarmManagerService 調用了 SystemClock 的 api setCurrentTimeMillis.

SystemClock.java
1
2
3
4
5
6
7
/** 
 * Sets the current wall time, in milliseconds.  Requires the calling
 * process to have appropriate permissions.
 *
 * @return if the clock was successfully set to the specified time.
 */
native public static boolean setCurrentTimeMillis(long millis);

SystemClock.java 調用了 native 方法

android_os_SystemClock.cpp
1
2
3
4
5
6
7
8
9
10
11
12
/*
 * Set the current time.  This only works when running as root.
 */
static int setCurrentTimeMillis(int64_t millis)
{

...
    fd = open("/dev/alarm", O_RDWR);
...
    res = ioctl(fd, ANDROID_ALARM_SET_RTC, &ts);
...
}

android_os_SystemClock.cpp 是直接利用 ioctl /dev/alarm 調用到 driver 的 alarm 來完成。

同步時間的功能是定期的從 Google 的服務器 2.android.pool.ntp.org (默認配置) 獲取時間,刷新的時間間隔有兩種,一種長的是 24 h,一種短的是 60s。

參考代碼:

  • frameworks/base/services/java/com/android/server/NetworkTimeUpdateService.java
  • frameworks/base/core/java/android/util/NtpTrustedTime.java
  • frameworks/base/core/res/res/values/config.xml

通過 ioctl /dev/alarm 操組 driver 的 alarm 模塊來完成設置時間,通過定期和 Google 服務器 2.android.pool.ntp.org 同步來完成時間的同步。

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多