在android中,定时alarm功能是很常用的,现在来分析下怎么实现的,这里将采用自下而上的方式讲解。

1. driver

首先了解下android下是如何driver一个RTC的。android RTC 的driver 位于kernel\drivers\rtc 目录下。

在该目录下,有一堆以rtc-为前缀的文件,这些文件都是各种板子上用的rtc底层驱动代码,我们要看的只有3个,rtc-s3c.c ,alarm.c, alarm-dev.c

看下第一个,rtc-s3c.c 是三星产的arm芯片所专用的一个rtc驱动,看看怎么实现:

它用的是平台设备驱动,

static struct platform_driver s3c_rtc_driver ={

.probe= s3c_rtc_probe,
.remove= __devexit_p(s3c_rtc_remove),
.suspend= s3c_rtc_suspend,
.resume= s3c_rtc_resume,
.id_table= s3c_rtc_driver_ids,
.driver= {
.name= "s3c-rtc",
.owner= THIS_MODULE,
},
};

static const struct rtc_class_ops s3c_rtcops = {
.open= s3c_rtc_open,
.release= s3c_rtc_release,
.read_time= s3c_rtc_gettime,
.set_time= s3c_rtc_settime,
.read_alarm= s3c_rtc_getalarm,
.set_alarm= s3c_rtc_setalarm,
.proc= s3c_rtc_proc,
.alarm_irq_enable = s3c_rtc_setaie,
};

以上这两个结构体,第一个结构体是平台设备中的driver部分,第二个结构体被顺利注册进rtc子系统。Rtc的所用到的结构体被定义在kernel\include\linux\rtc.h里面。

struct rtc_device
{
struct device dev;
struct module *owner;

int id;
char name[RTC_DEVICE_NAME_SIZE];

const struct rtc_class_ops *ops;
struct mutex ops_lock;

struct cdev char_dev;
unsigned long flags;

unsigned long irq_data;
spinlock_t irq_lock;
wait_queue_head_t irq_queue;
struct fasync_struct *async_queue;

struct rtc_task *irq_task;
spinlock_t irq_task_lock;
int irq_freq;
int max_user_freq;

struct timerqueue_head timerqueue;
struct rtc_timer aie_timer;
struct rtc_timer uie_rtctimer;
struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */
int pie_enabled;
struct work_struct irqwork;


#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
struct work_struct uie_task;
struct timer_list uie_timer;
/* Those fields are protected by rtc->irq_lock */
unsigned int oldsecs;
unsigned int uie_irq_active:1;
unsigned int stop_uie_polling:1;
unsigned int uie_task_active:1;
unsigned int uie_timer_active:1;
#endif
};

这个结构体是核心部分,内核中就是靠它传递信息,不管在哪使用,都要靠它间接的调用底层信息。比如在alarm.c 中。

多次使用了rtc_set_time/rtc_get_time,这些函数虽然是定义在rtc目录下的interface.c 中,但实质还是rtc-s3c.c中结构体 rtc_class_ops所指过去的函数。

那么为什么多了一个alarm.c ,因为在android中它为了使得平台无关性提高,因此大量的增加过渡代码层,HAL就是这样诞生的。
alarm.c在用户空间中会多一个/dev/alarm 节点,而rtc-s3c.c 会产生/dev/rtc这样的节点。

Android在HAL层中,是对/dev/alarm这个结点进行操作。

3、JNI 的实现

com_android_server_AlarmManagerService.cpp 位于frameworks\base\services\jni 目录下,部分代码如下:

static jint android_server_AlarmManagerService_init(JNIEnv* env, jobject obj)
{
return open("/dev/alarm", O_RDWR);
}

static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jint fd, jint type, jlong nanoseconds)
{
struct timespec ts;
ts.tv_sec = NANOSECONDS_TO_SECONDS(nanoseconds);
ts.tv_nsec = nanoseconds - SECONDS_TO_NANOSECONDS(ts.tv_sec);

int result = ioctl(fd, ANDROID_ALARM_SET(type), &ts);

}

