浅谈Android8.1自动同步时间机制

本文详细解析了Android8.1系统中自动同步时间的机制,包括从用户界面设置到系统底层代码的实现过程,重点分析了NetworkTimeUpdateService如何监听自动同步设置的变化,并通过NTP协议获取网络时间。

好久没更新博客了,这一年来实在是太忙,上班、洗衣、做饭、看书、奶爸,忙确实是忙。最近工作中刚好研究了下Android8.1系统的自动同步时间机制,也学到了一点东西,leader也叫分享下,趁着这个机会就简单分享下我对Android8.1自动同步时间机制的理解:

                                       
一、问题前沿

公司的测试妹子提了个问题:链接上wifi,无法自动更新时间与日期,导致wifi无法正常使用,看了这个问题吓了一跳,wifi不能使用,这个会涉及到Android系统wifi模块的,这个模块可大了,研究起来不是一时半会能解决的,此时我的心情是这样的:

                                                            

后来跟测试妹子沟通了,其实跟wifi模块没多大关系,wifi不能使用是由于Android系统时间没有更新导致的,当把系统时间调整到当前时间或者重启设备开机,发现设置里的自动同步日期和时间生效了,所以刚才那个问题缩小到是无法自动更新时间和日期这个原因。我们知道Android设备刷机后,设备需要去同步时间,也即是去请求谷歌服务器同步到最新的当前时间,如果同步失败,那么Android设备显示的时间就会与实际时间不同,感觉一个在中国,一个在美国。Android设备开机后并且设备wifi连上了,这时设备会去请求同步时间,偷偷地截图如下:

接下来我借助代码与日志来分析一波:

二、问题分析与定位

1、先从系统应用Settings入手

自动同步日期和时间功能路径:设置>通用>日期和时间>自动同步日期与时间,定位到相应代码模块如下:

上面的代码在Settings应用里只是修改了系统的一个属性:Settings.Global.AUTO_TIME,其值为"auto_time“,当我们打开或关闭自动同步日期与时间功能时,它将会把auto_time的值相应地修改为1或者0。此刻ContengProvider值发生变化了,其他地方对它有监听的都会发生相应变化,这时发现想解决问题还是得从framework层去定位解决。

2、源码分析

通过对源码的搜索,找到了相应的地方,其路径如下:

frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java

在NetworkTimeUpdateService中我发现了如下代码:

/** Observer to watch for changes to the AUTO_TIME setting */
    private static class SettingsObserver extends ContentObserver {

        private int mMsg;
        private Handler mHandler;

        SettingsObserver(Handler handler, int msg) {
            super(handler);
            mHandler = handler;
            mMsg = msg;
        }

        void observe(Context context) {
            ContentResolver resolver = context.getContentResolver();
            resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),
                    false, this);
        }

        @Override
        public void onChange(boolean selfChange) {
            mHandler.obtainMessage(mMsg).sendToTarget();
            Log.d("Andrew", "NetworkTimeUpdateService SettingsObserver onChange...");
        }
    }

上面代码块是对Settings.Global.AUTO_TIME监听,只要其值发生了变化,就会回调onChange方法,而onChange方法里用了一个Handler,找到它一看究竟,其代码定义如下:

/** Handler to do the network accesses on */
    private class MyHandler extends Handler {

        public MyHandler(Looper l) {
            super(l);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case EVENT_AUTO_TIME_CHANGED:
                case EVENT_POLL_NETWORK_TIME:
                case EVENT_NETWORK_CHANGED:
                    onPollNetworkTime(msg.what);
                    break;
            }
        }
    }

当Settings.Global.AUTO_TIME发生变化时,发送EVENT_AUTO_CHANGED给handler处理,最终执行的函数是onPollNetworkTime方法,我们一起来看下:

private void onPollNetworkTime(int event) {
        // If Automatic time is not set, don't bother. Similarly, if we don't
        // have any default network, don't bother.
        Log.d("Andrew", "onPollNetworkTime >>"+(!isAutomaticTimeRequested() || mDefaultNetwork == null));
        if (!isAutomaticTimeRequested() || mDefaultNetwork == null) return;
        mWakeLock.acquire();
        try {
            onPollNetworkTimeUnderWakeLock(event);
        } finally {
            mWakeLock.release();
        }
    }

