第72篇 | HarmonyOS 分享降级近场能力不可用时回到系统分享第 72 篇讲分享降级。真实设备环境很复杂有的设备支持碰一碰但不支持隔空抓取有的系统防护能力未开启有的分享面板可能被用户取消。一个训练营项目想写得像完整作品不能只展示“成功路径”还要让能力不可用时有可继续操作的出口。双镜记忆相机的降级策略比较清晰ShareKit 注册失败不会阻塞相册近场分享没有内容时主动拒绝系统分享作为通用出口保留在详情页和视频管理页分享过程用 busy 状态防重复点击。这篇我们把这些兜底点串起来看。本篇目标理解 ShareKit 部分能力失败时为什么不应该阻断页面。掌握系统分享面板作为通用降级出口的写法。理解systemShareBusy如何避免重复拉起分享。学会把近场分享、一键成片和普通系统分享放进同一套兜底策略。对应源码位置superImage/entry/src/main/ets/pages/Index.ets降级体验要让用户还有路可走从运行效果看用户并不会感知到底是 ShareKit 近场分享还是 SystemShare 面板用户只需要知道照片能不能发出去。项目在详情页保留了系统分享按钮在视频管理页也能把素材交给系统能力生成或分享这些都是降级体验的一部分。降级不是“失败后弹一句话”这么简单而是要在功能设计阶段就提供第二条路径。近场能力不可用时相册仍然可浏览隔空抓取注册失败时碰一碰可以继续两者都不可用时系统分享按钮仍然可以完成文件流转。地图和相册流程之外仍然保留系统级分享出口注册失败要区分可忽略和需要提示在注册 ShareKit 能力时项目把碰一碰和隔空抓取分开处理。隔空抓取注册失败后如果碰一碰已经成功就不更新错误文案如果两个都失败再根据错误码决定是否提示。错误码 801 这类“不支持”场景会安静处理避免页面反复出现无意义提示。这类处理对真实设备非常重要。训练营文章写到这里时可以提醒读者不是所有错误都应该以弹窗形式展示。设备不支持某个增强能力时保持主流程可用才是面向用户的降级。注册 knockShare 和 gesturesShare 时区分部分成功和全部失败private async registerNearbyShareListeners(): Promisevoid { 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: ArrayLocalShareItem): Promisevoid { 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 ): Promisevoid { 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。这说明系统分享面板不仅是普通照片出口也能承接“把照片交给系统能力继续处理”的场景。这里的状态闭环同样完整素材不足直接返回分享中展示数量成功后追加视频管理记录并跳转预览失败时展示系统分享失败。一个高质量实战项目应该像这样把降级出口和业务后续动作都写清楚。一键成片入口同样复用 showSystemSharePanelprivate async openHarmonyOneClickMovieForSelection(): Promisevoid { 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恢复状态不能让按钮永久不可用。今日练习模拟gesturesShare抛出 801检查页面是否保持安静。在系统分享失败分支里输出错误信息观察状态是否恢复。把视频素材数量改成 1确认一键成片不会拉起分享面板。训练营里的每一篇都建议按同一个节奏复盘先看页面行为再回到源码定位状态和服务层最后自己改一个很小的参数验证结果。这样写文章时不会停留在 API 名词读者也能沿着真实工程把功能跑通。
第72篇 | HarmonyOS 分享降级:近场能力不可用时回到系统分享
发布时间:2026/6/9 11:44:44
第72篇 | HarmonyOS 分享降级近场能力不可用时回到系统分享第 72 篇讲分享降级。真实设备环境很复杂有的设备支持碰一碰但不支持隔空抓取有的系统防护能力未开启有的分享面板可能被用户取消。一个训练营项目想写得像完整作品不能只展示“成功路径”还要让能力不可用时有可继续操作的出口。双镜记忆相机的降级策略比较清晰ShareKit 注册失败不会阻塞相册近场分享没有内容时主动拒绝系统分享作为通用出口保留在详情页和视频管理页分享过程用 busy 状态防重复点击。这篇我们把这些兜底点串起来看。本篇目标理解 ShareKit 部分能力失败时为什么不应该阻断页面。掌握系统分享面板作为通用降级出口的写法。理解systemShareBusy如何避免重复拉起分享。学会把近场分享、一键成片和普通系统分享放进同一套兜底策略。对应源码位置superImage/entry/src/main/ets/pages/Index.ets降级体验要让用户还有路可走从运行效果看用户并不会感知到底是 ShareKit 近场分享还是 SystemShare 面板用户只需要知道照片能不能发出去。项目在详情页保留了系统分享按钮在视频管理页也能把素材交给系统能力生成或分享这些都是降级体验的一部分。降级不是“失败后弹一句话”这么简单而是要在功能设计阶段就提供第二条路径。近场能力不可用时相册仍然可浏览隔空抓取注册失败时碰一碰可以继续两者都不可用时系统分享按钮仍然可以完成文件流转。地图和相册流程之外仍然保留系统级分享出口注册失败要区分可忽略和需要提示在注册 ShareKit 能力时项目把碰一碰和隔空抓取分开处理。隔空抓取注册失败后如果碰一碰已经成功就不更新错误文案如果两个都失败再根据错误码决定是否提示。错误码 801 这类“不支持”场景会安静处理避免页面反复出现无意义提示。这类处理对真实设备非常重要。训练营文章写到这里时可以提醒读者不是所有错误都应该以弹窗形式展示。设备不支持某个增强能力时保持主流程可用才是面向用户的降级。注册 knockShare 和 gesturesShare 时区分部分成功和全部失败private async registerNearbyShareListeners(): Promisevoid { 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: ArrayLocalShareItem): Promisevoid { 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 ): Promisevoid { 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。这说明系统分享面板不仅是普通照片出口也能承接“把照片交给系统能力继续处理”的场景。这里的状态闭环同样完整素材不足直接返回分享中展示数量成功后追加视频管理记录并跳转预览失败时展示系统分享失败。一个高质量实战项目应该像这样把降级出口和业务后续动作都写清楚。一键成片入口同样复用 showSystemSharePanelprivate async openHarmonyOneClickMovieForSelection(): Promisevoid { 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恢复状态不能让按钮永久不可用。今日练习模拟gesturesShare抛出 801检查页面是否保持安静。在系统分享失败分支里输出错误信息观察状态是否恢复。把视频素材数量改成 1确认一键成片不会拉起分享面板。训练营里的每一篇都建议按同一个节奏复盘先看页面行为再回到源码定位状态和服务层最后自己改一个很小的参数验证结果。这样写文章时不会停留在 API 名词读者也能沿着真实工程把功能跑通。