static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"init", "()I", (void*)android_server_AlarmManagerService_init},
{"close", "(I)V", (void*)android_server_AlarmManagerService_close},
{"set", "(IIJ)V", (void*)android_server_AlarmManagerService_set},
};

JNI就是按照特定写法的JAVA版的linux c应用程序。

4、 framework层

frameworks/base/services/java/com/android/server/AlarmManagerService.java
frameworks/base/core/java/android/app/AlarmManager.java

AlarmManager是直接提供给app层的API接口,它是AlarmManagerService.java的一个封装。

AlarmManagerService是把上面分析的JNI拿来在此调用。然后包装一下,将功能实现得更完美些。

下面是 AlarmManagerService这个类中摘出来的小段:

private native int init();
private native void close(int fd);
private native void set(int fd, int type, long nanoseconds);
private native int waitForAlarm(int fd);
private native int setKernelTimezone(int fd, int minuteswest);

这些就是JNI实现过来的接口。

5、APP层

\packages\apps\DeskClock 下面就是闹钟的应用模块

Alarm 调用流程,alarm的流程实现了从上层应用一直到下面driver的调用流程,下面简单阐述:

涉及代码;
./packages/apps/DeskClock/src/com/android/deskclock/Alarms.java
./frameworks/base/core/java/android/app/AlarmManager.java
./frameworks/base/services/java/com/android/server/AlarmManagerService.java
./frameworks/base/services/jni/com_android_server_AlarmManagerService.cpp
./kernel/kernel/drivers/rtc/alarm-dev.c
./kernel/kernel/include/linux/android_alarm.h
./kernel/kernel/drivers/rtc/alarm.c
./kernel/kernel/drivers/rtc/interface.c
./kernel/kernel/drivers/rtc/rtc-pcf8563.c
./packages/apps/DeskClock/src/com/android/deskclock/AlarmReceiver.java
./kernel/arch/arm/configs/mmp2_android_defconfig
./kernel/kernel/kernel/.config

点击Clock 应用程序,然后设置新闹钟,会调到 Alarms.java 里面的
public static long setAlarm(Context context, Alarm alarm) {
....
setNextAlert(context);
....
}
然后这里面也会调用到
public static void setNextAlert(final Context context) {
if (!enableSnoozeAlert(context)) {
Alarm alarm = calculateNextAlert(context); //new 一个新的alarm
if (alarm != null) {
enableAlert(context, alarm, alarm.time);
} else {
disableAlert(context);
}
}
}
然后继续调用到
private static void enableAlert(Context context, final Alarm alarm, final long atTimeInMillis) {
.......
am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender); //这里是RTC_WAKEUP, 这就保证了即使系统睡眠了,都能唤醒,闹钟工作(android平台关机闹钟好像不行)
.....
}

然后就调用到了AlarmManager.java 里面方法
public void set(int type, long triggerAtTime, PendingIntent operation) {
try {
mService.set(type, triggerAtTime, operation);
} catch (RemoteException ex) {
}
}

然后就调用到了AlarmManagerService.java 里面方法
public void set(int type, long triggerAtTime, PendingIntent operation) {
setRepeating(type, triggerAtTime, 0, operation);
}

然后继续调用
public void setRepeating(int type, long triggerAtTime, long interval,
PendingIntent operation) {
.....
synchronized (mLock) {
Alarm alarm = new Alarm();
alarm.type = type;
alarm.when = triggerAtTime;
alarm.repeatInterval = interval;
alarm.operation = operation;

// Remove this alarm if already scheduled.
removeLocked(operation);

if (localLOGV) Slog.v(TAG, "set: " + alarm);

int index = addAlarmLocked(alarm);
if (index == 0) {
setLocked(alarm);
}
}
}

然后就调用到
private void setLocked(Alarm alarm)
{
......
set(mDescriptor, alarm.type, alarmSeconds, alarmNanoseconds); //mDescriptor 这里的文件是 /dev/alarm
.....
}

这里就调用到jni了
private native void set(int fd, int type, long seconds, long nanoseconds);

这就调用到了com_android_server_AlarmManagerService.cpp 里面
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},
};

