一、问题介绍

      上一篇分享介绍了4.1R版本TextClock在设置不同地区语音的情况下存在时间显示的问题,而在master(注意是2025-08-18,这个时间之前的master、5.1R、5.0.3R ... 到5.0.0R版本),使用TextClock组件设置format('hh:mm aa')不显示秒,设备连接网络,系统自动同步时间,或者通过hdc shell date '2025-07-17 08:00:00'命令设置系统时间,TextClock组件大概率不会立即更新,而是存在0~59秒的延时后才会更新显示正确时间。  

二、修改文件

foundation/arkui/ace_engine/frameworks/core/components_ng/pattern/text_clock/text_clock_pattern.cpp

修改RequestUpdateForNextSecond函数

# diff 参考
diff --git a/frameworks/core/components_ng/pattern/text_clock/text_clock_pattern.cpp b/frameworks/core/components_ng/pattern/text_clock/text_clock_pattern.cpp
index f69361b3bf6aa4e3ffb6d046b6d2174b835a9f67..626f5e0619297c43fae0cf8e9c0a9e1cac68bc3b 100644
--- a/frameworks/core/components_ng/pattern/text_clock/text_clock_pattern.cpp
+++ b/frameworks/core/components_ng/pattern/text_clock/text_clock_pattern.cpp
@@ -320,8 +320,15 @@ void TextClockPattern::RequestUpdateForNextSecond()
         1; // millisecond
     auto nextMinuteFlag = isForm_ || (!makeFunc_.has_value() && GetFormat().find('S') == std::string::npos
             && GetFormat().find('s') == std::string::npos);
+    auto now = std::chrono::system_clock::now();
+    auto duration_cast_to_millis = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
+    auto timeValue = duration_cast_to_millis.count();
+    if (std::abs(timeValue - timeValue_) > LOG_INTERVAL_TIME) {
+        timeValue_ = timeValue;
+        nextMinuteFlag = false;
+    }
     if (nextMinuteFlag) {
-        time_t current = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+        time_t current = std::chrono::system_clock::to_time_t(now);
         auto* timeZoneTime = std::localtime(&current);
         // delay to next minute
         int32_t tempTime = (TOTAL_SECONDS_OF_MINUTE - timeZoneTime->tm_sec) * MILLISECONDS_OF_SECOND;

社区修改PR(仅供参考,尚未合入):https://gitee.com/openharmony/arkui_ace_engine/pulls/68235/files

img

三、代码解析

RequestUpdateForNextSecond函数主要功能是发送TextClock组件下一次更新的延时任务。

img

在其延时任务中,会执行UpdateTimeText函数,UpdateTimeText主要功能是更新TextClock组件时间:

    delayTask_.Reset([weak = WeakClaim(this)] {
        auto textClock = weak.Upgrade();
        CHECK_NULL_VOID(textClock);
        if (!textClock->isStart_) {
            return;
        }
        textClock->UpdateTimeText();
    });

在UpdateTimeText函数中又会调用RequestUpdateForNextSecond:

void TextClockPattern::UpdateTimeText(bool isTimeChange)
{
    ...
    if (!isForm_) {
        RequestUpdateForNextSecond();
    }
    ...
}

这里的isForm_表示是否是卡片或者动态组件,应用页面开发时默认为false

这里UpdateTimeText与RequestUpdateForNextSecond的相互调用就是TextClock组件更新的基本逻辑,可以参考理解为用setTimeout调用自身模拟setInterval来实现循环执行某一操作,当然还有其他的情况会触发TextClock组件更新,这里不讨论。

在RequestUpdateForNextSecond中,默认的延时时间delayTime


void TextClockPattern::UpdateTimeText(bool isTimeChange)
{
     /**
     * 1 second = 1000 millisecond = 1000000 microsecond.
     * Millisecond is abbreviated as msec. Microsecond is abbreviated as usec.
     * unit of [delayTime] is msec, unit of [tv_usec] is usec
     * when [tv_usec] is 000100, (INTERVAL_OF_U_SECOND - timeUsec) / MICROSECONDS_OF_MILLISECOND = 999 msec
     * which will cause the delay task still arriving in current second, because 999000 + 000100 = 999100 < 1 second
     * so add an additional millisecond to modify the loss of precision during division
     */
    int32_t delayTime =
        (INTERVAL_OF_U_SECOND - static_cast<int32_t>(currentTime.tv_usec)) / MICROSECONDS_OF_MILLISECOND +
        1; // millisecond
}

代码中的注释解释了delayTime这么计算的原因:

/**
* 1 秒 = 1000 毫秒 = 1000000 微秒。
* 毫秒缩写为 msec。微秒缩写为 usec。
* [delayTime] 的单位是毫秒,[tv_usec] 的单位是 usec
* 当 [tv_usec] 为 000100 时,(INTERVAL_OF_U_SECOND - 时间 Usec) / MICROSECONDS_OF_MILLISECOND = 999 毫秒
* 这将导致延迟任务仍然在当前秒到达,因为 999000 + 000100 = 999100 < 1 秒
* 因此,再增加一毫秒以修改除法过程中精度的损失
*/

这里减去了当前的实际时间,并修改除法过程中精度的损失,因此下一次更新的时间是小于1秒的。

而后判断是否在下一分钟来更新:

    auto nextMinuteFlag = isForm_ || (!makeFunc_.has_value() && GetFormat().find('S') == std::string::npos
            && GetFormat().find('s') == std::string::npos);
    if (nextMinuteFlag) {
        time_t current = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
        auto* timeZoneTime = std::localtime(&current);
        // delay to next minute
        int32_t tempTime = (TOTAL_SECONDS_OF_MINUTE - timeZoneTime->tm_sec) * MILLISECONDS_OF_SECOND;
        delayTime += (tempTime - MILLISECONDS_OF_SECOND);
    }

前面提到isForm_在应用开发时默认为false

后面的makeFunc_.has_value表示是否通过样式Builder定制内容区(可以在官方文档中搜索如何使用),默认也是false

后面的GetFormat().find('S') == std::string::npos && GetFormat().find('s') == std::string::npos则是判断TextClock组件设置的format属性中不存在S或者s

因此设置时间格式化不带秒的时候,nextMinuteFlag为true,从而delayTime被修改,修改的值为60秒减去当前时间对应的秒数乘以1000得到当前整秒距离下一分钟的毫秒数,再减去1000毫秒,再加上默认算出距离下一秒的毫秒数,从而得到准确的当前时间到下一分钟的毫秒数。

因此存在当设置时间格式化不带秒时,TextClock组件更新可能在0~59秒后才能更新,而此时如果系统时间发生了改变,TextClock组件大概率不能立即触发更新。

上面的修改代码逻辑为,获取系统当前时间,并转换为毫秒数

auto now = std::chrono::system_clock::now();
auto duration_cast_to_millis = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
auto timeValue = duration_cast_to_millis.count();

将当前时间的毫秒数减去上一次更新的时间毫秒数timeValue_ ,如果两者相差超过对了59秒对应的毫秒数,就在下一秒进行更新,将nextMinuteFlag 改为false,并更新timeValue_

上面的修改方法已通过社区的门禁验证,门禁编译的镜像地址为:https://cidownload.openharmony.cn/Artifacts/dayu200/20250720-1-00053/version/Artifacts-dayu200-20250720-1-00053-version-dayu200.tar.gz

可以下载镜像烧录到RK3568设备上查看效果。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