当前位置: 首页 > news >正文

第72篇 | HarmonyOS 分享降级:近场能力不可用时回到系统分享

第72篇 | HarmonyOS 分享降级:近场能力不可用时回到系统分享

第 72 篇讲分享降级。真实设备环境很复杂:有的设备支持碰一碰但不支持隔空抓取,有的系统防护能力未开启,有的分享面板可能被用户取消。一个训练营项目想写得像完整作品,不能只展示“成功路径”,还要让能力不可用时有可继续操作的出口。

双镜记忆相机的降级策略比较清晰:ShareKit 注册失败不会阻塞相册;近场分享没有内容时主动拒绝;系统分享作为通用出口保留在详情页和视频管理页;分享过程用 busy 状态防重复点击。这篇我们把这些兜底点串起来看。

本篇目标

  • 理解 ShareKit 部分能力失败时为什么不应该阻断页面。
  • 掌握系统分享面板作为通用降级出口的写法。
  • 理解systemShareBusy如何避免重复拉起分享。
  • 学会把近场分享、一键成片和普通系统分享放进同一套兜底策略。

对应源码位置

  • superImage/entry/src/main/ets/pages/Index.ets

降级体验要让用户还有路可走

从运行效果看,用户并不会感知到底是 ShareKit 近场分享还是 SystemShare 面板;用户只需要知道照片能不能发出去。项目在详情页保留了系统分享按钮,在视频管理页也能把素材交给系统能力生成或分享,这些都是降级体验的一部分。

降级不是“失败后弹一句话”这么简单,而是要在功能设计阶段就提供第二条路径。近场能力不可用时,相册仍然可浏览;隔空抓取注册失败时,碰一碰可以继续;两者都不可用时,系统分享按钮仍然可以完成文件流转。

地图和相册流程之外仍然保留系统级分享出口

注册失败要区分可忽略和需要提示

在注册 ShareKit 能力时,项目把碰一碰和隔空抓取分开处理。隔空抓取注册失败后,如果碰一碰已经成功,就不更新错误文案;如果两个都失败,再根据错误码决定是否提示。错误码 801 这类“不支持”场景会安静处理,避免页面反复出现无意义提示。

这类处理对真实设备非常重要。训练营文章写到这里时,可以提醒读者:不是所有错误都应该以弹窗形式展示。设备不支持某个增强能力时,保持主流程可用,才是面向用户的降级。

注册 knockShare 和 gesturesShare 时区分部分成功和全部失败

private async registerNearbyShareListeners(): Promise<void> { let ready = this.knockShareRegistered || this.gesturesShareRegistered; if (!this.knockShareRegistered) { try { harmonyShare.on('knockShare', this.nearbyShareCallback); this.knockShareRegistered = true; ready = true; } catch (error) { } } if (!this.gesturesShareRegistered) { try { const registry = await this.createSendCapabilityRegistry(); harmonyShare.on('gesturesShare', registry, this.nearbyShareCallback); this.gesturesShareRegistry = registry; this.gesturesShareRegistered = true; ready = true; } catch (error) { if (!this.knockShareRegistered) { const err = error as BusinessError; this.nearbyShareStatusText = err.code === 801 ? '' : `附近分享初始化失败:${err.message ?? err.code ?? -1}`; } } } this.nearbyShareReady = ready; if (ready) { this.nearbyShareStatusText = ''; } }

系统分享面板是最稳的通用出口

系统分享面板不依赖附近设备和手势能力,只要文件路径和媒体类型准备正确,就可以作为普通分享出口。项目里showSystemSharePanel把 SharedData 交给ShareController,并设置单选和详情预览模式。

当 ShareKit 能力不可用时,用户仍然可以通过这个入口把照片发到其他应用、保存到系统能力或继续进入后续流程。对课程文章来说,这一节应该强调“降级不是另写一套数据”,而是复用buildSharedData

系统分享面板复用 SharedData 构建结果

private async showSystemSharePanel(items: Array<LocalShareItem>): Promise<void> { if (items.length === 0) { throw new Error(''); } try { const sharedData = this.buildSharedData(items); const controller: systemShare.ShareController = new systemShare.ShareController(sharedData); await controller.show(this.getAbilityContext(), { selectionMode: systemShare.SelectionMode.SINGLE, previewMode: systemShare.SharePreviewMode.DETAIL }); } catch (error) { const err = error as BusinessError; throw new Error(`拉起系统分享面板失败:${err.message ?? err.code ?? 'unknown'}`); } }

普通照片分享也要防重复点击

shareRecordWithSystemShare的第一行就判断systemShareBusy。这不是小细节,系统分享面板是异步拉起的,如果用户连续点击,很容易出现多个面板、状态错乱或二次失败。busy 状态让每次分享都有明确开始和结束。

函数还处理了空文件列表、分享中状态、成功清空状态和失败文案。这样即使系统面板失败,页面也能恢复按钮可点状态。降级链路最怕失败后卡死,finally里恢复 busy 就是兜底闭环。

shareRecordWithSystemShare 用 busy 状态保护系统分享流程

private async shareRecordWithSystemShare( record: GalleryMoment, scope: 'gallery' | 'vault' ): Promise<void> { if (this.systemShareBusy) { return; } const shareItems = this.buildRecordShareItems(record); if (shareItems.length === 0) { this.updateRecordExportStatus(scope, ''); return; } this.systemShareBusy = true; this.updateRecordExportStatus(scope, `正在分享 ${shareItems.length} 个文件...`); try { await this.showSystemSharePanel(shareItems); this.updateRecordExportStatus(scope, ''); } catch (error) { const message = error instanceof Error ? error.message : JSON.stringify(error); this.updateRecordExportStatus(scope, `系统分享失败:${message}`); } finally { this.systemShareBusy = false; } }