set 对应的是android_server_AlarmManagerService_set, 具体是
static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jint fd, jint type, jlong seconds, jlong nanoseconds)
{
#if HAVE_ANDROID_OS
struct timespec ts;
ts.tv_sec = seconds;
ts.tv_nsec = nanoseconds;

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

然后ioctl 就调用到了alarm-dev.c
static long alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
....
case ANDROID_ALARM_SET(0):
if (copy_from_user(&new_alarm_time, (void __user *)arg,
sizeof(new_alarm_time))) {
rv = -EFAULT;
goto err1;
}
from_old_alarm_set:
spin_lock_irqsave(&alarm_slock, flags);
pr_alarm(IO, "alarm %d set %ld.%09ld\n", alarm_type,
new_alarm_time.tv_sec, new_alarm_time.tv_nsec);
alarm_enabled |= alarm_type_mask;
alarm_start_range(&alarms[alarm_type],
timespec_to_ktime(new_alarm_time),
timespec_to_ktime(new_alarm_time));
spin_unlock_irqrestore(&alarm_slock, flags);
if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_SET_AND_WAIT(0)
&& cmd != ANDROID_ALARM_SET_AND_WAIT_OLD)
break;
/* fall though */
....

case ANDROID_ALARM_SET_RTC:
if (copy_from_user(&new_rtc_time, (void __user *)arg,
sizeof(new_rtc_time))) {
rv = -EFAULT;
goto err1;
}
rv = alarm_set_rtc(new_rtc_time);
spin_lock_irqsave(&alarm_slock, flags);
alarm_pending |= ANDROID_ALARM_TIME_CHANGE_MASK;
wake_up(&alarm_wait_queue);
spin_unlock_irqrestore(&alarm_slock, flags);
if (rv < 0)
goto err1;
break;
....
}

然后这边就调用到了alarm_start_range 设置闹钟, alarm_set_rtc 设置RTC
这两个函数在 android_alarm.h 声明,在 alarm.c 里实现
这是android_alarm.h 里面的声明
void alarm_start_range(struct alarm *alarm, ktime_t start, ktime_t end);
int alarm_try_to_cancel(struct alarm *alarm);
int alarm_cancel(struct alarm *alarm);
ktime_t alarm_get_elapsed_realtime(void);

/* set rtc while preserving elapsed realtime */
int alarm_set_rtc(const struct timespec ts);

下面看alarm.c 里面实现:
int alarm_set_rtc(struct timespec new_time)
{
....
ret = rtc_set_time(alarm_rtc_dev, &rtc_new_rtc_time);
....
}

alarm.c 里面实现了 alarm_suspend alarm_resume 函数
就是如果系统没有suspend的时候,设置闹钟并不会往rtc 芯片的寄存器上写数据,因为不需要唤醒系统,所以闹钟数据时间什么的就通过上层写到设备文件/dev/alarm
里面就可以了,AlarmThread 会不停的去轮寻下一个时间有没有闹钟,直接从设备文件 /dev/alarm 里面读取
第二种,系统要是进入susupend的话,alarm 的alarm_suspend 就会写到下层的rtc芯片的寄存器上去, 然后即使系统suspend之后,闹钟通过rtc 也能唤醒系统



这里就调用到了interface.c 里面 //这里面 int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm) 差不多 也是跟下面一样
int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
{
....
err = rtc->ops->set_time(rtc->dev.parent, tm);
....
}

然后set_time 就看到具体的是那个RTC芯片,这边我们是rtc-pcf8563.c
static const struct rtc_class_ops pcf8563_rtc_ops = {
.read_time = pcf8563_rtc_read_time,
.set_time = pcf8563_rtc_set_time,
.read_alarm = pcf8563_rtc_read_alarm,
.set_alarm = pcf8563_rtc_set_alarm,
};
然后就到了
static int pcf8563_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
unsigned char buf[TIME_NUM];
int ret;

ret = data_calc(buf, tm, TIME_NUM);
if (ret < 0)
goto out;
ret = i2c_smbus_write_i2c_block_data(pcf8563_info->client, PCF8563_RTC_SEC, TIME_NUM, buf); //这边就调用i2c统一接口,往pcf8563rtc芯片寄存器里面写出数据
out:
return ret;
}

