RK3566-OS11自动更新时区
摘要
本文为实现RK3566-Android11连接Wi-Fi自动更改时区并更新当地时间做出相关修改和记录。为了实现该功能,本文主要针对自动获取设备当前所在的时区进行更改。采用网络定位API(IP或Google API)获取时区ID,结合NTP的UTC时间,用设备本地时区数据库计算得到本地时间。设备出厂时预设为美国时区,但当在中国开机并连接Wi-Fi后,它将自动同步更新到中国地区的时区以及时间,而不仅仅是保持美国时区并显示一个换算后的时间。
一、方法原理与可行性分析
1.1实现方法与原理
设备在中国连接 WiFi → 获取到中国的公网 IP → IP 定位 API 返回时区Asia/Shanghai→ 系统时区自动设置为中国时区;设备寄到美国后连接 WiFi → 获取到美国的公网 IP → IP 定位 API 返回时区America/New_York→ 系统时区自动更新为美国时区
作为系统的一部分这是最专业,最符合Android架构的方式。将创建一个新的系统服务(例如:AutoTimeZoneService),创建如NetworkTimeUpdateService,将其作为服务的一个工具类。
1.2逻辑步骤
step1:获取公共IP地址:设备连接wifi后,会有一个公网IP;
step2:调用IP地理位置API:使用这个IP向一个地理定位服务商发起请求,返回的信息中会包含时区信息;
step3:解析并设置时区:从API响应中提取时区标识符(如America/Toronto),并用它来设置系统的时区。
1.3实现优势
1.精度足够(国家/城市级别),对于确定时区来说完全够用。
2.适用于所有连接Wi-Fi的设备,无论是否有GPS模块。
3.实现相对简单。
1.4放置路径及优势
/frameworks/base/services/core/java/com/android/server/timezone/1.系统权限:系统服务天生具有SET_TIME和SET_TIME_ZONE权限
2.生命周期:服务可以随系统启动而启动,并在后台监听网络连接状态,实现真正的“连接Wi-Fi后自动触发”。
3.架构清晰:符合 Android 的模块化设计,与系统其他部分(如网络管理、时间管理)能很好地交互。
二、具体实现步骤——基于AOSP源码修改
2.1创建核心工具类
在/frameworks/base/services/core/java/com/android/server/timezone/创建NetworkTimeZoneUpdater.java
package com.android.server.timezone; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.util.Log; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.TimeZone; public class NetworkTimeZoneUpdater { private static final String TAG = "NetworkTimeZoneUpdater"; //private static final String IPAPI_URL = "https://ipapi.co/json/"; private final Context mContext; private final ConnectivityManager mConnectivityManager; private final Handler mHandler; private ConnectivityManager.NetworkCallback mNetworkCallback; public NetworkTimeZoneUpdater(Context context) { mContext = context; mConnectivityManager = context.getSystemService(ConnectivityManager.class); mHandler = new Handler(Looper.getMainLooper()); } public void startMonitoring() { Log.i(TAG, "Starting network monitoring for auto timezone update"); NetworkRequest request = new NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) .build(); mNetworkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { Log.d(TAG, "Network available, triggering timezone update"); // 延迟执行,确保网络完全就绪 mHandler.postDelayed(() -> updateTimezoneFromNetwork(), 5000); } @Override public void onLost(Network network) { Log.d(TAG, "Network lost"); } }; mConnectivityManager.registerNetworkCallback(request, mNetworkCallback); } public void stopMonitoring() { if (mNetworkCallback != null) { mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); mNetworkCallback = null; } } private void updateTimezoneFromNetwork() { new Thread(() -> { try { String detectedTimezone = getTimezoneFromIP(); if (detectedTimezone != null && !detectedTimezone.isEmpty()) { // 获取当前系统时区 String currentTimezone = TimeZone.getDefault().getID(); // 只有当检测到的时区与当前时区不同时才更新 if (!detectedTimezone.equals(currentTimezone)) { setSystemTimezone(detectedTimezone); Log.i(TAG, "Timezone updated from " + currentTimezone + " to: " + detectedTimezone); } else { Log.d(TAG, "Timezone unchanged: " + currentTimezone); } } } catch (Exception e) { Log.e(TAG, "Failed to update timezone from network", e); } }).start(); } private String getTimezoneFromIP() { Log.d(TAG, "Attempting to get timezone from IP API"); // 尝试多个备用API String[] apiUrls = { "https://ipapi.co/json/", "http://ip-api.com/json/", "https://extreme-ip-lookup.com/json/" }; for (String apiUrl : apiUrls) { try { String timezone = fetchTimezoneFromAPI(apiUrl); if (timezone != null) { Log.d(TAG, "Successfully got timezone from " + apiUrl + ": " + timezone); return timezone; } } catch (Exception e) { Log.w(TAG, "API failed: " + apiUrl, e); } } return null; } private String fetchTimezoneFromAPI(String apiUrl) { HttpURLConnection connection = null; BufferedReader reader = null; try { URL url = new URL(apiUrl); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(10000); connection.setReadTimeout(10000); Log.d(TAG, "Connecting to: " + apiUrl); int responseCode = connection.getResponseCode(); Log.d(TAG, "HTTP Response Code: " + responseCode); if (responseCode == HttpURLConnection.HTTP_OK) { InputStream inputStream = connection.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } String timezone = parseTimezoneFromJson(response.toString()); if (timezone != null) { Log.d(TAG, "Successfully parsed timezone: " + timezone); } return timezone; } else { Log.e(TAG, "HTTP Error: " + responseCode); } } catch (Exception e) { Log.e(TAG, "Error getting timezone from API: " + apiUrl, e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { Log.e(TAG, "Error closing reader", e); } } if (connection != null) { connection.disconnect(); } } return null; } private String parseTimezoneFromJson(String json) { // 简化的JSON解析 if (json != null && json.contains("\"timezone\":")) { try { int start = json.indexOf("\"timezone\":\"") + 12; int end = json.indexOf("\"", start); if (start > 11 && end > start) { return json.substring(start, end); } } catch (Exception e) { Log.e(TAG, "Error parsing timezone from JSON", e); } } else if (json != null && json.contains("\"timeZone\":")) { // 处理 ip-api.com 的格式 try { int start = json.indexOf("\"timeZone\":\"") + 12; int end = json.indexOf("\"", start); if (start > 11 && end > start) { return json.substring(start, end); } } catch (Exception e) { Log.e(TAG, "Error parsing timeZone from JSON", e); } } Log.d(TAG, "No timezone found in JSON: " + (json != null ? json.substring(0, Math.min(100, json.length())) : "null")); return null; } private void setSystemTimezone(String timezoneId) { try { // 方法1: 使用SystemProperties android.os.SystemProperties.set("persist.sys.timezone", timezoneId); // 方法2: 使用alarm命令(需要root) if (isRooted()) { Runtime.getRuntime().exec("su -c 'setprop persist.sys.timezone " + timezoneId + "'"); } // 方法3: 发送广播通知系统时区变化 Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED); intent.putExtra("time-zone", timezoneId); mContext.sendBroadcast(intent); Log.i(TAG, "Successfully set timezone to: " + timezoneId); } catch (Exception e) { Log.e(TAG, "Failed to set timezone: " + timezoneId, e); } } private boolean isRooted() { return new File("/system/bin/su").exists() || new File("/system/xbin/su").exists(); } }2.2创建系统服务
基于2.1的目录创建AutoTimezoneService.java:
package com.android.server.timezone; import android.content.Context; import android.util.Log; import com.android.server.SystemService; public class AutoTimezoneService extends SystemService { private static final String TAG = "AutoTimezoneService"; private NetworkTimeZoneUpdater mTimeZoneUpdater; public AutoTimezoneService(Context context) { super(context); mTimeZoneUpdater = new NetworkTimeZoneUpdater(context); } @Override public void onStart() { Log.i(TAG, "Starting AutoTimezoneService"); } @Override public void onBootPhase(int phase) { Log.i(TAG, "onBootPhase: " + phase); if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { Log.i(TAG, "System services ready, starting timezone monitoring"); mTimeZoneUpdater.startMonitoring(); } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { Log.i(TAG, "Boot completed"); } } // 移除错误的@Override注解 public void onShutdown() { Log.i(TAG, "Shutting down AutoTimezoneService"); mTimeZoneUpdater.stopMonitoring(); } }2.3在 SystemServer 中注册服务
在\frameworks\base\services\java\com\android\server/SystemServer.java的startOtherServices()方法中添加:
// 在 SystemServer.java 的 startOtherServices() 方法中 // 先找到这些网络相关的服务启动代码 try { t.traceBegin("StartNetworkManagementService"); networkManagement = NetworkManagementService.create(context); ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement); t.traceEnd(); } catch (Throwable e) { reportWtf("starting NetworkManagement Service", e); } // ... 其他网络服务 ... try { t.traceBegin("StartConnectivityService"); connectivity = new ConnectivityService( context, networkManagement, networkStats, networkPolicy); ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity); networkStats.bindConnectivityManager(connectivity); networkPolicy.bindConnectivityManager(connectivity); t.traceEnd(); } catch (Throwable e) { reportWtf("starting Connectivity Service", e); } // 在 ConnectivityService 启动之后,添加您的服务 t.traceBegin("StartAutoTimezoneService"); try { mSystemServiceManager.startService(AutoTimezoneService.class); } catch (Throwable e) { reportWtf("starting AutoTimezoneService", e); } t.traceEnd();在SystemServer.java文件开头添加导入:
import com.android.server.timezone.AutoTimezoneService;
2.4添加网络权限
确保在合适的 Android.mk 或 Android.bp 中添加网络权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2.5检查编译配置
确认/frameworks/base/services/core/Android.bp包含了您的服务文件:
filegroup { name: "services.core-sources", srcs: ["java/**/*.java"], path: "java", visibility: ["//frameworks/base/services"], }查看是否有这部分代码,有则无需自行添加。意思是在这个/frameworks/base/services路径下面的所有.java文件都会被编译。
若不含有此部分代码,则需添加在这部分里面。
java_library_static { name: "services.core", defaults: ["services.core_defaults"], srcs: [ // ... 其他文件 ... "java/com/android/server/timezone/AutoTimeZoneManagerService.java", "java/com/android/server/timezone/NetworkTimeZoneUpdater.java", ], // ... 其他配置 ... }如果使用Android.mk,编辑/frameworks/base/services/core/Android.mk:
LOCAL_SRC_FILES += \ # ... 其他文件 ... $(LOCAL_REL_DIR)/com/android/server/timezone/AutoTimeZoneManagerService.java \ $(LOCAL_REL_DIR)/com/android/server/timezone/NetworkTimeZoneUpdater.java2.6调试方法
adb shell logcat | grep -E "(AutoTimezoneService|NetworkTimeZoneUpdater)" # 或者查看详细日志 adb logcat -v time -s AutoTimezoneService NetworkTimeZoneUpdater三、关键技术要点
3.1关键技术及难点
网络状态监听:使用
ConnectivityManager.NetworkCallback监听网络连接事件IP 地理定位:通过
ipapi.co或其他商业 API 获取时区信息系统权限:由于是系统服务,天然具有设置时区的权限
延迟触发:网络连接后延迟几秒执行,确保网络稳定
错误处理:完善的异常处理和日志记录
3.2生产环境建议
使用商业 API:将
ipapi.co替换为更稳定的商业服务(如 Ipstack、MaxMind)添加重试机制:网络失败时自动重试
频率限制:避免过于频繁的 API 调用
缓存策略:相同 IP 段时缓存时区信息
用户设置覆盖:允许用户手动设置时区并禁用自动更新
3.3结论
通过上述方案,您的设备无论从中国寄到美国还是其他国家,只要连接 WiFi 就能自动切换到当地时区,完全不需要 GPS。