方法中第一个if条件语句是用来判断当前自动同步时间是否打开,如果是关闭的,也就是说Settings.Global.AUTO_TIME为0时,方法会返回,后面的代码不再执行。换一种说法就是只有当Settings.Global.AUTO_TIME为1时,后面才会执行。好的,我们接着分析onPollNetworkTimeUnderWakeLock方法,这个方法比较长,其代码如下:

private void onPollNetworkTimeUnderWakeLock(int event) {
        //mNitzTimeSetTime 手机设置运营商时间的时刻
        //refTime 手机当前时刻
        //POLLING_INTERVAL_MS 24L * 60 * 60 * 1000; // 24 hrs
        //所以在24小时内已经设置了运营商时间了,那么不设置网络时间

        final long refTime = SystemClock.elapsedRealtime();
        // If NITZ time was received less than mPollingIntervalMs time ago,
        // no need to sync to NTP.
        if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) {
            resetAlarm(mPollingIntervalMs);
            return;
        }
        final long currentTime = System.currentTimeMillis();
        if (DBG) Log.d(TAG, "System time = " + currentTime);
        // Get the NTP time
        Log.d("Andrew", "onPollNetworkTimeUnderWakeLock1 >> "+(mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
                || event == EVENT_AUTO_TIME_CHANGED));
        if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
                || event == EVENT_AUTO_TIME_CHANGED) {
            if (DBG) Log.d(TAG, "Before Ntp fetch");

            // force refresh NTP cache when outdated
            Log.d("Andrew", "onPollNetworkTimeUnderWakeLock2 >> "+(mTime.getCacheAge() >= mPollingIntervalMs));
            //如果网络时间的缓存超过了一天,那么重新获取网络时间,具体的怎么获取在介绍完这个函数再谈。
            //if (mTime.getCacheAge() >= mPollingIntervalMs) {
                mTime.forceRefresh();
            //}

            // only update when NTP time is fresh
            // 抽出了 if 语句中的核心代码,ntp 为网络时间缓存中的时间,当 ntp 和本地时间的误差大于 TIME_ERROR_THRESHOLD_MS = 5000 时,设置时间。
            if (mTime.getCacheAge() < mPollingIntervalMs) {
                final long ntp = mTime.currentTimeMillis();
                mTryAgainCounter = 0;
                // If the clock is more than N seconds off or this is the first time it's been
                // fetched since boot, set the current time.
                if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
                        || mLastNtpFetchTime == NOT_SET) {
                    // Set the system time
                    if (DBG && mLastNtpFetchTime == NOT_SET
                            && Math.abs(ntp - currentTime) <= mTimeErrorThresholdMs) {
                        Log.d(TAG, "For initial setup, rtc = " + currentTime);
                    }
                    if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp);
                    // Make sure we don't overflow, since it's going to be converted to an int
                    if (ntp / 1000 < Integer.MAX_VALUE) {
                        SystemClock.setCurrentTimeMillis(ntp);
                    }
                } else {
                    if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp);
                }
                mLastNtpFetchTime = SystemClock.elapsedRealtime();
            } else {
                // Try again shortly
                //网络时间更新失败,进行定时重新更新
                mTryAgainCounter++;
                if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
                    resetAlarm(mPollingIntervalShorterMs);
                } else {
                    // Try much later
                    mTryAgainCounter = 0;
                    resetAlarm(mPollingIntervalMs);
                }
                return;
            }
        }
        resetAlarm(mPollingIntervalMs);
    }

在上面我们看到了获取网络时间的关键代码是mTime.forceRefresh(),我们找到mTime的定义mTime = NtpTrustedTime.getInstance(context),顺藤摸瓜,找NtpTrustedTime。我们就直接跳到网络请求的关键代码吧。

其路径为:frameworks/base/core/java/android/util/NtpTrustedTime.java

