这两年的HarmonyOS NEXT把不少原生Android依赖都切断了,很多人第一时间想到的替代方案是ArkUI,但如果你手里已经有一套成熟的Flutter业务代码,或者团队里没人写过arkts,完全没必要推倒重来。我最近用Flutter做了一个护眼提醒APP,目标平台直接锁鸿蒙,端到端跑通了从环境配置、功能开发、平台通道调用到hap打包的完整流程。这篇文章把我踩过的坑和值得照抄的代码逻辑都整理出来,给正在纠结“Flutter到底能不能好好上鸿蒙”的同学一个明确参考。
这个项目选的场景是护眼提醒,看起来很轻量,但它恰好覆盖了Flutter跨平台开发的几个关键难点:Timer计时器的生命周期可靠性、本地数据存储选型、系统通知能力的平台通道调用,以及鸿蒙后台任务限制下的提醒策略。可以说,把这套逻辑理顺,再做更复杂的功能也只是往上堆模块的事。
1. 先搞明白:鸿蒙NEXT上跑Flutter,和你想的不太一样
1.1 为什么用Flutter而不是ArkUI原生开发
我不是说ArkUI不好。客观讲,HarmonyOS NEXT对ArkUI的投入很猛,声明式UI、状态管理、跨端流转都做得有模有样。但技术选型不是哪个新用哪个,而是看团队已有的资产能不能复用。
我这个护眼提醒APP之前在Android和iOS上都已经有版本了,界面、业务逻辑、数据层全是Flutter写的。如果鸿蒙单独用ArkUI重写一遍,意味着两套代码库长期并行维护,UI细节打磨两遍,上线排期多一倍。而Flutter社区很早就有人在推动鸿蒙适配,华为那边也有自己的SIG分支在维护,跑通一个纯Dart业务+少量系统能力调用的APP完全可行。
另一个原因在于护眼提醒这个品类本身:它需要的系统能力非常克制,无非是一个定时器、一个通知、几个本地数据存储的key-value,真正复杂的业务逻辑全部在Dart层。系统能力部分我完全可以用MethodChannel写一个薄薄的原生通道,其余的一切都继续交给Flutter跨平台框架处理。
有一个认知必须纠正:HarmonyOS NEXT 5.0开始不再支持直接安装APK,所以你不能像以前那样在鸿蒙手机上装一个Flutter打出来的Android包来“兼容运行”。Flutter要跑在鸿蒙手机上,必须是Flutter代码生成鸿蒙自己的hap包,Flutter引擎也必须在鸿蒙环境里有对应的适配版本。这是整个技术选型的起点,所有环境配置都要围绕它展开。
1.2 Flutter官方支持与OpenHarmony SIG适配现状
很多人一搜“Flutter鸿蒙”就蒙了,因为Flutter官方主线的flutter repo里并没有一个叫harmonyos的平台目录。鸿蒙支持目前主要由OpenHarmony SIG(特别兴趣小组)在推进,核心仓库是OpenHarmony-SIG/flutter_flutter,常用的分支是dev/ohos,同时还有一个配套的flutter engine分支做鸿蒙侧引擎适配。
这套东西的实际效果是:你得先切换到ohos分支的Flutter SDK,然后flutter create的时候加--platforms ohos参数,生成工程里会出现ohos目录,构建时用hvigor而不是gradle,最终产物是.hap文件。整个路径走下来,和以前做Android原生插件适配的体验很像,只是有些细节需要自己试。
版本匹配这一块很容易踩坑。我最初装的是Flutter 3.16左右的ohos分支,搭配DevEco Studio 5.0.x,结果构建时老是提示hvigor版本过低。后来换了SIG仓库明确兼容的组合才稳定下来。这里我给一个当时验证可行的参考组合:
| 组件 | 参考版本 |
|---|---|
| Flutter SDK | OpenHarmony-SIG flutter_flutter dev/ohos分支(3.16系列) |
| DevEco Studio | 5.0.3 Release及以上 |
| HarmonyOS SDK API | 12以上 |
| hvigor | 5.0.x(随DevEco安装) |
| Node.js | 18以上(hvigor依赖) |
这个表格不一定长期有效,因为SIG仓库的更新频率不低,但思路是对的:不要单独装最新版Flutter再去碰运气,优先盯SIG仓库的README里写了哪些版本组合经过CI验证。这样能省下大量排查编译错误的时间。
1.3 选型小结与技术边界
用Flutter做鸿蒙APP的边界我这次也摸得很清楚:纯Dart的UI、状态管理、网络库、本地key-value存储基本都能直接跑,难点集中在设备平台相关的插件上,比如支付(IAP)、系统图库、推送SDK这类需要鸿蒙原生服务支撑的功能,必须找鸿蒙适配版或者自己写通道。
护眼提醒APP恰恰避开了这些高难度依赖。通知能力我用原生通道自己实现,本地数据存储我选了纯Dart实现的Hive,整个项目没有引用任何需要深度绑定Android/iOS系统的第三方插件。这也算一个选型经验:如果想降低Flutter+鸿蒙的风险,功能设计上要刻意“去平台化”,把需要系统能力的部分收敛到几个接口后面。
2. 环境搭建:省下3小时的版本匹配清单
2.1 必备工具与版本对应表
环境搭建是第一个劝退点。如果你直接用flutter官方SDK去执行flutter create --platforms ohos,大概率会报错说不认识这个平台。因为我前面提到的ohos支持只存在于SIG分支里,普通Flutter SDK不包含。
我这次的实际安装顺序是这样的:
- 安装DevEco Studio,它会顺带装好HarmonyOS SDK、hvigor和相关命令行工具。
- 把OpenHarmony-SIG的flutter_flutter仓库clone下来,切到dev/ohos分支。
- 把该分支下的bin目录配到PATH里,重新打开终端让flutter命令生效。
- 执行flutter doctor,确认flutter命令能跑起来,再检查环境变量。
- 装Node.js,因为在构建hap时hvigor会调用npm相关能力。
这套顺序里最容易犯的错是先去配Android SDK或者把旧版Flutter的路径残留着。同一个终端里如果先加载了旧Flutter的PATH再加载ohos分支,flutter --version显示的还是旧版本,后面所有步骤全乱。我当时是直接用一个独立的终端Profile来管理Flutter SDK路径的,避免和日常Android开发环境冲突。
2.2 接入ohos平台的两种方式
接入流程分两种情况。
第一种是全新项目,直接敲:
flutter create --platforms ohos eye_care_app这样生成的项目会同时带上lib目录和ohos原生工程目录,ohos目录的结构和HarmonyOS标准工程一致,里面会预置好Flutter引擎的依赖引用。
第二种是已有Flutter项目,需要把它扩展为支持ohos平台:
flutter create --platforms ohos .在已有项目根目录执行后,它会自动补出ohos子目录。这里要特别注意:执行之前先把项目里引用的第三方插件清一遍,凡是没有ohos平台实现的插件,在flutter pub get阶段就可能报错。我当时遇到最多的问题就是某个插件只有android和ios目录,没有ohos目录,hvigor去编的时候直接找不到对应的har依赖。
2.3 本地构建配置(环境变量与签名)
环境变量这块,不同版本的SDK要求不太一样,我最后配置的是这两个:
export OHOS_SDK_HOME=你的DevEco Sdk目录 export DEVECO_SDK_HOME=你的DevEco Sdk目录调试时在flutter项目下的ohos目录里运行:
hvigorw assembleHap --mode module -p product=default或者直接用Flutter侧的命令:
flutter build hap --debug签名是很多人的第一道坎。HarmonyOS真机安装hap必须签名,否则安装阶段直接失败。最简单的方式是在DevEco Studio里打开项目的ohos目录,进入Project Structure里的Signing Configs,登录华为账号后让它自动生成签名文件。它会自动在build-profile.json5里写入对应的证书信息,之后命令行构建也会读取这份配置。
有一点要提醒:签名是跟着包名走的,如果你Flutter项目里设置的应用包名和ohos工程里的bundleName不一致,签名成功但安装到真机上会提示应用异常。我第一次跑真机就撞上了这个,后来统一在flutter create时就指定好包名,后面不再改来改去。
3. 护眼提醒APP的需求拆解与数据模型
3.1 最小可用功能清单
护眼提醒这个品类市面上很多,但真正落到逻辑上无非就几件事:提醒用户别过度用眼,引导用户休息,记录用户的用眼数据。我没有一上来就做花哨的AI识别坐姿、疲劳检测那种功能,而是先稳住最小可用版本:
- 用眼计时:记录当前一段连续用眼的时长,达到阈值后进入提醒状态。
- 到点提醒:弹全屏休息页并发送系统通知,告知用户需要休息。
- 休息倒计时:休息页大字显示剩余时间,倒计时结束自动回到专注状态。
- 护眼贴士:休息页随机展示一条用眼健康建议。
- 每日统计:本地记录当天累计用眼时长、休息次数和休息时长。
- 基础设置:允许用户自定义用眼时长阈值和休息时长。
这个功能清单有个明显特点:每一项都不依赖远端服务器,也不依赖复杂系统API,非常适合作为Flutter+鸿蒙这个组合的验证项目。做完以后,核心代码可以直接迁移回Android/iOS版本复用。
3.2 UI交互流程与界面结构
交互流程我设计成一条直线,降低用户理解成本:
主页(显示今日累计与当前状态) -> 点击开始专注 -> 用眼计时中 -> 到达阈值 -> 全屏休息页 -> 倒计时结束 -> 回到主页 -> 生成一条统计记录
页面结构上分了四个页面加一个控制器:
lib/ main.dart controllers/eye_care_controller.dart models/eye_care_config.dart models/daily_record.dart pages/home_page.dart pages/rest_page.dart pages/stats_page.dart pages/settings_page.dart services/notification_service.dart services/storage_service.dart颜色主题我没有用什么花哨设计,用Material 3的ColorScheme.fromSeed直接以护眼绿为种子色生成一套配色:
final theme = ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF4CAF50)), );界面风格主打大数字、大按钮、高对比度。因为护眼App的使用场景往往是用户盯屏幕已经很累了,界面再花哨就是另一种视觉负担。主页中央一个半透明进度环,环内显示当前用眼时长或剩余休息时间,环下方一个大按钮切换开始/暂停。
3.3 本地数据模型设计
数据层我用两个模型就够了,正好对应设置项和统计项。
第一个模型是全局配置:
class EyeCareConfig { final int focusThresholdMinutes; final int restMinutes; const EyeCareConfig({ this.focusThresholdMinutes = 25, this.restMinutes = 5, }); }第二个模型是每日记录,以自然日为单位存储:
class DailyRecord { final String dateKey; // 如 2025-06-01 final int focusSeconds; final int breakCount; final int breakSeconds; const DailyRecord({ required this.dateKey, this.focusSeconds = 0, this.breakCount = 0, this.breakSeconds = 0, }); DailyRecord copyWith({ int? focusSeconds, int? breakCount, int? breakSeconds, }) { return DailyRecord( dateKey: dateKey, focusSeconds: focusSeconds ?? this.focusSeconds, breakCount: breakCount ?? this.breakCount, breakSeconds: breakSeconds ?? this.breakSeconds, ); } }这两个模型的设计遵循一个原则:不存“开始时间”这种过程态,只存可累加的数值。因为过程态要依赖定时器连续触发才能保持,而可累加的数值可以用时间戳差值随时补算,就算App被杀死一段时间,恢复后也能把这段时长补进记录里。
4. 核心逻辑实现:计时、存储与提醒的三段式落地
4.1 用眼计时器的可靠实现方式
护眼提醒最核心的就是计时器。很多新手会直接写一个Timer.periodic(Duration(seconds: 1), ...)然后每秒去减倒计时。这在App保持前台运行时没问题,但一旦锁屏、切后台、被系统省电策略挂起,Timer回调就会停掉,导致计时严重失真。
我的做法是:Timer只负责每秒刷新UI,真正的时间记录靠DateTime.now()的时间戳差值计算。简单说,Timer是显示驱动,时间戳才是数据源。
Timer? _ticker; DateTime? _focusStartTime; void startFocus() { _focusStartTime = DateTime.now(); state = EyeCareState.focusing; _focusElapsed = Duration.zero; _ticker?.cancel(); _ticker = Timer.periodic(const Duration(seconds: 1), (_) { final elapsed = DateTime.now().difference(_focusStartTime!); _focusElapsed = elapsed; notifyListeners(); if (elapsed.inMinutes >= config.focusThresholdMinutes) { triggerBreak(); } }); }你可能会问,既然Timer在后台会停止,那后台这段时间的计时不就断了?答案是:重启Timer的那一瞬间,时间戳差值会自动补上,根本不需要逐秒累积。而提醒这件事不能只靠Dart层Timer,必须依赖鸿蒙原生通知或代理提醒能力,这部分放到后面讲。
4.2 休息倒计时与状态切换
休息倒计时的实现和专注计时原理一样,用结束时间减去当前时间得到剩余时长。区别在于,休息状态必须是一个明确的“终态”,用户不能看两眼手机就算休息过了。
我定义了一个状态枚举:
enum EyeCareState { idle, focusing, resting }进入休息状态时记录restEndTime,倒计时每秒刷新剩余秒数,归零后自动回到idle并保存一条统计数据:
void startRest() { _ticker?.cancel(); state = EyeCareState.resting; _restEndTime = DateTime.now().add(Duration(minutes: config.restMinutes)); _ticker = Timer.periodic(const Duration(seconds: 1), (_) { final remain = _restEndTime!.difference(DateTime.now()); if (remain.isNegative) { finishRest(); } else { _restRemain = remain; notifyListeners(); } }); } void finishRest() { _ticker?.cancel(); state = EyeCareState.idle; _todayRecord = _todayRecord.copyWith( breakCount: _todayRecord.breakCount + 1, breakSeconds: _todayRecord.breakSeconds + config.restMinutes * 60, ); _storageService.saveTodayRecord(_todayRecord); notifyListeners(); }状态切换的关键是:所有分支都收敛到这三个状态里,不会出现既在专注又在休息的中间态。这也方便后面做界面联动,rest页和home页直接根据state判断谁显示。
4.3 本地数据库:Hive记录每日数据
本地存储我选了Hive,原因是它纯Dart实现,不依赖平台原生数据库,在Flutter+鸿蒙这种三方插件兼容性不确定的情况下风险最低。它处理配置和每日统计这种轻量数据非常合适,不需要引入sqflite或者drift那种偏重的方案。
初始化两步走:
void main() async { WidgetsFlutterBinding.ensureInitialized(); final dir = await getApplicationSupportDirectory(); Hive.init(dir.path); runApp(const EyeCareApp()); }注意getApplicationSupportDirectory来自path_provider插件,而path_provider本身也要有ohos适配。如果你怕这个也有坑,可以手动拿鸿蒙的沙箱路径传给Hive.init,这个思路是通用的。我实际测试时path_provider的ohos适配版是可以工作的,如果你用的Flutter分支版本旧,自己传路径也完全可行。
存储服务封装如下:
class StorageService { static const _boxName = 'eye_care_box'; late Box _box; Future<void> init() async { _box = await Hive.openBox(_boxName); } EyeCareConfig getConfig() { final focus = _box.get('focusThreshold', defaultValue: 25) as int; final rest = _box.get('restMinutes', defaultValue: 5) as int; return EyeCareConfig(focusThresholdMinutes: focus, restMinutes: rest); } void saveConfig(EyeCareConfig config) { _box.put('focusThreshold', config.focusThresholdMinutes); _box.put('restMinutes', config.restMinutes); } DailyRecord getTodayRecord() { final key = _todayKey(); final focusSeconds = _box.get('focus_$key', defaultValue: 0) as int; final breakCount = _box.get('break_count_$key', defaultValue: 0) as int; final breakSeconds = _box.get('break_seconds_$key', defaultValue: 0) as int; return DailyRecord(dateKey: key, focusSeconds: focusSeconds, breakCount: breakCount, breakSeconds: breakSeconds); } }用日期拼key的好处是无需遍历,天然支持按天统计。如果你想扩展每周/每月趋势,多存一份周维度或月维度的box就行。
4.4 触发提醒:平台通道调用鸿蒙原生通知
前面已经反复强调过:纯Dart层的Timer在后台不可靠,所以提醒动作必须交给鸿蒙原生能力。我在Dart侧定义了一个细薄的NotificationService,统一负责所有提醒相关调用:
class NotificationService { static const _channel = MethodChannel('com.eyecare.app/ohos'); Future<void> showRestNotification() async { try { await _channel.invokeMethod('sendNotification', { 'title': '该休息啦', 'content': '连续用眼已到设定时间,起来活动一下吧', }); } on PlatformException catch (e) { debugPrint('通知发送失败: ${e.message}'); } } }这里有个设计决策值得说一下:为什么不在Dart层用flutter_local_notifications?因为这个插件默认支持Android/iOS,鸿蒙适配并不成熟,与其等一个不确定的插件更新,不如自己写一个20行左右的MethodChannel。护眼提醒这种场景只需要发一条文案固定的通知,完全够用。
MethodChannel在鸿蒙侧的具体实现放到下一节讲,那是整个鸿蒙适配的关键。
5. 鸿蒙侧的适配细节:权限、后台与生命周期
5.1 通知权限与后台代理提醒
鸿蒙的通知体系要分两层看:一层是普通应用通知,走的是notificationManager,应用在前台或后台都可以发送;另一层是系统代理提醒,类似“闹钟提醒”,走reminderAgentManager,它的优势是即使应用进程被系统杀掉,到点了一样能拉起提醒。
护眼提醒这个场景,“到点提醒”是刚需,用户不希望App被杀掉就完全没有提醒。所以理想方案是同时做两手:
- 在App存活的情况下,用notificationManager发布一条普通通知,展示在通知栏。
- 同时注册一个reminderAgentManager的定时提醒作为兜底,保证进程被杀也能触发。
权限声明需要在ohos工程里的module.json5中加上相关权限字段,并在代码里引导用户打开通知开关。这里提个容易踩的坑:静态权限声明和用户开关不是一回事,鸿蒙对通知的控制更接近“用户可关、应用只能引导”,所以代码里要有一个“去设置打开通知”的引导入口。
5.2 应用生命周期与计时校准
App在前台和后台切换时,计时器需要做校准。Flutter本身提供WidgetsBindingObserver或AppLifecycleListener,鸿蒙侧的生命周期也会通过Flutter引擎透传到Dart层,所以这套机制在Flutter+鸿蒙里是通用的。
我用的处理方式:
AppLifecycleListener( onResume: () { // 回到前台时重新校准计时,不用依赖Timer是否连续触发 controller.recalcElapsedAtResume(); }, onInactive: () { // 记录进入后台的时刻 controller.markBackground(); }, );onResume里做的事情很简单:重新读取DateTime.now(),把当前时刻减去进入后台的时刻,补进专注时长或者从休息剩余时间里扣掉。这个过程的可靠性建立在时间戳差值上,不依赖Timer的连续性,所以即使鸿蒙系统在后台把Flutter的Timer整个冻结,回到前台也能还原真实时长。
5.3 平台通道的ArkTS侧实现
MethodChannel的鸿蒙侧实现,是这次项目里最有价值的原生代码。在ohos工程的主页Ability里注册通道,ArkTS侧大致长这样:
import { MethodCall, MethodChannel, common } from '@kit.AbilityKit'; import { notificationManager } from '@kit.NotificationKit'; import { BusinessError } from '@kit.BasicServicesKit'; const CHANNEL_NAME = 'com.eyecare.app/ohos'; function sendNotification(title: string, content: string): void { const request: notificationManager.NotificationRequest = { id: 1, content: { notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, normal: { title: title, text: content, }, }, }; notificationManager.publish(request).catch((err: BusinessError) => { console.error(`publish failed, code is ${err.code}, message is ${err.message}`); }); } export function registerEyeCareChannel(context: common.UIAbilityContext): void { const channel = new MethodChannel(context, CHANNEL_NAME); channel.setMethodCallHandler((call: MethodCall, promise) => { if (call.method === 'sendNotification') { const title = call.arguments['title'] as string; const content = call.arguments['content'] as string; sendNotification(title, content); promise.resolve(true); } else { promise.reject(new Error('method not found')); } }); }这段代码需要登录鸿蒙开发者账号、申请通知权限才能完整跑通。在我的实测中,普通通知在应用处于前台和后台时都能正常弹出,这个体验和Android原生通知很接近。
6. 真机调试与hap打包过程中的常见报错
6.1 签名与包名配置
真机调试的第一个关卡就是包名统一。Flutter项目里的applicationId和ohos工程里的bundleName必须一致,否则签名后安装会出现应用异常。我在flutter create时用它默认的生成规则创建,然后再把DevEco工程里的bundleName改成同一个值。
签名配置我推荐在DevEco Studio的Signing Configs里自动生成:
- 打开ohos目录对应的DevEco工程。
- 进入File -> Project Structure -> Signing Configs。
- 勾选Automatically generate certificate,登录华为账号。
- 等待生成cer和p12文件,并自动写入build-profile.json5。
命令行构建时会自动读取这份签名配置,不需要再手动指定。
6.2 编译报错清单
这部分我把这一路上遇到的高频报错整理成一张表,方便你遇到时直接定位:
| 报错/现象 | 原因 | 解决方案 |
|---|---|---|
| 提示无法识别的平台ohos | Flutter SDK不是ohos分支 | 换成OpenHarmony-SIG的flutter_flutter,切分支 |
| hvigor版本过低 | 新版构建脚本需要更高hvigor | 升级DevEco Studio或手动指定高版本hvigor依赖 |
| 某个pub依赖找不到ohos实现 | 插件只写了android/ios目录 | 找社区适配版,或把对应功能改为MethodChannel自研 |
| hap安装失败提示证书错误 | 签名配置过期或包名不一致 | 重新生成签名,核对bundleName与Flutter包名一致 |
| 真机运行时MethodChannel调用没响应 | 通道名不一致或未在正确Ability注册 | 核对Dart侧和ArkTS侧通道名,注册放onWindowStageCreate之后 |
| 构建产物运行即闪退 | Flutter engine版本与DevEco SDK不匹配 | 换用SIG仓库README验证过的SDK版本组合 |
这里我要特别强调第一个报错:很多人的Flutter环境早装好了,直接跑flutter create --platforms ohos就会报错。这个错不是你项目的问题,是SDK分支的问题,别在项目文件里反复找原因。
第二个高频问题是第三方插件。护眼提醒App里我用了path_provider和hive,这两个都有ohos适配痕迹,所以问题不大。但如果你引用了类似video_player、image_picker、微信登录这类插件,就要提前确认是否支持ohos,不支持的话要么替换成鸿蒙侧原生实现,要么放弃该功能。
你可能会看到“flutter兼容鸿蒙拉起iap支付”这类讨论,思路其实一样:支付这类强系统能力依赖最终都绕不开鸿蒙原生SDK,Flutter侧只是封装调用,关键看原生适配是否到位。
6.3 性能与耗电实测
我在一台HarmonyOS NEXT真机上跑了一个星期的日常使用测试,感受比较真实:
- Flutter页面滑动流畅度没问题,60帧渲染稳定,浅色护眼主题下没有明显发热。
- 应用包体积比空Flutter工程大一些,因为要带鸿蒙引擎库,不过对于工具类App完全可以接受。
- 耗电主要集中在前台计时刷新的场景,后台退到桌面后CPU占用会迅速降下来。
- Timer在后台确实会停,但因为我用的是时间戳差值,回到前台后补算正确,用户不会发现统计数据“少了一段”。
这个项目形态很适合做性能验证:它既有持续的前台动画刷新,又有隔一段时间才触发的后台通知,基本把Flutter+鸿蒙常见运行时路径都覆盖到了。如果你后面要做更重的业务,可以参照这个测试方式来评估瓶颈。
7. 一些只有做完整流程才会有的体会
整个过程跑下来,我的判断是:Flutter跑鸿蒙这条路已经走通了,但它现在更适合理想型项目,而不是什么功能都无缝迁移的万能方案。所谓理想型,就是业务逻辑尽量集中在Dart层、对第三方插件依赖少、系统能力需求克制。护眼提醒APP恰好就是这样的项目,所以整体风险被我控制在一个很小的范围内。
如果让我给你的项目提建议,我会说:动手之前先做一次“插件适配体检”,把你需要的所有pub依赖查一遍,凡是只写了android/ios目录的,要么找ohos适配版,要么想清楚备选实现。这一项检查比任何环境配置教程都重要,它能直接决定你是顺利编译还是卡在第一天。
二次开发方面,这个项目还可以继续加很多有意思的东西:把每日统计通过后端同步到多设备,接入蓝牙硬件做坐姿监测,或者根据时间动态调节护眼提示强度。这些扩展点在架构上都已经留好了位置,Flutter的跨平台能力会在后续版本里体现出更大的复利价值。