到此,闹钟时间就已经写到rtc 芯片的寄存器里面,第二个参数就是寄存器的名字,后面的buf就是要写入的时间,rtc芯片是额外供电的,所以系统suspend之后,系统kernel都关了,但是rtc里面还有电,寄存器里面数据还是有的(掉电就会丢失数据),所以闹钟到了,通过硬件中断机制就可以唤醒系统。

上面那个rtc下面有几十个rtc芯片驱动代码,没有结构基本一样,都有基本操作函数,注册函数,都是对各自芯片上特有的寄存器操作,为什么调用的是pcf8563rtc呢?这个要看你系统用的是那个芯片,这个可以通过./kernel/kernel/kernel/.config 查看,这边的pcf8563rtc 是当前系统正在使用的芯片型号
# CONFIG_RTC_DRV_ISL1208 is not set
# CONFIG_RTC_DRV_X1205 is not set
CONFIG_RTC_DRV_PCF8563=y
# CONFIG_RTC_DRV_PCF8583 is not set
# CONFIG_RTC_DRV_M41T80 is not set



下面是系统唤醒之后,闹钟怎么工作的流程,简单阐述
系统没有suspend的话直接走下面流程,如果suspend的话会被RTC唤醒,然后还是走下面的流程

private class AlarmThread extends Thread
{
public AlarmThread()
{
super("AlarmManager");
}

public void run()
{
while (true)
{
int result = waitForAlarm(mDescriptor); //这里调用jni调用static jint android_server_AlarmManagerService_waitForAlarm,主要还是对 /dev/alarm 操作
....
Alarm alarm = it.next();
try {
if (localLOGV) Slog.v(TAG, "sending alarm " + alarm);
alarm.operation.send(mContext, 0,
mBackgroundIntent.putExtra(
Intent.EXTRA_ALARM_COUNT, alarm.count),
mResultReceiver, mHandler);
....
}

}
}


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

return result;
#endif
}

AlarmManagerService 里面有个AlarmThread 会一直轮询 /dev/alarm文件,如果打开失败就直接返回,成功就会做一些动作,比如查找时间最近的
alarm,比如睡眠被闹钟唤醒的时候,这边就发一个intent出去,然后在AlarmReceiver.java里面弹出里面会收到就会调用下面的
context.startActivity(alarmAlert);

然后弹出alarm 这个界面
Class c = AlarmAlert.class;
其中public class AlarmAlert extends AlarmAlertFullScreen 所以系统睡眠之后被alarm唤醒弹出的alarm就是这边start的
public class AlarmReceiver extends BroadcastReceiver {

/** If the alarm is older than STALE_WINDOW, ignore. It
is probably the result of a time or timezone change */
private final static int STALE_WINDOW = 30 * 60 * 1000;

@Override
public void onReceive(Context context, Intent intent) {
.........
Intent alarmAlert = new Intent(context, c);
alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_NO_USER_ACTION);
context.startActivity(alarmAlert);
........
}

到这里alarm 就显示出来了

更多相关文章

  1. Nginx系列教程(六)| 手把手教你搭建 LNMP 架构并部署天空网络电影
  2. Android要完?来看看谷歌的下一代操作系统Fuchsia长什么样
  3. 6、与iOS、Android的交互 实践篇——传递参数
  4. Android中给系统控件添加配置的自定义属性
  5. vlc android 编译
  6. Android网络编程之——Android下菜单系统模块的实现(Android客户
  7. Android(安卓)系统中 Location Service 的实现与架构
  8. Android属性系统(转载)
  9. Android(安卓)AndroidManifest 清单文件以及权限详解

随机推荐

  1. html移动应用开发技术分享
  2. 【转】Android 虚拟机安装APK文件方法
  3. 针对Android(安卓)Studio安装完之后activ
  4. cocos2dx android resources.ap_ does no
  5. android 横竖屏切换问题的解决方案
  6. findViewById()使用常见错误
  7. 2018.10月Android优质开源项目
  8. flutter 密码/验证码输入框
  9. Android 2.3姜饼的API改变大全
  10. 第一章:初入Android大门(教程篇)(下)