一键成片也复用分享兜底

视频管理页的openHarmonyOneClickMovieForSelection先检查素材数量,再把选中的照片转为分享项,最后调用同一个showSystemSharePanel。这说明系统分享面板不仅是普通照片出口,也能承接“把照片交给系统能力继续处理”的场景。

这里的状态闭环同样完整:素材不足直接返回,分享中展示数量,成功后追加视频管理记录并跳转预览,失败时展示系统分享失败。一个高质量实战项目应该像这样,把降级出口和业务后续动作都写清楚。

一键成片入口同样复用 showSystemSharePanel

private async openHarmonyOneClickMovieForSelection(): Promise<void> { if (this.systemShareBusy) { return; } const sourceRecords = this.getSelectedVideoRecords(); if (sourceRecords.length < 2) { this.harmonyMovieStatusText = ''; return; } const shareItems = this.buildVideoPhotoShareItems(sourceRecords); if (shareItems.length < 2) { this.harmonyMovieStatusText = ''; return; } this.systemShareBusy = true; this.harmonyMovieStatusText = `正在分享 ${shareItems.length} 个文件...`; try { await this.showSystemSharePanel(shareItems); this.harmonyMovieStatusText = ''; await this.appendSystemVideoManagerRecord(sourceRecords); this.galleryMediaTab = 'video'; const latestVideoRecord = this.getVideoManagerRecordsForRender()[0]; if (latestVideoRecord && latestVideoRecord.mode === 'normal' && this.canPreviewVideoManagerRecord(latestVideoRecord)) { this.selectedVideoManagerRecordId = latestVideoRecord.id; this.videoPreviewFrameIndex = 0; this.galleryViewMode = 'videoPreview'; } else { this.galleryViewMode = 'album'; } } catch (error) { const message = error instanceof Error ? error.message : JSON.stringify(error); this.harmonyMovieStatusText = `系统分享失败:${message}`; } finally { this.systemShareBusy = false; } }

工程检查清单

  • 部分能力注册失败时不要阻塞页面主流程。
  • 系统分享面板要复用同一套 SharedData 构建逻辑。
  • 分享按钮必须有 busy 状态防重复点击。
  • 失败后通过finally恢复状态,不能让按钮永久不可用。

今日练习

  1. 模拟gesturesShare抛出 801,检查页面是否保持安静。
  2. 在系统分享失败分支里输出错误信息,观察状态是否恢复。
  3. 把视频素材数量改成 1,确认一键成片不会拉起分享面板。

训练营里的每一篇都建议按同一个节奏复盘:先看页面行为,再回到源码定位状态和服务层,最后自己改一个很小的参数验证结果。这样写文章时不会停留在 API 名词,读者也能沿着真实工程把功能跑通。

http://www.rkmt.cn/news/1492720.html

相关文章:

  • FastbootEnhance:3倍效率提升的Android设备终极管理解决方案
  • HCS12嵌入式内核升级:从M68HC11到高效指令集与寻址模式解析
  • 大模型伦理使用实操指南:从提问到交付的七步校验法
  • 跟我一起学“计算机网络”通识-网络概述
  • 2026年6月最新版盐城第三方CMACNAS甲醛检测治理口碑名单:万清CMA检测中心等5家深度测评 - 一休咨询
  • 遗传算法三大算子深度解析:选择、交叉、变异的工程调优逻辑
  • D48: 性能与信息保护的平衡实践
  • 有哪些高效的NOI省选专题题目解题技巧
  • 京华ALTDH382SS PCIe转RS232串口卡原厂驱动包(Win7/Win10双系统支持)
  • 太阳能领域情感分析实战:NLP舆情监测轻量级方案
  • WinUI 3项目实战:手把手教你用C#和Windows App SDK打造一个Fluent Design风格的应用界面
  • 基于扩散模型的 UI 图标生成:风格一致性控制与工程落地
  • 2026最新 孩子英语发音不标准 实用的发音纠正听说软件推荐
  • 淮安劳力士+欧米茄手表专业回收,26年精选回收店铺排行榜推荐 - 莘州文化
  • COM3D2.MaidFiddler:3分钟上手的游戏实时编辑器完全指南
  • 09-Plugins 上篇:安装、使用与社区生态 —— 一键安装全家桶
  • 别再直接转unsigned short了!深入理解fp16与float互转的IEEE 754标准(附C代码详解)
  • PHP树结构实现与遍历算法
  • Python开发工程师全景解析:岗位职责·各城市薪资·发展前景·高考志愿填报(2026版)
  • Off-Policy Actor-Critic 与重要性采样
  • 99个免费公共Tracker终极指南:让BT下载速度飙升300%的完整方案
  • 2024 LLM开发实操指南:本地化部署与RAG微调全链路
  • LLM代理层消亡史:当模型原生能力让网关退化为透传器
  • 吉安法穆兰+卡地亚手表专业回收,26年精选回收店铺排行榜推荐 - 莘州文化
  • 嘉定区配镜深度调研:行业洗牌下,本土品牌如何突围?—— 以嘉艺眼镜公场为例 - 国麟测评
  • LLM技术雷达:推理优化、长上下文与评估可信度实战指南
  • douyin-downloader:如何通过三层架构设计实现抖音内容的高效批量采集
  • 高校信息安全课用的Python版CA证书系统(带源码+部署指南+全流程截图)
  • 深度解析 Deep-Live-Cam:从原理到实战的 AI 换脸技术指南
  • 如何快速掌握Calibre豆瓣元数据插件:面向电子书爱好者的完整解决方案