public boolean forceRefresh() {
        if (TextUtils.isEmpty(mServer)) {
            // missing server, so no trusted time available
            return false;
        }

        // We can't do this at initialization time: ConnectivityService might not be running yet.
        synchronized (this) {
            if (mCM == null) {
                mCM = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
            }
        }

        final NetworkInfo ni = mCM == null ? null : mCM.getActiveNetworkInfo();
        if (ni == null || !ni.isConnected()) {
            if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
            return false;
        }


        if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
        final SntpClient client = new SntpClient();

        String mTargetServer = mServer;
        if (true == getBackupmode()) {
            this.setBackupmode(false);
            mTargetServer = mBackupServer;
        }
        if (LOGD) Log.d(TAG, "Ntp Server to access at:" + mTargetServer);
        final String backServer = "ntp1.aliyun.com";
        if (client.requestTime(mTargetServer, (int) mTimeout)) {
            Log.d("Andrew", "request mTargetServer success...");
            mHasCache = true;
            mCachedNtpTime = client.getNtpTime();
            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
            mCachedNtpCertainty = client.getRoundTripTime() / 2;
            return true;
        } else if(client.requestTime(backServer, (int) mTimeout)){
            Log.d("Andrew", "request backServer success...");
            mHasCache = true;
            mCachedNtpTime = client.getNtpTime();
            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
            mCachedNtpCertainty = client.getRoundTripTime() / 2;
            return true;
        } else {
            Log.d("Andrew", "request failed...");
            countInBackupmode();
            return false;
        }
    }

SNTP,是一种简单的NTP协议,误差要比NTP大一点,Android帮我们封装了SntpClient,所以我们要自己获取网络时间的话,就可以直接拿来用了。这边我比较好奇的是Android从哪个服务器获取的时间。defaultServer = res.getString(com.android.internal.R.string.config_ntpServer),我们再找对应的字符串config.xml。

其代码路径为:frameworks/base/core/res/res/values/config.xml

<!-- Remote server that can provide NTP responses. -->
    <string translatable="false" name="config_ntpServer">time.android.com</string>

这样我们就知道谷歌的时间服务器地址是time.android.com,谷歌也设置了一个备份的服务器:0.pool.ntp.org,我们可以从下面代码看出:

public static synchronized NtpTrustedTime getInstance(Context context) {
        if (sSingleton == null) {
            final Resources res = context.getResources();
            final ContentResolver resolver = context.getContentResolver();

            final String defaultServer = res.getString(
                    com.android.internal.R.string.config_ntpServer);
            final long defaultTimeout = res.getInteger(
                    com.android.internal.R.integer.config_ntpTimeout);

            final String secureServer = Settings.Global.getString(
                    resolver, Settings.Global.NTP_SERVER);
            final long timeout = Settings.Global.getLong(
                    resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout);

            final String server = secureServer != null ? secureServer : defaultServer;
            sSingleton = new NtpTrustedTime(server, timeout);
            sContext = context;

            if (null != sSingleton) {
                final String backupServer = SystemProperties.get("persist.backup.ntpServer");

                sSingleton.mNtpRetriesMax = res.getInteger(
                    com.android.internal.R.integer.config_ntpRetry);

                if ((0 < sSingleton.mNtpRetriesMax) &&
                    (null != backupServer) &&
                    (0 != backupServer.length())) {
                    sSingleton.mBackupServer = (backupServer.trim()).replace("\"", "");
                } else {
                    sSingleton.mNtpRetriesMax = 0;
                    sSingleton.mBackupServer = "";
                }
            }
        }

        return sSingleton;
    }

不过我现在还没找到这个0.pool.ntp.org在哪定义的?谷歌暂时用到服务器暂时只发现这两个,可能还有其他的,当然还有国内的NTP服务器,如:

cn.ntp.org.cn #中国
edu.ntp.org.cn #中国教育网
ntp1.aliyun.com #阿里云
ntp2.aliyun.com #阿里云
cn.pool.ntp.org #最常用的国内NTP服务器

这次的问题解决我在forceRefresh方法中增加了阿里云NTP服务器的请求,并去掉了网络缓存时间的判断直接去请求服务器的时间。

三、小结

1、本次分享只做了NTP服务器请求流程的简单分析。

2、遗留问题,leader所说的每次请求失败重新尝试连接的问题。

3、更深层次分析

 

<think>首先,用户的问题是:“我想查找在Android 8.1系统中实现自动调节亮度的方法 请问Android 8.1系统实现自动调节亮度的方法”。我需要根据提供的引用和系统指令来回答。 系统指令: - 所有行内数学表达式必须使用 $...$ 格式。 - 独立公式使用 $$...$$ 格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1] - 参考站内引用:用户提供了引用 [1] 到 [4]。 用户提供的引用: - [^1]: 讨论了亮度调节的几种方式,包括手动和自动调节。 - [^2]: 关于在SystemUI中增加自动调节亮度的部分,包括UI修改。 - [^3]: 提到了屏幕亮度变化时,Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ值应与亮度变化相关。 - [^4]: 一个代码diff,显示了在SystemUI中修改亮度视图的逻辑。 用户上一次的问题似乎是关于Android 8.1系统中实现自动调节亮度的方法。用户说:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。” 但在这个对话中,这是用户的第一个问题,所以我需要基于当前的引用和问题来回答。 关键点:用户想知道在Android 8.1中实现自动调节亮度的方法。引用提供了线索: - 自动调节亮度涉及系统级别的组件,如DisplayPowerController和SystemUI。 - 引用[^2]提到在SystemUI中增加自动调节亮度,包括UI元素如checkbox。 - 引用[^3]指出当屏幕亮度变化时,一个系统设置值(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ)应随之变化。 - 引用[^4]显示了代码修改,影响亮度视图的可见性。 实现自动调节亮度的方法通常包括: - 使用光传感器检测环境光。 - 根据光传感器数据调整屏幕亮度。 - 在Android系统中,DisplayPowerController处理亮度控制。 - UI部分在SystemUI中实现,如快速设置面板。 基于引用: - 从[^1],我们知道自动调节亮度是基于光传感器的。 - [^2]描述了如何在SystemUI中添加自动亮度切换按钮,使用checkbox。 - [^3]暗示了系统设置值SCREEN_AUTO_BRIGHTNESS_ADJ的变化与亮度相关。 - [^4]的代码diff修改了亮度视图的行为,但似乎与自动亮度无关(它把VISIBLE改成了GONE,可能是个错误或特定修改)。 回答结构: - 我必须使用中文回答。 - 结构清晰,逐步解决问题。 - 在相关内容末尾添加引用标识。 - 最后生成相关问题。 大纲回答: 1. 解释自动调节亮度的原理:在Android中,自动亮度由DisplayPowerController控制,使用光传感器数据调整亮度。 - 参考[^1]提到自动调节。 2. 实现方法: - 硬件层:确保设备有光传感器。 - 软件层:在DisplayPowerController中配置自动亮度逻辑。 - 参考[^3],提到亮度变化时应更新系统设置值。 - UI部分:在SystemUI中添加切换按钮。 - 参考[^2],描述如何在SystemUI中添加checkbox用于自动亮度。 3. 代码示例或步骤: - 提供简化的代码片段或修改点。 - 引用[^2]有XML和代码diff;[^4]有代码修改。 4. 注意事项:不同设备可能有自定义实现。 生成相关问题:结尾添加几个相关问题。 确保LaTeX使用:用户问题涉及Android开发,没有数学表达式,所以可能不需要LaTeX。但如果可以自然添加,比如在解释算法时使用,但这里可能不必要。系统指令说“所有行内数学表达式必须使用$...$格式”,但在这个上下文中,没有数学内容,所以我应该避免不必要的插入。如果需要,我可以跳过数学部分。 最终回答: - 开头解释方法。 - 分步描述实现。 - 在引用段落末尾加引用标识。 - 结尾生成相关问题。 </think>### Android 8.1系统中实现自动调节亮度的方法 在Android 8.1系统中,自动调节亮度(Auto-Brightness)主要通过系统服务`DisplayPowerController`和UI组件(如SystemUI)协同工作实现。该方法依赖于光传感器数据,根据环境光照强度自动调整屏幕背光亮度。下面我将逐步解释实现方法,基于提供的引用和Android原生机制。 #### 1. **核心原理:DisplayPowerController的处理** - `DisplayPowerController`(位于`frameworks/base/services/core/java/com/android/server/display/`)是控制亮度的核心模块。它监听光传感器数据,计算目标亮度值。 - 自动亮度逻辑: - 系统读取光传感器值(单位:lux),并映射到预设的亮度曲线(brightness curve)。 - 目标亮度计算公式:亮度值 = $f(\text{lux})$,其中 $f$ 是一个非线性函数,定义在配置文件中(如`config_autoBrightnessLevels`和`config_autoBrightnessDisplayValuesNits`)[^1][^3]。 - 当亮度变化时,内核通过`/sys/class/backlight/backlight/brightness`更新背光值;同时,系统设置`Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ`会自动调整以反映当前亮度偏移量(范围:-1.0到1.0)[^3]。 - 关键代码点:初始化传感器监听器,并调用`updatePowerState()`方法更新亮度。 #### 2. **UI实现:SystemUI中的自动亮度开关** - 自动亮度功能需要在用户界面(如快速设置面板)提供开关选项。这通常在SystemUI中实现: - **添加Toggle按钮**:在SystemUI的`QSPanel.java`或类似文件中,添加一个CheckBox用于自动亮度开关。用户点击时,toggle状态变化会触发系统服务。 - 示例(基于引用[^2]): ```xml <!-- res/drawable/auto_checkbox.xml --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_checked="true" android:drawable="@drawable/chose_1" /> <item android:state_checked="false" android:drawable="@drawable/chose" /> </selector> ``` Java代码中绑定状态:通过`onTuningChanged()`监听设置变化,更新UI可见性[^2][^4]。 - **处理用户交互**:当用户启用自动亮度开关时,SystemUI调用`BrightnessController`设置`Settings.System.SCREEN_BRIGHTNESS_MODE`为`Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC`,触发`DisplayPowerController`启用自动逻辑[^1][^2]。 - **注意**:引用[^4]中的代码diff(`mBrightnessView.setVisibility(...)`)可能影响亮度视图的显示,但需确保自动模式开关独立处理以避免冲突。 #### 3. **完整实现步骤** - **步骤1: 配置硬件抽象层(HAL)** - 确保设备光传感器驱动正确加载(例如,通过`SensorManager`获取`TYPE_LIGHT`传感器)。 - 验证`/sys/class/backlight/`路径下的背光接口可读写[^3]。 - **步骤2: 修改DisplayPowerController** - 在`DisplayPowerController.java`中,强化自动亮度算法: - 使用`AutomaticBrightnessController`类处理传感器数据。 - 调整亮度曲线:编辑`config.xml`文件定义lux-to-brightness映射(例如:`<integer-array name="config_autoBrightnessLevels">`)。 - 监听设置变化:注册ContentObserver监测`SCREEN_AUTO_BRIGHTNESS_ADJ`的值变化[^3]。 - **步骤3: 集成SystemUI开关** - 在SystemUI的快速设置面板(`QSPanel.java`)添加CheckBox: ```java // 示例代码片段 CheckBox autoBrightnessToggle = findViewById(R.id.auto_brightness_toggle); autoBrightnessToggle.setOnCheckedChangeListener((buttonView, isChecked) -> { Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, isChecked ? Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC : Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); }); ``` - 更新资源文件:添加图标和字符串(如引用[^2]的XML)[^2]。 - **步骤4: 测试和调试** - 使用ADB命令验证:`adb shell settings put system screen_brightness_mode 1`(启用自动模式)。 - 监控日志:检查`logcat`中`DisplayPowerController`的输出,确保传感器数据正常处理。 - 性能优化:避免频繁亮度跳变,可添加滤波算法(如移动平均)[^1]。 #### 注意事项 - **设备兼容性**:不同硬件(如RK3288芯片)可能需要定制传感器驱动或亮度曲线(引用[^3]提到的路径调整)[^3]。 - **权限要求**:自动亮度功能需要`WRITE_SETTINGS`权限,并在Manifest中声明。 - **常见问题**:如果自动亮度失效,检查传感器校准或SystemUI的可见性逻辑(引用[^4]的diff可能导致视图隐藏,需修正为条件显示)[^4]。 通过以上方法,您可以在Android 8.1中实现自动调节亮度,确保系统响应环境光变化提升用户体验。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值