尧图精选

HarmonyOS 7 精准碰一碰 SlideDrop 实战 05:状态同步 + 生命周期收口完成演示协作工作台【鸿蒙心迹】

🕒 发布时间:2026/10/2 11:40:19 📁 来源:尧图网络
这套 SlideDrop 五连载写到第五篇我反而没有继续追着“新能力”往前冲。前四篇已经把主链路、精准落位、规则判断、冲突恢复都讲得差不多了最后这一篇如果还继续堆新点就容易把整套 Demo 写散。第五篇我更想做的一件事是把前面零散补起来的能力真正收住状态怎么同步、页面退出以后资源怎么释放、会话怎么归零、最近结果怎么保留、最后又怎么做一轮完整验收。说白了第五篇不是“再做一个功能”而是把 SlideDrop 从“能演示”收成“能交代”的工程 Demo。它不一定最热闹但它决定这个五连载最后是不是站得稳。一、为什么第五篇不再追新功能而是开始做工程收口做到第四篇的时候SlideDrop 其实已经挺像一个完整 Demo 了手机端可以发起精准碰一碰目标区域可以识别并完成落位素材类型与槽位规则已经建立起来冲突替换、取消、超时、重试这些边界也有了基本闭环。但如果站在“文章完结”和“项目交付”的角度看前四篇还差最后一层东西。那就是系统在任务结束以后能不能回到一个干净、稳定、可继续演示的状态。这个问题其实比前面任何一篇都更像真实工程。因为功能做出来以后最容易出问题的往往不是“执行时”而是“执行完之后”。比如页面退出了但上一次会话状态还留在内存里最近结果列表能看到记录可是对应缓存没有清掉预览图还在占着本地空间失败重试队列其实已经没意义了但没有释放一轮 Demo 演示跑完以后再重新开始第二轮会发现界面还带着上一次的痕迹。这些问题看起来都不是大 Bug但它们会直接影响读者对 Demo 的判断这到底是一个“演示后就乱”的半成品还是一个“跑完一轮还能马上继续演示”的工程化样品。所以第五篇我给自己定下来的核心目标是四个字收口、同步。收口把任务、会话、页面和资源统一收回到干净状态同步让手机端、页面端、日志端、结果端都对同一轮任务有一致认知。二、第五篇的主线让 SlideDrop 从“跑通”变成“跑完也稳定”如果说第一篇是搭主链路第二篇是做落位第三篇是做插入规则第四篇是补异常恢复那第五篇很自然就该进入“工程收官”阶段。我后来把第五篇真正想讲的东西收成了下面这条线会话状态同步 → 页面生命周期收尾 → 本地资源释放 → 最近结果保留 → 最终验收输出。这条线看起来没有前三篇那么“显眼”但它非常重要。因为 SlideDrop 这种跨设备演示 Demo并不是只在某一帧 UI 上成立它更像一段完整过程开始、传输、结束、清理、再开始。如果结束以后收不干净前面做得再漂亮后面也会越来越脏。所以第五篇我不是再去扩展一个新页面而是尽量让整个 Demo 的末端逻辑变得更顺当前会话结束以后状态中心能立刻同步到“空闲”最近结果区保留本轮成功记录便于文章展示与后续验收预览缓存、临时文件、失败任务队列都能及时释放页面重新进入时不会被旧状态污染最终还能输出一份“验收通过”的可见结果。从文章写作角度看第五篇也是我最愿意写“工程手记”的一篇。因为它不再只是讲某个局部技巧而是在讲一个 Demo 怎么真正收束成作品。三、第五篇我重新看了一遍项目目录确定最后该收哪些层到第五篇时目录结构已经比较完整了这一篇我没有再大拆而是重新确认哪些层需要做最后的收尾。SlideDropDemo ├─ entry │ ├─ src/main/ets │ │ ├─ pages │ │ │ ├─ HomePage.ets │ │ │ ├─ SlideDropPage.ets │ │ │ ├─ ResultPage.ets │ │ │ └─ ProfilePage.ets │ │ ├─ components │ │ │ ├─ ProgressCard.ets │ │ │ ├─ ResultItem.ets │ │ │ └─ StatusCard.ets │ │ ├─ manager │ │ │ ├─ TransferManager.ets │ │ │ ├─ ConflictRecoveryManager.ets │ │ │ └─ FinalAcceptanceService.ets │ │ ├─ store │ │ │ ├─ TransferStore.ets │ │ │ └─ AppStore.ets │ │ ├─ model │ │ │ ├─ TransferMission.ets │ │ │ ├─ RecentResult.ets │ │ │ └─ SessionInfo.ets │ │ ├─ utils │ │ │ ├─ FileUtil.ets │ │ │ ├─ SessionUtils.ets │ │ │ └─ DateUtil.ets │ │ └─ common │ │ └─ Logger.ets │ └─ resources └─ module.json5这一版目录我最关心的是三个点TransferStore负责把当前会话、最近结果、同步状态放在统一位置SlideDropPage负责页面进入和离开时的状态同步与清理FinalAcceptanceService负责最后做一致性校验、资源释放检查和验收快照。也就是说第五篇并不是重写项目而是把前面几篇零散分布的“结束动作”重新聚拢起来。四、先把状态同步做好否则页面一退出前面的结果全都可能失真我自己做这类 Demo 时最怕出现的情况不是“功能没做出来”而是“功能做出来了但状态散在各处”。比如页面上显示成功了日志还停在传输中或者最近结果列表更新了但当前会话却没有被清空。这种“不一致”比直接失败更烦因为它最容易把读者绕晕。所以第五篇第一件事我先回头把TransferStore再收了一遍让当前会话和最近结果都通过同一个 Store 管理。文件位置entry/src/main/ets/store/TransferStore.ets用途统一维护当前会话状态、最近结果、同步进度和页面共享数据。export enum SessionState { IDLE IDLE, WAITING WAITING, TRANSFERRING TRANSFERRING, SUCCESS SUCCESS, FAILED FAILED } export interface SessionInfo { id: string status: SessionState progress: number fileName: string updateTime: number } export class TransferStore { private static instance: TransferStore private currentSession?: SessionInfo private recentResults: RecentResult[] [] static getInstance(): TransferStore { if (!TransferStore.instance) { TransferStore.instance new TransferStore() } return TransferStore.instance } updateSession(session: SessionInfo) { this.currentSession session } getCurrentSession(): SessionInfo | undefined { return this.currentSession } resetCurrentSession() { this.currentSession { id: , status: SessionState.IDLE, progress: 0, fileName: , updateTime: Date.now() } } pushRecentResult(result: RecentResult) { this.recentResults.unshift(result) this.recentResults this.recentResults.slice(0, 20) } getRecentResults(): RecentResult[] { return this.recentResults } }这段代码看起来很基础但它把第五篇最重要的一件事定下来了页面不是自己保存状态页面只是消费状态。这样一来SlideDrop 页面、结果页、状态页看到的都是同一份会话数据。任务结束以后系统要回到IDLE也只需要在一个入口里统一重置。五、生命周期收尾必须认真做aboutToAppear 和 aboutToDisappear 终于成了关键角色我以前写 Demo 时经常把生命周期函数当成“顺手写一下”。但到第五篇我反而觉得它们变成了整套 Demo 能不能收住的关键。原因很直接页面进入时你得把上次会话同步回来页面离开时你得把该释放的资源释放掉如果这里做得马虎前面所有状态同步都会被拖垮。所以第五篇里我重点收的是SlideDropPage.ets。这次不再把它仅仅当作一个显示页面而是让它真正承担起“页面级的状态同步与资源回收”。文件位置entry/src/main/ets/pages/SlideDropPage.ets用途页面进入时同步当前会话页面离开时完成缓存释放、重试队列清理和会话归零。import { AppStorage } from ohos.storage import { TransferStore, SessionState } from ../store/TransferStore import { syncCurrentSession, clearPendingTasks, releasePreviewCache } from ../utils/SessionUtils Entry Component struct SlideDropPage { State currentTab: number 0 State sessionState: SessionState SessionState.IDLE private store: TransferStore TransferStore.getInstance() aboutToAppear(): void { console.info([SlideDrop] [Appear] page aboutToAppear) syncCurrentSession().then((session) { this.sessionState session.status this.store.updateSession(session) console.info([SlideDrop] [Appear] sync page state, sessionId session.id) }).catch((err) { console.error([SlideDrop] [Appear] sync page state failed: err) }) } aboutToDisappear(): void { console.info([SlideDrop] [Dispose] page aboutToDisappear) releasePreviewCache().then(() { console.info([SlideDrop] [Dispose] release preview cache) }) clearPendingTasks().then(() { console.info([SlideDrop] [Dispose] clear pending retry tasks) }) AppStorage.setOrCreateSessionState(slideDrop_session_state, SessionState.IDLE) this.store.resetCurrentSession() console.info([SlideDrop] [State] session reset to IDLE) } }这段代码最直接的价值就是把“演示结束以后要干什么”说清楚了释放预览缓存清理失败重试任务当前会话重置为IDLE下次再进入页面时从干净状态重新开始。对于读者来说这种代码很有说服力。因为它不是为了凑一个生命周期 API而是真在解决“跑完一轮以后能不能马上再演示一次”的问题。六、第五篇里我最在意的是“成功结果要保留运行痕迹要清掉”做工程收口时很容易出现一个极端要么什么都不保留任务一结束页面像没发生过一样要么什么都保留页面上到处都是旧痕迹。SlideDrop 第五篇我想要的不是这两个极端里的任何一个。我最后给自己定的原则是成功结果要保留运行痕迹要清掉。什么意思当前会话卡片里的“传输中”“等待连接”这些状态是运行痕迹任务结束以后应该清掉最近结果列表里的“第 4 章产品设计方案传输成功”这种记录是成果信息应该保留预览缓存和临时文件是技术过程中的中间产物应该释放验收结果和统计快照是最后要对外展示的证据可以保留。第五篇最让我满意的一点就是我终于把这四类信息分清楚了。以前很多 Demo 做着做着什么该留、什么该删会越来越模糊。到最后页面虽然热闹但结构是乱的。现在 SlideDrop 到第五篇这个边界已经清楚了。七、手机端状态页必须讲人话用户要看懂“这一轮已经收完了”技术文章配图里手机图最容易只顾好看不顾表达。第五篇我反而特别在意手机端状态页是不是够“像人话”。因为这一页本质上承担的是“告诉用户这轮任务已经收口”的责任。所以我在这一页里保留了四组信息当前会话已经完成清理状态已经通过最近结果还能看到刚刚成功的记录验收清单已经走完。这四组信息组合起来读者一眼就能看懂这不是单纯显示一个成功图标而是在说明本轮传输、资源释放、状态归位都已经处理结束。八、最后要有一个真正的“验收出口”所以我补了 FinalAcceptanceService如果第五篇只停在“生命周期清理”这一层工程意味还是不够强。因为它只是做了过程整理还没有给出一个明确的“最后结论”。所以这篇我又补了一层FinalAcceptanceService把最后那一步做完整做一轮一致性检查并给出验收结果。我做这个类时其实只想解决三个问题当前状态是不是一致资源是不是真的清掉了最近结果是不是还能查到。如果这三件事都成立我就认为这一轮 Demo 可以被视为“完成收口”。文件位置entry/src/main/ets/manager/FinalAcceptanceService.ets用途做状态一致性校验、资源释放检查、最近结果验证并生成演示快照。import { AppStore } from ../store/AppStore import { FileUtil } from ../utils/FileUtil import { Logger } from ../common/Logger export class FinalAcceptanceService { private store: AppStore AppStore.getInstance() private logger: Logger Logger.getInstance() async validateStateConsistency(): Promiseboolean { const storeState this.store.getState() const queueEmpty storeState.transferQueue.length 0 const activeTask storeState.currentTransfer undefined const resultCountOk storeState.recentResults.length 0 const pass queueEmpty activeTask resultCountOk this.logger.info([Accept] state consistency ${pass ? pass : fail}) return pass } async validateResourceRelease(): Promiseboolean { const tempDir ${this.store.getCacheDir()}/temp const files await FileUtil.listFiles(tempDir) const released files.length 0 this.logger.info([Accept] resource release ${released ? pass : fail}) return released } async validateRecentResult(): Promiseboolean { const results this.store.getRecentResults() const hasSuccess results.some(r r.status success) this.logger.info([Accept] recent result ${hasSuccess ? found : not found}) return hasSuccess } async runFinalAcceptance(): Promiseboolean { const s1 await this.validateStateConsistency() const s2 await this.validateResourceRelease() const s3 await this.validateRecentResult() const pass s1 s2 s3 this.logger.info([Accept] final acceptance ${pass ? SUCCESS : FAILED}) return pass } }这段代码让我觉得第五篇终于有了一个“结束句号”。因为你前面无论做了多少同步、多少清理最后总要有个地方能说清楚这一轮到底算不算完整结束。九、类型二综合讲解图这一篇我最想强调的是“从同步到清理的完整闭环”前几篇你已经让我固定了两种图的语言简约封面和类型二综合讲解图。到第五篇这张类型二图的意义比前面更大因为它不再只是解释一个单点逻辑而是把整条闭环压缩成了一张图。在这张图里我最想放进去的是四种证据DevEco 里的状态监听与页面刷新代码手机端的同步完成与清理状态界面中间的状态流 / 资源流关系图下方的实机传输过程。这样读者一眼就会明白第五篇不是在讲“某一个按钮怎么写”而是在讲一轮跨设备演示任务结束以后系统怎样把状态同步、会话管理、资源清理和最终验收收成一个闭环。十、这一篇我怎么验收重点已经不是功能成立而是“下一轮还能不能马上继续”第五篇的验收方式和前几篇已经很不一样了。前面几篇更像“这条功能链路是不是成立”而这一篇更像“这一轮结束以后系统还能不能马上继续工作”。我这次基本按下面几条来验收。1当前会话是否能回到 IDLE这是最基础的一条。传输完成以后当前会话页不能一直挂着上一次任务信息而是要回到空闲态明确告诉用户“现在没有进行中的任务”。2最近结果是否被保留虽然当前任务状态要清掉但最近结果区必须能看到上一轮成功记录。这样读者和用户都能回看刚才发生了什么也方便文章截图佐证。3缓存与临时文件是否被释放这是第五篇的重点之一。我会故意跑一轮带预览、带文件中转的完整流程再检查缓存和临时目录看释放动作是否真的执行。4失败重试队列是否被清空第四篇引入了重试机制第五篇必须把这件事收住。否则页面看起来结束了队列里还留着失败任务后面肯定会污染下一轮演示。5页面重新进入时是否干净我会退出页面再重新进入观察是否还会带上旧 sessionId、旧进度、旧文件名。如果这些痕迹还在就说明“收口”并没有真正完成。6最终验收日志是否清晰最后我会看日志有没有出现状态同步有没有出现资源释放有没有出现最近结果验证最终是否打印SUCCESS。只有这些日志和页面表现一致第五篇我才算真正写踏实了。十一、第五篇写完以后这个五连载终于真正收住了回头看这五篇其实它们像是在围着同一个 Demo 做五次不同维度的逼近第一篇解决“能不能建立主链路”第二篇解决“能不能落到正确位置”第三篇解决“素材类型和槽位规则能不能协同”第四篇解决“冲突、取消、重试能不能处理”第五篇解决“演示结束以后系统能不能自己收回来”。写到最后我越来越确定第五篇虽然不一定是最有冲击力的一篇但它非常关键。因为它让整个系列从“会做功能”走向“会做收尾”。而一个项目真正成熟往往就体现在这里。十二、本文小记如果让我用一句话总结第五篇我会说第五篇最重要的不是增加了多少新功能而是让 SlideDrop 终于具备了“完成一轮演示并回到稳定起点”的能力。它做的事情包括统一当前会话与最近结果的状态来源用生命周期函数把页面进入与离开真正管起来把预览缓存、失败任务和临时文件收干净用状态中心页把“收尾结果”对用户讲明白最后通过验收服务给出完整结论。如果前四篇是在搭房子那第五篇更像是在做收边、封口、清场。它不会比建主体更显眼但没有它整套 Demo 就始终差最后一口气。现在这口气总算补上了。而且写完这一篇以后我对这个五连载最满意的一点也终于确定下来它不是单纯围着一堆 API 做讲解而是真的围着一个 Demo 的成长过程在写。这样一来读者看到的不只是功能点而是一个项目从“能跑”到“能交代”的完整变化。十三、第五篇里我专门补了一轮“反向验证”看收尾逻辑是不是只在理想路径上成立工程收口这件事最怕的不是没写而是只在最理想的一条路径上成立。也就是说你在 happy path 里跑一遍页面确实变成了IDLE缓存似乎也释放了于是你以为问题解决了。但只要换一个顺序比如传到一半退出、成功以后快速重复打开、或者刚生成最近结果就立刻切到后台很多收尾逻辑就会开始露馅。所以第五篇我给自己多补了一轮“反向验证”。它不是为了多制造复杂度而是想确认这套收尾逻辑不是靠运气成立。我大致按下面几组场景去测1传输成功后立刻离开页面这个场景主要看两件事最近结果是否已经写入当前会话是否已经在离开前被重置。如果写入顺序不对很可能出现结果没保存、状态却先清空的情况。这样文章里看起来像“系统干净了”但用户会觉得“我刚刚传成功的记录怎么没了”。所以我在这一组里特别关注 recentResults 的写入时机。2传输过程中主动中断再返回首页这组场景不是为了验证成功路径而是为了验证页面销毁回调是否足够稳。因为一旦中断发生失败重试队列、临时文件、预览资源都可能残留。如果aboutToDisappear()只做表面清理没有真正处理好 pending task那么下一次重新进入页面系统会误以为还有任务在继续。读者可能看不出这是哪一层出的问题但会明显感觉“页面不干净”。3连续执行两轮演示任务这一组我觉得最接近实际使用。因为做社区文章、课程演示或线下分享时你几乎不会只跑一遍。往往是第一轮给大家看主链路第二轮再补某个细节。如果系统第二轮刚开始就带着第一轮的残留状态那整套 Demo 的可信度就会一下掉下来。所以我会连续传两次不同文件第一次传会议资料.pdf第二次传产品方案_v3.pptx然后观察当前会话是否仍然从IDLE起步最近结果是不是两条都能看到第二轮是否被第一轮的 sessionId、progress 或缓存影响。4切后台再回来这一组主要看页面重新激活时是否会误判当前状态。有些 Demo 最大的问题不是前台逻辑不工作而是用户切出去一下再回来就乱了。第五篇虽然没有把“前后台切换管理”单独展开成新能力但我至少希望回来以后页面不会突然显示旧进度已经完成的任务不会重新进入“传输中”状态中心依然能保持收口后的样子。做完这轮反向验证以后我心里会更踏实。因为这说明第五篇做的事情不只是把代码写齐了而是真的开始经得住一点变化。十四、这篇里几个容易被忽略但我觉得很值钱的实现细节写这篇时我越来越有一个感觉真正让 Demo 变成熟的不一定是最显眼的功能而是那些不太起眼、却能减少后续麻烦的细节。第五篇里我最想记下来的有下面几件。1把“空闲态”明确当成一种状态而不是默认空白很多人做页面收尾时会把“任务完成以后”理解成“把 UI 清空”。但我后来发现清空不是状态空闲才是状态。两者区别很大。清空意味着界面上什么都没有用户不知道当前系统是不是正常空闲意味着界面明确告诉用户没有进行中的任务但系统是准备好的。所以我在第五篇里反复强调IDLE并让手机端页面明确显示“当前无传输任务”。这不是字面小改动而是把页面从“消失”变成“可理解”。2最近结果限制长度不让状态页越用越重我在pushRecentResult()里保留了截断逻辑只保存最近 20 条结果。原因很现实如果把状态中心做成一个无限增长的列表短时间内看不出问题后面一定会越来越臃肿。Demo 虽然不像正式产品那样需要长期运行但既然第五篇是在做工程收口那我就不希望它把问题留给以后。限制长度是一个很简单的动作却能让页面、内存和写作叙述都更干净。3清理动作尽量可见而不是悄悄发生资源释放本身用户不一定看得见但我还是希望它是“可感知”的。比如状态页里能看到清理状态、日志里能看到 release 记录、验收清单里能看到资源释放通过。这些信息并不是为了让页面更热闹而是为了让“清理完成”成为一个能被验证的事实。如果一切清理都静默完成技术上也许没问题但文章层面就少了证据读者也更难建立信任。4日志语义尽量和正文表述保持一致我这几篇一直在刻意做一件事正文里怎么说日志里尽量就怎么表达。正文说“状态同步”日志就打印state consistency正文说“资源释放”日志就打印resource release正文说“最终验收”日志就打印final acceptance。这件事看起来很小但很有用。因为读者在图里看到一行日志时不需要再额外做一次语义映射他会自然知道这行日志对应文章哪一段描述。技术文章如果能做到这一点阅读成本会低很多。十五、写到第五篇我觉得 SlideDrop 真正完成的不是一个功能而是一种讲清楚工程的方式这套五连载一开始我的想法其实没有那么复杂。看到 HarmonyOS 7 的精准碰一碰能力我只是觉得很适合写一个和演示文稿素材插入相关的小 Demo。它天然有画面感也天然适合做文章。但一路写到第五篇我慢慢发现真正让我想继续写下去的不只是它的“新”而是它很适合把一个工程过程讲清楚。从第一篇到第五篇Demo 一直在变一开始只是把链路跑通后来开始关心落位和规则再往后开始关心异常和恢复最后终于开始关心同步、清理和验收。这其实就是一个很典型的工程成长路径。你会发现真正决定一个 Demo 有没有味道的往往不是第一步把功能做出来而是后面有没有继续把边界、状态、清理和闭环补完整。所以如果说第五篇和前几篇最大的不同是什么我会说它开始让整套连载带上一点“作品感”。所谓作品感不是页面多华丽也不是功能堆得多满而是你能从头到尾把这件事讲顺——它为什么存在它解决了什么它在异常下怎么处理它结束以后如何回到稳定起点。这样读者读完以后记住的就不只是“精准碰一碰能传文件”而是“我看到了一个 Demo 是怎么一步步被打磨出来的”。对我来说这反而是第五篇最有价值的部分。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →