Zhibin's blog

always smile :-)

Alarm

| Comments

关于时间,我们还知之甚少。

不知道是谁发现了时间这个 NB 的概念! 有了时间,使得我们可以知道从一个事件到另外一个事件,中间经历了多久。时间参考系的建立,使得我们可以即使在不同的地点也可以取得同步,完成协作。

每个系统都要维持一个时钟系统,一方面维持自身的秩序,另一方面和外界取得一致。

Linux 和 Android 都不例外。在手机上我们需要时间系统提供什么样的服务呢?

  • 允许我设置时间/同步时间。
  • 告诉我现在是什么时间。
  • 告诉我系统运行了多长时间。
  • 允许我设置特定时间提醒。

先来看看设置特定时间提醒这个功能。

这个特定时间,有两个参考系:

  • RTC
  • ELAPSED_REALTIME

RTC 指得就是当前时间,UTC时间,java api System.currentTimeMillis() 返回的时间,通过这个时间我们知道现在是几年几月几日几时几分几秒。

ELAPSED_REALTIME 指的是过去的时间,从开机开始过去了多久, java api SystemClock.elapsedRealtime() 返回的时间, 通过这个时间我们知道系统运行了多久。

由于手机系统会有 “休眠” 状态,特定时间提醒这个服务在 “休眠” 状态可以有两种选择,唤醒手机提醒,或者不唤醒手机,等待手机被其他原因唤醒后再提醒。针对这个特性,又添加了两种类型:

  • 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 唤醒并触发 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 就会循环等待 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 就会收到消息,发送当初设定的 PendingIntent.

这样就满足了设定特定时间提醒的功能。

对于像钟表这样的程序,就需要一种机制,几乎是时时的告诉,当前是什么时间,而且要每一秒都要更新。这个需求怎么满足呢?

Android 的设计中有一个 Intent 是标识这种时间改变的,但是不是每秒,是每分钟啊 !!

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 的构造函数中会创建一个 ClockReceiver, 并在 scheduleTimeTickEvent 中调用native set 方法设置一个一分钟后的时间提醒,设置的 PendingIntent 就是 ACTION_TIME_TICK 这个 Intent。

而且,这个 ClockReceiver 还注册了ACTION_TIME_TICK 的监听。一分钟后它自己也会收到 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);
        }
    }

可是这样的话,每秒钟的提醒它肯定满足不了,那么时钟是如何实现秒针的现实的呢?

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);
        }
    };

在创建的时候 post 一个 Runnable, 在 Runnable 中的 run 方法中 又设定了在一秒钟之后,再 post 这个 Runnable。这样每秒钟都会执行 Runnable 一次,进行重新绘制。

DeskClock 中的 widget 插件和应用里面第二个 TAB 中的数字时钟都是使用 TextClockTextClock 也是监听 ACTION_TIME_TICK 来完成每分钟的更新的。参考代码:

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

Statusbar 上的 Clock 也是监听 ACTION_TIME_TICK 来完成每分钟的更新的。参考代码:

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

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

对于分钟这个精度的时间显示都可以使用监听 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 环境下输入 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 保存秒数,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的实现是经 libc 进入内核,实际上是一个 system call 。详情参考 kernel 的代码。

关于 libc 和 内核的实现关系,我需要学习后再来讨论,可以参考下 user space and libc interface

获取系统已经运行的时间有几个不同的 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() 包含系统 sleep 的时间,而 uptimeMillis() 不包含 sleep 的时间。参考 SystemClock

elapsedRealtime() api 用的比较多,Android 系统里面 Settings->About phone->Status->Uptime 显示的就是这个时间。Settings->Battery 显示的时间也是用这个时间计算出来的(减去上一次 unplug 的时间)。

来看下 elapsedRealtime 的底层实现:

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 来完成的,具体的实现要参看 driver 的代码了。

好了,获取当前时间(UTC)通过 kernel system call 完成, 获取系统运行时间通过 ioctl /dev/alarm 来完成。

Alarm/Clock 系统还需要提供的一个功能是设置/同步时间。由于手机芯片中缺少一个断电之后能继续维持时间的模块,所以,拔掉电池之后再开机之后时间会出现偏差(如果没有同步或手动设置时间)。

在 Android 系统中, 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 同步来完成时间的同步。

Comments