1. 问题现象与背景分析
作为一名长期从事Android系统开发的工程师,最近在调试高通平台设备时遇到了一个棘手的时间同步问题:设备在未插入SIM卡且未连接WiFi的情况下,每次重启后系统时间都会重置为系统镜像的编译时间,而不是保持上次设置的时间或正常走时。这个现象在Android O/P/Q平台上稳定复现,概率高达100%。
从日志中可以清晰看到关键报错信息:
code复制01-03 08:35:35.946 1673 1673 I AlarmManager: Current time only 174935946, advancing to build time 1522836079000
01-03 08:35:35.946 1673 1673 D AlarmManagerService: Setting time of day to sec=1522836079
04-04 18:01:19.000 1673 1673 W AlarmManagerService: Unable to set rtc to 1522836079: Invalid argument
这里暴露了两个关键点:
- 当检测到当前系统时间早于构建时间时,AlarmManagerService会强制将时间设置为构建时间
- 尝试设置RTC时间时出现"Invalid argument"错误,表明RTC写入失败
2. 问题根因深度解析
2.1 Android时间管理机制
Android系统的时间管理主要涉及三个关键组件:
- System Clock:Linux内核维护的系统时钟,断电后丢失
- RTC硬件时钟:主板上的实时时钟芯片,电池供电保持运行
- NTP时间同步服务:通过网络获取标准时间
在没有网络连接的情况下,系统依赖RTC来保持持久化时间。但在这个案例中,RTC写入失败导致时间无法持久化。
2.2 关键代码流程分析
问题核心出现在AlarmManagerService.java中的这段逻辑:
java复制final long systemBuildTime = Environment.getRootDirectory().lastModified();
if (System.currentTimeMillis() < systemBuildTime) {
Slog.i(TAG, "Current time only "+ System.currentTimeMillis()+", advancing to build time "+ systemBuildTime);
setKernelTime(mNativeData, systemBuildTime);
}
这段代码是Google在Android O引入的补丁,本意是防止设备使用明显错误的时间。但结合高通平台的实现,却导致了问题。
2.3 RTC写入失败原因
通过分析AlarmImpl::setTime函数的执行流程,发现关键问题点:
settimeofday()调用成功更新了系统内存中的时间- 但
ioctl(fd, RTC_SET_TIME, &rtc)调用失败,因为高通的RTC驱动被配置为只读 - 导致时间变更无法持久化到硬件RTC中
3. 解决方案设计与实现
3.1 方案一:回退Google补丁
最直接的解决方案是回退Android O引入的这个时间校验补丁。具体修改:
diff复制diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index abc123..def456 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -xxx,6 +xxx,6 @@
final long systemBuildTime = Environment.getRootDirectory().lastModified();
-if (System.currentTimeMillis() < systemBuildTime) {
+if (false && System.currentTimeMillis() < systemBuildTime) {
Slog.i(TAG, "Current time only "+ System.currentTimeMillis()+", advancing to build time "+ systemBuildTime);
setKernelTime(mNativeData, systemBuildTime);
}
注意:这种方案虽然简单,但可能会影响其他依赖此逻辑的设备,需要全面测试。
3.2 方案二:增强TimeService广播接收
更完善的解决方案是修改高通的TimeService,使其能够接收LOCKED_BOOT_COMPLETED广播:
- 修改AndroidManifest.xml:
diff复制diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 123dc57..0541a25 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -12,6 +12,8 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application>
@@ -19,6 +21,7 @@
<intent-filter>
+ <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
- 修改TimeServiceBroadcastReceiver.java:
diff复制diff --git a/src/com/qualcomm/timeservice/TimeServiceBroadcastReceiver.java b/src/com/qualcomm/timeservice/TimeServiceBroadcastReceiver.java
index ce599d2..fb69df8 100755
--- a/src/com/qualcomm/timeservice/TimeServiceBroadcastReceiver.java
+++ b/src/com/qualcomm/timeservice/TimeServiceBroadcastReceiver.java
@@ -56,6 +56,7 @@
public void onReceive(Context context, Intent intent) {
- if ((Intent.ACTION_TIME_CHANGED.equals(intent.getAction()))) {
+ if ((Intent.ACTION_TIME_CHANGED.equals(intent.getAction()))
+ || Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(intent.getAction())) {
3.3 完整实现步骤
- 创建time-services目录:
bash复制mkdir -p packages/apps/time-services
cd packages/apps/time-services
- 准备编译配置文件Android.bp:
python复制android_app {
name: "TimeService",
manifest: "AndroidManifest.xml",
optimize: {
enabled: false,
},
dex_preopt: {
enabled: false,
},
srcs: ["src/**/*.java"],
certificate: "platform",
proprietary: true,
owner: "qti",
sdk_version: "system_current",
}
- 编译并替换APK:
bash复制mma
adb remount
adb push $OUT/system/app/TimeService/TimeService.apk /system/app/TimeService/
adb reboot
4. 验证与测试方法
4.1 测试步骤
- 初始状态确认:
bash复制adb shell date
adb shell ls -l /system/build.prop | awk '{print $6,$7,$8}'
- 时间修改测试:
bash复制adb shell date -s "20240101 12:00"
adb reboot
adb shell date # 应显示修改后的时间
- 长时间运行测试:
bash复制adb shell date -s "20240101 12:00"
# 等待10分钟后
adb reboot
adb shell date # 时间应增加约10分钟
4.2 常见问题排查
- TimeService未生效:
- 检查APK是否成功部署:
bash复制adb shell pm list packages | grep timeservice
- 检查广播接收器是否注册:
bash复制adb shell dumpsys package com.qualcomm.timeservice
- 权限问题:
- 确保已添加RECEIVE_BOOT_COMPLETED权限
- 检查SELinux策略:
bash复制adb shell dmesg | grep avc
- 时间漂移问题:
- 检查RTC硬件时钟:
bash复制adb shell cat /proc/driver/rtc
- 验证NTP服务状态:
bash复制adb shell settings get global auto_time
5. 经验总结与优化建议
在实际调试过程中,我发现几个值得注意的点:
- 高通平台特殊性:
- 某些高通机型的RTC驱动确实配置为只读,这是为了满足特定认证要求
- 修改前务必确认硬件规格,避免违反认证要求
- 广播接收时机:
- BOOT_COMPLETED广播在用户解锁后才会发送
- LOCKED_BOOT_COMPLETED在系统启动完成后立即发送
- 根据实际需求选择合适的广播类型
- 兼容性考虑:
- 方案二虽然更完善,但需要确保所有设备都使用高通的TimeService
- 在AOSP通用设备上可能需要不同的实现方式
- 性能影响:
- 频繁的时间同步操作可能影响启动速度
- 建议在onReceive中添加时间差判断,避免不必要的同步
对于需要深度定制的项目,我建议进一步优化TimeService的实现:
- 增加时间源优先级配置(NTP > RTC > 构建时间)
- 实现时间漂移补偿算法
- 添加时间同步状态监控接口