用户设置多个闹钟然后关机,闹钟在之前的实现下会响动一次,这时如果用户选择关机,则后面的闹钟都不会响动了。由此需要在关机闹钟的代码中读取当前用户设置的闹钟,并在用户选择关机时再次设置。众所周知,android 中的用户数据大都储存于 SQLite 数据库中,因此要完成此功能就需要用 c 语言去读取数据库里面信息。在刚开始学习 linux 的时候接触过 SQLite,不过一直都没有仔细用过,这次因此好好总结了一下(感慨下 几个月前自己写的代码自己都快不认识了)。
一、SQLite 常用命令
首先用命令 sqlite3 alarms.db 打开数据库,然后输入 .help,可以看到相关的命令说明及用法介绍:
sqlite> .help .backup ?DB? FILE 备份数据库到 FILE 指定的文件 .bail ON|OFF Stop after hitting an error. Default OFF .databases 列出当前数据库名称和位置 .dump ?TABLE? ... 打印当前数据库内容 If TABLE specified, only dump tables matching LIKE pattern TABLE. .echo ON|OFF Turn command echo on or off .exit 退出 .explain ?ON|OFF? Turn output mode suitable for EXPLAIN on or off. With no args, it turns EXPLAIN on. .header(s) ON|OFF 打开或者关闭表头显示 .help Show this message .import FILE TABLE Import data from FILE into TABLE .indices ?TABLE? 列出所有的目录名称 If TABLE specified, only show indices for tables matching LIKE pattern TABLE. .load FILE ?ENTRY? Load an extension library .log FILE|off Turn logging on or off. FILE can be stderr/stdout .mode MODE ?TABLE? 设置显示模式 csv Comma-separated values column Left-aligned columns. (See .width) html HTML <table> code insert SQL insert statements for TABLE line One value per line list Values delimited by .separator string tabs Tab-separated values tcl TCL list elements .nullvalue STRING 这是 null 字符串显示为 STRING .output FILENAME 将输出定向到 FILENAME 文件中 .output stdout Send output to the screen .prompt MAIN CONTINUE Replace the standard prompts .quit 退出 .read FILENAME 导入文件数据 .restore ?DB? FILE Restore content of DB (default "main") from FILE .schema ?TABLE? 查看表的创建语句 If TABLE specified, only show tables matching LIKE pattern TABLE. .separator STRING 设置新的分隔符 .show 显示当前配置信息 .stats ON|OFF Turn stats on or off .tables ?TABLE? 列出表的名称 If TABLE specified, only list tables matching LIKE pattern TABLE. .timeout MS Try opening locked tables for MS milliseconds .width NUM1 NUM2 ... Set column widths for "column" mode .timer ON|OFF Turn the CPU timer measurement on or offor off
更多详细用法参考Blog:SQLite 入门教程,当然 SQLite 支持 SQL 语句,通过如下语句查询 alarm.db 中的数据获取用户的设置情况:
sqlite> select * from alarms; 1|6|10|31|0|1|1||content://settings/system/alarm_alert 2|9|0|96|0|0|1||更详细的信息可以通过 .dump 命令获取:
sqlite> .dump alarms PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE alarms (_id INTEGER PRIMARY KEY,hour INTEGER, minutes INTEGER, daysofweek INTEGER, alarmtime INTEGER, enabled INTEGER, vibrate INTEGER, message TEXT, alert TEXT); INSERT INTO "alarms" VALUES(1,6,10,31,0,1,1,'','content://settings/system/alarm_alert'); INSERT INTO "alarms" VALUES(2,9,0,96,0,0,1,'',''); COMMIT;这里可以看到每个字段代表的含义:id、小时、分钟、星期、响闹时间、使能、振动、消息、铃音。
二、SQLite 编程简介
在 c 代码中想要查询数据库里面的内容,可以通过以下几个接口对数据库进行操作:
1、sqlite3_open
在操作数据库之前需要先通过该接口打开,这个函数的原型为:
SQLITE_API int sqlite3_open( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb /* OUT: SQLite db handle */ );函数会打开 filename 指定的数据库,并通过 ppDb 返回数据库的连接对象,如果指定的数据库不存在则创建一个同名的数据库,数据库采用 UTF-8 的编码方式。
这个函数将一条 SQL 语句转换为一个准备语句对象,同时返回这个对象的指针(Statement handle),函数原型如下:
SQLITE_API int sqlite3_prepare( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ );其中 db 为 sqlite3_open 的返回值,zSql 为 SQL 语句,nByte 一般设置为 -1,ppStmt 为准备语句指针。
3、sqlite3_step
该函数执行 sqlite3_prepare 创建好的准备语句,原型如下:
SQLITE_API int sqlite3_step(sqlite3_stmt*);执行的结果集保存在参数中以便后续使用。
4、sqlite3_column_int
这个函数从 sqlite3_step 执行完后的结果集中返回指定列的值,函数原型为:
SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol);其中 iCol 指定了想要返回的列的索引值,这个函数的返回值为 int 类型。
5、sqlite3_finalize
这个函数用于销毁之前创建的准备语句,以防内存泄露,原型如下:
SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt);6、sqlite3_close
函数用于关闭打开的数据库。在关机闹钟里面操作数据库的代码如下:
static int read_alarm_from_db(void) { int id, ret = 0; sqlite3* db = 0; sqlite3_stmt* stmt = 0; ret = sqlite3_open("/data/data/com.android.deskclock/databases/alarms.db", &db); if (ret != SQLITE_OK) { sqlite3_close(db); LOGE("Could not open alarms.db\n"); return ret; } ret = sqlite3_prepare(db, "select * from alarms", -1, &stmt, 0); if (ret != SQLITE_OK) { sqlite3_close(db); LOGE("Could not execute SELECT\n"); return ret; } while (sqlite3_step(stmt) == SQLITE_ROW) { alarm_num = sqlite3_column_int(stmt, 0); } alarm_time = malloc(alarm_num * sizeof(struct alarm_time_from_db)); if (alarm_time == NULL) { sqlite3_close(db); LOGE("malloc fail.\n"); return -1; } while (sqlite3_step(stmt) == SQLITE_ROW) { id = sqlite3_column_int(stmt, 0); alarm_time[id-1].hour = sqlite3_column_int(stmt, 1); alarm_time[id-1].min = sqlite3_column_int(stmt, 2); alarm_time[id-1].week = sqlite3_column_int(stmt, 3); alarm_time[id-1].enable = sqlite3_column_int(stmt, 5); LOGI("alarms: id = %d, time = %02d:%02d, week = %d, enable = %d\n", id - 1, alarm_time[id-1].hour, alarm_time[id-1].min, alarm_time[id-1].week, alarm_time[id-1].enable); } sqlite3_finalize(stmt); sqlite3_close(db); return SQLITE_OK; }
根据数据库中获取的用户设置,设计了一个巧妙的算法用于计算下一次闹钟的时间。在数据库的记录中,记录的是某些天的响闹时间以及星期数(每次循环为一个星期),因此以星期天(day0)开始计算当前经历的分钟数以及闹钟设置时间的分钟数,并从今天开始遍历闹钟时间,以时间最近的一次闹钟时间作为返回值,算法如下:
static long find_next_alarm(void) { time_t now_time; struct tm real_time; long next_alarm = -1; unsigned int week_mask = 0; int i, id, weekday, now_min, alarm_min, sub_min, first_id = -1; time(&now_time); get_real_time(&real_time); now_min = real_time.tm_wday * 1440 + real_time.tm_hour * 60 + real_time.tm_min; /* The minutes since Sunday */ weekday = real_time.tm_wday; for (i = 0; i < DAY_OF_WEEK; i++, weekday++) { sub_min = 1440 * (i + 1); /* alarm_min - now_min */ /* * Sunday Monday ... * tm_wday 0 1 ... * week_mask 2^6 2^0 ... * */ if (weekday > 0) week_mask = 1 << ((weekday - 1) % DAY_OF_WEEK); else week_mask = 1 << 6; /* find the next alarm */ for (id = 0; id < alarm_num; id++) { if (alarm_time[id].enable && ((alarm_time[id].week & week_mask) || alarm_time[id].week == 0)) { alarm_min = weekday * 1440 + alarm_time[id].hour * 60 + alarm_time[id].min; if ((alarm_min - now_min > 0) && (alarm_min - now_min < sub_min)) { first_id = id; sub_min = alarm_min - now_min; } } } /* calculation the next alarm by secnods */ if (first_id >= 0) { next_alarm = now_time + sub_min * 60; LOGI("find next alarm: id = %d, now_time = %ld, sub_min = %d\n", first_id, now_time, sub_min); break; } } return next_alarm; }
附: Android.mk
在调用 sqlite 的相关接口时需要配置头文件路径以及库文件如下:
LOCAL_C_INCLUDES := external/sqlite LOCAL_SHARED_LIBRARIES := libsqlite
还需要将强制静态执行关掉:
#LOCAL_FORCE_STATIC_EXECUTABLE := true