:喝水提醒App從0到1完整實現(xiàn))
最近在做一個小項目把 Flutter 應(yīng)用跑到了 OpenHarmony 系統(tǒng)上做了一個帶喝水提醒功能的生活助手 App。折騰過程中踩了不少坑也把一套從環(huán)境搭建、功能設(shè)計、原生橋接到問題排查的完整鏈路捋清楚了。這篇文章把整個實現(xiàn)過程整理出來給同樣想在 OpenHarmony 上用 Flutter 做實際項目的開發(fā)者一個可參考的路徑。這套東西適合兩類人看一是已經(jīng)會 Flutter、想嘗試鴻蒙生態(tài)但不知道怎么入手的開發(fā)者二是打算做工具類、助手類應(yīng)用的團(tuán)隊想快速驗證“一套 Flutter 代碼能否在 OpenHarmony 設(shè)備上跑核心功能”。喝水提醒看著簡單但真要拆開來看涉及提醒調(diào)度、狀態(tài)管理、數(shù)據(jù)持久化、原生通道通信、前后臺切換等一整套基礎(chǔ)能力做一遍能把 Flutter 在 OpenHarmony 上的開發(fā)套路摸個七七八八。1. 項目整體拆解喝水提醒App的需求與架構(gòu)1.1 一個喝水提醒App到底在解決什么很多人覺得喝水提醒不就是“定個鬧鐘”嗎但實際用下來會發(fā)現(xiàn)簡單需求里藏著不少細(xì)節(jié)。真正的問題不是“我要喝水”而是“我忘了喝水”“我不知道自己今天喝了多少”“我工作一忙就兩三個小時想不起來起身”。所以這個功能真正要解決的事情有三塊規(guī)律提醒、記錄反饋、歷史統(tǒng)計。拿我自己的場景來說項目開發(fā)期坐在電腦前經(jīng)常一坐就是半天等反應(yīng)過來已經(jīng)口干舌燥。后來用了一個簡單的提醒策略工作時段內(nèi)每隔一定時間提醒一次每次提醒后記錄一杯水當(dāng)天可以看到自己離目標(biāo)還差多少。這種“提醒 記錄 反饋”的小閉環(huán)比單純鬧鐘有效得多。功能清單拆出來大概是這些設(shè)置每日目標(biāo)杯數(shù)比如8杯、10杯以及提醒的起止時間段在有效時間段內(nèi)按固定間隔比如60、90、120分鐘觸發(fā)提醒支持“本次跳過”“稍后提醒”“記錄喝水”三種操作記錄每次喝水時間支持當(dāng)天進(jìn)度統(tǒng)計和歷史記錄查詢本地持久化App重啟不丟數(shù)據(jù)這些功能放在 Flutter 里實現(xiàn)多數(shù)邏輯完全可以在 Dart 層完成只有“真正在正確時間觸發(fā)通知”這件事需要借助系統(tǒng)能力。這也是整個項目里 Flutter 和 OpenHarmony 原生層打交道最核心的地方。1.2 為什么用Flutter來寫OpenHarmony應(yīng)用在 OpenHarmony 上開發(fā)應(yīng)用主流選擇肯定是 ArkTS 和 ArkUI這個生態(tài)是系統(tǒng)原生支持的。但為什么我選了 Flutter說白了就一個原因跨端復(fù)用。我手上本來就有一套 Flutter 寫的生活助手類應(yīng)用代碼里面已經(jīng)實現(xiàn)了運(yùn)動記錄、飲水提醒、體重趨勢這些模塊。如果 OpenHarmony 上要用 ArkTS 重寫一遍工作量大且后續(xù)多端維護(hù)要寫兩套邏輯。而 Flutter 在 OpenHarmony 上已經(jīng)有社區(qū)維護(hù)的適配分支——有專門面向 OpenHarmony 的 Flutter SDK可以讓同一套 Dart 代碼在 OpenHarmony 設(shè)備上編譯運(yùn)行。這個方案的吸引力在于UI 層、業(yè)務(wù)邏輯層、狀態(tài)管理層全部復(fù)用只替換掉和系統(tǒng)底層相關(guān)的橋接層。當(dāng)然代價也很現(xiàn)實適配分支的版本跟進(jìn)不如官方 Flutter 快部分插件不一定有 OpenHarmony 版本遇到問題能參考的資料遠(yuǎn)不如 Android/iOS 多。所以這個方案適合“已經(jīng)有 Flutter 資產(chǎn)、想在鴻蒙設(shè)備上快速落地核心體驗”的團(tuán)隊不適合從零開始、目標(biāo)平臺只有 OpenHarmony 的項目——那種情況老老實實用 ArkTS 反而更穩(wěn)。1.3 整體架構(gòu)與數(shù)據(jù)流設(shè)計這個項目的架構(gòu)我覺得可以分四層來看后續(xù)所有實現(xiàn)都圍繞這個分層展開UI 層純 Flutter Widget負(fù)責(zé)設(shè)置頁、今日進(jìn)度頁、提醒彈窗的展示狀態(tài)層用 Cubit 管理提醒配置、喝水記錄等全局狀態(tài)橋接層MethodChannel 調(diào)用原生通知能力EventChannel 接收原生側(cè)的回傳事件原生層OpenHarmony 側(cè)實現(xiàn)通知渠道注冊、提醒發(fā)送、后臺調(diào)度等能力數(shù)據(jù)層本地文件存儲保存配置和每日記錄數(shù)據(jù)流上用戶設(shè)置配置后狀態(tài)層更新內(nèi)存數(shù)據(jù)并寫入本地存儲同時把下一次提醒時間計算好。提醒時間到了之后有兩種路徑App 在前臺時Dart 層直接彈提醒App 在后臺或需要系統(tǒng)級通知時由原生層發(fā)送系統(tǒng)通知同時通過 EventChannel 把“提醒已觸發(fā)”的事件推回給 Dart 層用于刷新頁面狀態(tài)和落記錄。2. 環(huán)境搭建與工程初始化Flutter適配OpenHarmony實錄2.1 SDK與工具鏈準(zhǔn)備要把 Flutter 跑在 OpenHarmony 上第一步就得換一套帶 OpenHarmony 平臺支持的 Flutter SDK。我用的不是 flutter.dev 官方下載的標(biāo)準(zhǔn)版而是社區(qū)維護(hù)的 OpenHarmony 分支 SDK從代碼倉庫拉下來之后本地編譯或者直接下載編譯好的對應(yīng)版本。這里有個很關(guān)鍵的認(rèn)知要提前說清楚OpenHarmony 版的 Flutter SDK 結(jié)構(gòu)里會多出一個 ohos 平臺目錄flutter 命令行工具也會支持生成 OpenHarmony 工程。環(huán)境準(zhǔn)備大致是這幾步拉取 OpenHarmony 分支的 Flutter SDK配置到環(huán)境變量 PATH 里務(wù)必確認(rèn)flutter --version指向的是這套 SDK安裝 DevEco Studio用于打開和編譯 OpenHarmony 殼工程配置 OpenHarmony SDK路徑在 DevEco Studio 里的 SDK Manager 中下載準(zhǔn)備一臺 OpenHarmony 真機(jī)或者用模擬器用于部署調(diào)試我在這個環(huán)節(jié)第一次碰到的問題基本都是“版本不匹配”。OpenHarmony 分支的 Flutter SDK 版本和 DevEco Studio 所帶的 OpenHarmony SDK 版本以及設(shè)備系統(tǒng)版本三者之間必須對應(yīng)上。官方倉庫的說明文檔里通常會寫清楚每個版本對應(yīng)的 OpenHarmony API 版本號我實測下來最好直接按文檔推薦組合來盲目用最新版本反而容易翻車。2.2 創(chuàng)建并轉(zhuǎn)換Flutter工程環(huán)境就緒之后創(chuàng)建工程比預(yù)想中簡單。用 OpenHarmony 版 Flutter SDK 執(zhí)行標(biāo)準(zhǔn)的flutter create命令創(chuàng)建一個項目然后通過命令行或工具生成 ohos 平臺工程。這一步完成之后項目目錄里會出現(xiàn)一個 ohos 目錄里面是 OpenHarmony 殼工程的骨架。我用的是帶 OpenHarmony 支持的 Flutter SDK創(chuàng)建命令大概是這樣flutter create --project-name water_helper --org com.example water_helper創(chuàng)建完普通工程后再執(zhí)行生成 ohos 平臺目錄的命令或者直接檢查flutter config是否已啟用 ohos 支持。生成成功后目錄結(jié)構(gòu)大致會這樣water_helper/ ├── lib/ # Dart 業(yè)務(wù)代碼 ├── android/ # Android 殼工程 ├── ios/ # iOS 殼工程 ├── ohos/ # OpenHarmony 殼工程 │ ├── entry/ │ │ └── src/main/ │ │ ├── ets/ # 原生入口與遺留邏輯 │ │ └── module.json5 │ └── build-profile.json5 └── pubspec.yaml看到 ohos 目錄出現(xiàn)的那一刻這個項目才算真正邁出了第一步。2.3 首次構(gòu)建運(yùn)行驗收工程生成后用 DevEco Studio 打開 ohos 目錄配置好簽名證書和設(shè)備連接直接點擊運(yùn)行。第一次跑通的標(biāo)準(zhǔn)很簡單真機(jī)上出現(xiàn)一個 Flutter 渲染的頁面并且操作響應(yīng)正常。但第一次構(gòu)建大概率不會那么順利。我當(dāng)時遇到的一個典型問題是 Gradle 插件應(yīng)用方式導(dǎo)致的構(gòu)建報錯類似“使用 apply script 方式應(yīng)用 Flutter 插件”這種提示。這個在 Flutter 的 Android 工程里見過沒想到 OpenHarmony 殼工程里也會有類似的插件聲明問題解決思路倒是通用——檢查工程根目錄的 settings.gradle 和插件應(yīng)用方式改成聲明式引入即可。另外如果 DevEco Studio 提示簽名配置缺失或設(shè)備未授權(quán)也需要先在“Project Structure / Signing Configs”里完成簽名配置。首次跑通之后我通常會做一個快速驗收清單Flutter 頁面能否正常上屏、熱重載是否生效、日志里 Flutter 引擎是否正常初始化。這些確認(rèn)沒問題后面加業(yè)務(wù)功能就不會老懷疑是環(huán)境問題了。3. 喝水提醒核心模塊調(diào)度策略與原生通道3.1 提醒策略規(guī)則、間隔與計算邏輯喝水提醒的核心不是“彈個通知”而是“什么時候彈、怎么算下一次時間”。我采用的規(guī)則比較簡單用戶設(shè)定提醒起止時間比如 09:00 到 21:00設(shè)定間隔比如 120 分鐘系統(tǒng)在這段時間內(nèi)每隔兩小時提醒一次如果用戶已經(jīng)達(dá)到當(dāng)日目標(biāo)杯數(shù)則停止提醒。這個計算邏輯的難點在于邊界情況。不能簡單用“當(dāng)前時間加間隔”來算因為要考慮跨天、休息區(qū)間、用戶手動跳過后的下一次時間。我封裝了一個函數(shù)來做這件事核心思路是從開始時間出發(fā)以間隔為步長生成一個提醒時間序列從中選出第一個晚于當(dāng)前時間的點。class RemindSchedule { final TimeOfDay startTime; final TimeOfDay endTime; final Duration interval; DateTime nextRemindAfter(DateTime now) { final dayStart DateTime(now.year, now.month, now.day, startTime.hour, startTime.minute); final dayEnd DateTime(now.year, now.month, now.day, endTime.hour, endTime.minute); // 當(dāng)前時間在時段外直接取今天的開始時間 if (now.isBefore(dayStart) || now.isAfter(dayEnd)) { return dayStart; } // 從開始時間開始按步長掃描 DateTime cursor dayStart; while (cursor.isBefore(dayEnd)) { if (cursor.isAfter(now)) return cursor; cursor cursor.add(interval); } return dayStart.add(const Duration(days: 1)); } }這個函數(shù)在每次設(shè)置變更、每次記錄喝水之后重新計算一次把結(jié)果保存下來。特殊情況下比如用戶在 20:50 錯過了最后一次提醒函數(shù)返回的會是第二天的開始時間而不是今天超時的時間點符合預(yù)期。實際操作中還有一個細(xì)節(jié)只算時間點不夠還得把“跳過本次提醒”這種用戶行為考慮進(jìn)去。我的做法是在狀態(tài)里維護(hù)一個 skipUntil 時間戳計算下次提醒時如果計算結(jié)果早于 skipUntil就繼續(xù)往后推一個間隔直到晚于 skipUntil。這樣可以避免用戶點了“跳過”后一分鐘又彈出來。3.2 用Cubit管理提醒配置與喝水記錄狀態(tài)管理這塊我選了 Cubit 而不是完整的 Bloc原因很簡單這個項目的狀態(tài)邏輯不算復(fù)雜沒有特別多需要并發(fā)響應(yīng)的異步事件Cubit 更輕代碼量更少對新手也更友好。它在 OpenHarmony 上就是一個純 Dart 庫不存在平臺適配問題。先說狀態(tài)設(shè)計。整個 App 需要全局共享的狀態(tài)主要是三塊class ReminderState { final bool waterGoal; // 每日目標(biāo)杯數(shù) final TimeOfDay startTime; // 提醒開始時間 final TimeOfDay endTime; // 提醒結(jié)束時間 final Duration interval; // 提醒間隔 final MapString, int dailyRecords; // 日期字符串 - 喝水量 }Cubit 里的操作大致有這些更新目標(biāo)、更新時段、更新間隔、記錄一杯水、重置當(dāng)日記錄。這里比較重要的一點是所有狀態(tài)修改都要同步觸發(fā)“下次提醒時間”的計算。我把這個邏輯放在一個輔助函數(shù)里在每次 emit 新狀態(tài)后調(diào)用一遍保證任何時候拿到的最新狀態(tài)都帶有正確的提醒計劃。class ReminderCubit extends CubitReminderState { ReminderCubit() : super(ReminderState.initial()); void recordOneCup() { final today _todayKey(); final cups (state.dailyRecords[today] ?? 0) 1; final newRecords MapString, int.from(state.dailyRecords) ..[today] cups; emit(state.copyWith(dailyRecords: newRecords)); _reschedule(); } }頁面?zhèn)戎恍枰猚ontext.watchReminderCubit()或直接監(jiān)聽 state在 App 冷啟動時先loadFromStorage()把本地數(shù)據(jù)灌進(jìn) Cubit。這個模式在頁面切換時不會丟狀態(tài)本質(zhì)是因為 Cubit 實例掛在全局和具體頁面 Widget 的生命周期無關(guān)。3.3 橋接原生能力MethodChannel與EventChannel在 OpenHarmony 上Flutter 和原生的通信方式和 Android 大同小異MethodChannel 用于“從 Dart 調(diào)原生”EventChannel 用于“從原生向 Dart 推事件”。這個項目的橋接設(shè)計比較清晰MethodChannel調(diào)用原生側(cè)顯示一條系統(tǒng)通知通知欄提醒EventChannel原生側(cè)在提醒觸發(fā)時主動通知 Dart 層讓界面同步狀態(tài)Dart 側(cè)統(tǒng)一封裝在一個服務(wù)類里避免頁面里到處散落 MethodChannel 代碼class NativeBridge { static const _channel MethodChannel(com.example.water/notification); static const _eventChannel EventChannel(com.example.water/remind_event); static Futurevoid showNotification(String title, String body) async { await _channel.invokeMethod(showNotification, { title: title, body: body, }); } static Streamdynamic onRemindEvent() { return _eventChannel.receiveBroadcastStream(); } }原生側(cè)的工作集中在 OpenHarmony 殼工程里。需要做幾件事聲明通知相關(guān)權(quán)限、注冊通知渠道、實現(xiàn) MethodChannel 的 setMethodCallHandler 來處理 showNotification 調(diào)用、為 EventChannel 設(shè)置 StreamHandler 來發(fā)送提醒事件。這些代碼寫在 ohos 工程的 ArkTS 層用 DevEco Studio 調(diào)試時可以直接在原生側(cè)打日志排查通道問題比 Dart 側(cè)方便。事件通道這塊稍微繞一些因為 EventChannel 是單向推送Dart 側(cè)要先創(chuàng)建好監(jiān)聽原生側(cè)才發(fā)得過來。我在 App 啟動時就調(diào)用了onRemindEvent()建立訂閱后續(xù)提醒觸發(fā)事件一到Dart 側(cè)就更新頁面上的“最近提醒時間”同時寫入當(dāng)天記錄這樣即使用戶正停在 App 內(nèi)也能實時看到變化。4. 數(shù)據(jù)持久化與頁面狀態(tài)保持4.1 本地存儲選型與實現(xiàn)喝水記錄和配置必須持久化否則 App 一重啟數(shù)據(jù)全丟這功能就沒法用了。Flutter 在 OpenHarmony 上的本地存儲方案和官方 Flutter 生態(tài)類似主要就是三選一SharedPreferences、文件存儲、輕量級數(shù)據(jù)庫。我最終選了文件存儲把數(shù)據(jù)寫成一個 JSON 文件放在應(yīng)用私有目錄里。原因有幾個首先這項目的數(shù)據(jù)量很小只有配置項和每天一條喝水記錄數(shù)據(jù)庫完全是大材小用其次SharedPreferences 插件在 OpenHarmony 分支上的適配成熟度一般我不想在平臺的通道層多踩一個不確定的坑。文件讀寫是 Dart 語言內(nèi)置能力不需要任何第三方插件依賴最穩(wěn)。class LocalStore { final String filePath; FutureMapString, dynamic load() async { final file File(filePath); if (!await file.exists()) return {}; final content await file.readAsString(); return jsonDecode(content) as MapString, dynamic? ?? {}; } Futurevoid save(MapString, dynamic data) async { final file File(filePath); await file.writeAsString(jsonEncode(data)); } }保存的數(shù)據(jù)結(jié)構(gòu)長這樣goalCups、startTime、endTime、intervalMinutes再加上 dailyRecords 這個映射鍵是“yyyy-MM-dd”值是當(dāng)天的杯數(shù)。每次狀態(tài)變更就整體寫一次文件文件很小性能完全不是瓶頸。唯一要注意的是異步寫入的競態(tài)——如果用戶快速連續(xù)記錄多杯水要保證最后一次 emit 的狀態(tài)是最終保存的不能在寫入完成前又覆蓋。我的做法是在 Cubit 里每次變更后都調(diào)用 save 并傳入完整最新狀態(tài)而不是局部 patch。4.2 每日數(shù)據(jù)模型與統(tǒng)計進(jìn)度有了持久化結(jié)構(gòu)接下來要處理的是界面上的統(tǒng)計展示。這個統(tǒng)計邏輯看著簡單做的時候有幾個想明白的點。我給今日記錄設(shè)計的模型是“杯數(shù)優(yōu)先”而不是“毫升數(shù)優(yōu)先”。雖然喝水總量更科學(xué)但用戶在提醒彈窗場景下的操作成本要低點一下“記一杯”比“輸入毫升數(shù)”體驗好太多。真要做精細(xì)的話可以在設(shè)置里允許用戶自定義“一杯多少毫升”展示時換算成總毫升數(shù)但存儲核心還是以杯數(shù)為準(zhǔn)。每日統(tǒng)計的核心是當(dāng)天已經(jīng)喝了多少杯、距離目標(biāo)還差幾杯、完成百分比是多少。這些全部可以從前面的 dailyRecords 里算出來double progressPercent(int cups, int goal) { if (goal 0) return 0; return min(1.0, cups / goal); }頁面上的進(jìn)度環(huán)我用 Flutter 自帶的 CustomPaint 畫的畫圓弧的邏輯很簡單關(guān)鍵是動畫要做平滑過渡。記錄了一杯水之后從 50% 到 62.5% 的圓弧變化如果直接重繪會顯得很生硬我加了一個 400 毫秒的隱式動畫包裝視覺上自然很多。4.3 Navigator切換頁面時會丟狀態(tài)嗎這個問題是 Flutter 里被問得特別多的問題放到這個項目里同樣重要。用戶從“今日統(tǒng)計頁”切到“設(shè)置頁”再切回來統(tǒng)計頁的數(shù)據(jù)如果重新走了一遍初始化就會出現(xiàn)白屏閃爍、狀態(tài)短暫丟失等問題。實際上Navigator 默認(rèn)是不丟狀態(tài)的。頁面被 push 覆蓋時只是從視圖樹中摘除但頁面對象和它持有的狀態(tài)對象還活著只要不 pop 銷毀回來時數(shù)據(jù)都在。但有一種情況例外頁面內(nèi)部在initState里做了異步加載而這個異步結(jié)果依賴頁面 Lifecycle那每次回到頁面時如果觸發(fā)了重建就可能看到加載態(tài)。我的做法是讓頁面成為狀態(tài)的“消費者”而不是“持有者”。所有數(shù)據(jù)都放在全局 Cubit 里頁面只負(fù)責(zé)根據(jù) Cubit 狀態(tài)渲染。這樣無論頁面怎么跳轉(zhuǎn)、怎么重建只要 Cubit 還活著數(shù)據(jù)就在。頁面切回來時可能畫面會閃一下但不會出現(xiàn)數(shù)據(jù)丟失。真想在頁面回到前臺時刷新數(shù)據(jù)可以監(jiān)聽系統(tǒng)側(cè)的事件通知比如通過 EventChannel 在提醒觸發(fā)事件到達(dá)時更新狀態(tài)這比依賴頁面生命周期要可靠得多。5. 常見問題與排查技巧實錄5.1 構(gòu)建與工具鏈高頻踩坑折騰這套環(huán)境的過程里構(gòu)建問題占了很大比重而且很多報錯在網(wǎng)上查不到直接答案。我把遇到過的三類典型問題列一下做個速查參考。問題現(xiàn)象常見原因解決思路構(gòu)建提示 Gradle 插件應(yīng)用方式錯誤ohos 殼工程里插件聲明方式不兼容修改 settings.gradle 中的插件應(yīng)用方式改為聲明式引入類似 Flutter 官方 Android 工程的插件管理方式Flutter SDK 版本不被當(dāng)前工具鏈識別命令行用的 Flutter 和環(huán)境變量里的版本不是同一套嚴(yán)格檢查where flutter確保指向 OpenHarmony 分支 SDK版本組合以官方文檔為準(zhǔn)編譯失敗提示某個第三方 Flutter 包缺少 ohos 實現(xiàn)插件沒有適配 OpenHarmony 平臺檢查插件是否有 ohos 支持沒有就換等效方案或者在橋接層自己實現(xiàn)對應(yīng)能力第一類問題最容易被忽略因為錯誤信息長且涉及 Gradle 內(nèi)部邏輯很多人會以為是自己項目配置寫錯了。實際上就是平臺殼工程和 Flutter 插件之間的集成方式差異調(diào)整插件聲明格式基本就能解決。第三類問題更值得提前預(yù)防。選 Flutter 插件時先看它有沒有 ohos 目錄沒有的話基本用不了。像 flutter_local_notifications 這種社區(qū)熱門插件在 OpenHarmony 分支下的適配不一定完善我的方案就是直接在橋接層用 MethodChannel 自己實現(xiàn)通知繞開了插件限制反而更可控。5.2 運(yùn)行時與UI兼容問題構(gòu)建通過只是第一關(guān)運(yùn)行時的問題更隱蔽。我在這個項目里碰到過兩個有代表性的問題分享出來供參考。第一個是頁面切換動畫異常。Flutter 的頁面切換默認(rèn)動畫在部分 OpenHarmony 真機(jī)上表現(xiàn)不佳偶爾會出現(xiàn)跳變或卡頓。這和 Flutter 的 Impeller 渲染引擎在 OpenHarmony 分支上的兼容性有關(guān)渲染層適配還沒有做到像 Android 那樣成熟的水平。我暫時不追求解決引擎層的問題而是在用戶體驗上做補(bǔ)償把不必要的頁面切換動畫關(guān)掉或者用更輕的自定義過渡。實測下來減少動畫復(fù)雜度之后切換流暢度改善明顯。如果你的項目對頁面切換動畫要求很高建議先在目標(biāo)設(shè)備上做真機(jī)驗證評估動畫是否可接受。第二個是通知權(quán)限缺失。在桌面調(diào)試時點擊記錄喝水沒問題但設(shè)置提醒后一到時間完全沒反應(yīng)看原生日志才意識到是通知權(quán)限沒申請。這里有個容易漏掉的細(xì)節(jié)OpenHarmony 側(cè)的應(yīng)用權(quán)限聲明和 Android 不同需要在工程的配置文件中添加通知權(quán)限聲明同時運(yùn)行時可能需要用戶在系統(tǒng)設(shè)置里手動確認(rèn)。我的建議是在設(shè)置頁加入一個“檢查提醒權(quán)限”入口首次啟動時引導(dǎo)用戶完成授權(quán)避免功能裝好了但觸發(fā)不了這種尷尬場景。5.3 后臺提醒可靠性與電量優(yōu)化這個項目做的是生活助手類應(yīng)用用戶大概率會切到后臺甚至鎖屏所以提醒能不能準(zhǔn)點觸發(fā)是決定成敗的細(xì)節(jié)。但我也要說句實在話純 Flutter 層面的 Timer 在應(yīng)用進(jìn)入后臺后并不可靠特別是 OpenHarmony 這種對后臺任務(wù)有管控的系統(tǒng)。我的方案是分場景處理。App 在前臺時用 Dart 層 Timer 做準(zhǔn)點提醒響應(yīng)快、體驗順滑代碼也不用跨橋。App 進(jìn)入后臺或鎖屏后把提醒調(diào)度能力放到原生層利用系統(tǒng)級的通知調(diào)度能力來觸發(fā)。實現(xiàn)上Dart 側(cè)一收到生命周期變化事件就把計算好的下一次提醒時間傳給原生層原生層負(fù)責(zé)在系統(tǒng)側(cè)掛提醒。等提醒觸發(fā)原生層通過 EventChannel 把事件回傳Dart 側(cè)再補(bǔ)記杯數(shù)和刷新 UI。這個方案在電量表現(xiàn)上也算合理前臺 Timer 和后臺系統(tǒng)調(diào)度各管各的時間段不存在雙重喚醒。即使系統(tǒng)對后臺調(diào)度做了限制至少在前臺場景下功能是完整可用的不會因為后臺限制而顯得不可用。5.4 跨端生態(tài)版本差異的提醒最后補(bǔ)充一個不只針對 OpenHarmony 的普遍問題Flutter 生態(tài)的版本差異。寫代碼時如果打開網(wǎng)絡(luò)上的 Flutter 教程很多方案默認(rèn)跑的是某個特定版本換到 OpenHarmony 分支上往往細(xì)節(jié)對不上。比如有的包要求最新版 Dart 語法但 OpenHarmony 分支的 Flutter SDK 版本相對滯后就可能導(dǎo)致一堆依賴報版本低。所以在給項目選依賴包時我習(xí)慣去 pub.dev 看一眼包的歷史版本選一個和當(dāng)前 SDK 兼容性最好的而不是無腦最新版。這個習(xí)慣幫我省了很多莫名其妙的上手時間。6. 一點補(bǔ)充的想法做這個項目最大的感受是OpenHarmony 上寫 Flutter 應(yīng)用最大的成本不在寫業(yè)務(wù)代碼而在環(huán)境適配和原生橋接。業(yè)務(wù)層幾乎可以做到零修改移植過來真正花時間的是把通知、權(quán)限、后臺調(diào)度這些系統(tǒng)能力一個一個趟明白。如果你也準(zhǔn)備做類似的事我的建議是第一保持 Dart 業(yè)務(wù)層干凈把一切涉及系統(tǒng)能力的調(diào)用都收口到一個橋接服務(wù)里將來換平臺只改橋接層第二在做功能規(guī)劃時留出原生聯(lián)調(diào)的時間不要假設(shè)插件一定可用第三先在真機(jī)上跑通最小閉環(huán)再做完整功能環(huán)境問題的排查速度會快很多。這個喝水提醒功能只是整套生活助手 App 的一個模塊后續(xù)如果要加“運(yùn)動步數(shù)同步”“體重趨勢圖表”這些能力架構(gòu)上完全可以直接吃下狀態(tài)管理用 Cubit 擴(kuò)展新的領(lǐng)域模塊系統(tǒng)能力繼續(xù)走 MethodChannel / EventChannel 橋接數(shù)據(jù)層增加對應(yīng)的存儲結(jié)構(gòu)就行。走到這一步項目的地基算是打穩(wěn)了。