React Native 三方库 react-native-share 的 HarmonyOS 适配实战
是 React Native 生态中最流行的社交分享库之一,Star 数超过 12K,支持将文本、图片、视频等内容分享到 Facebook、Twitter、WhatsApp、Instagram 等数十个社交平台。通用分享open):调起系统分享面板,用户自行选择目标应用定向分享):直接将内容分享到指定的社交应用应用检测):判断目标应用是否已安装Base64 检测):判断 URL 是否为 Base6
React Native 三方库 react-native-share 的 HarmonyOS 适配实战
本文记录了将开源 React Native 三方库
react-native-share从零到一适配到 HarmonyOS NEXT / OpenHarmony 平台的完整过程,包含适配思路、架构设计、代码改动对照、关键决策和踩坑复盘。
一、背景
1.1 三方库简介
react-native-share 是 React Native 生态中最流行的社交分享库之一,Star 数超过 12K,支持将文本、图片、视频等内容分享到 Facebook、Twitter、WhatsApp、Instagram 等数十个社交平台。其核心能力包括:
- 通用分享(
open):调起系统分享面板,用户自行选择目标应用 - 定向分享(
shareSingle):直接将内容分享到指定的社交应用 - 应用检测(
isPackageInstalled):判断目标应用是否已安装 - Base64 检测(
isBase64File):判断 URL 是否为 Base64 编码文件
1.2 适配目标
| 项目 | 原平台 | 适配目标 |
|---|---|---|
| 运行平台 | Android / iOS | HarmonyOS /OpenHarmony |
| 原生语言 | Kotlin / Java / Obj-C | ArkTS (ETS) |
| 分享机制 | Android Intent / iOS UIActivityViewController | HarmonyOS Want + SystemShare API |
| 架构模式 | Native Module | TurboModule (RNOH) |
1.3 为什么需要适配
随着鸿蒙生态的快速发展,越来越多的应用需要迁移到 HarmonyOS 平台。react-native-share 作为高频使用的基础能力库,其鸿蒙化适配是众多 RN 应用迁移的前置依赖。
二、适配路线图
整个适配过程分为以下四个阶段:
阶段一:项目初始化与结构搭建
↓
阶段二:核心架构设计与基类实现
↓
阶段三:21 个社交平台逐一实现
↓
阶段四:JS 层桥接与平台兼容性修复
三、逐步适配过程
3.1 阶段一:项目初始化与结构搭建
3.1.1 创建 HarmonyOS 模块目录
参考 rntpc_react-native-tts 等已适配项目的目录规范,建立如下结构:
harmony/rn_share/
├── index.ets # 模块入口
├── ts.ts # TS 导出
├── oh-package.json5 # 模块包配置
├── build-profile.json5 # 构建配置
├── BuildProfile.ets # 构建辅助
├── hvigorfile.ts # Hvigor 构建脚本
├── consumer-rules.txt # 混淆规则
├── obfuscation-rules.txt # 混淆规则
├── .gitignore
└── src/main/
├── module.json5 # HAR 模块声明
└── ets/
├── RNSharePackage.ts # RN 包注册
├── RNShareTurboModule.ts # TurboModule 实现
├── Types.ts # 类型定义
├── Logger.ts # 日志工具
├── utils/
│ ├── FileUtils.ts # 文件处理工具
│ └── Logger.ts # Logger 重导出
└── share/
├── Share.ts # 统一导出
├── ShareBaseInstance.ts # 分享基类
├── GenericShare.ts # 系统分享
├── FacebookShare.ts # Facebook
├── ... # 其他 21 个平台
└── shareMediaObject/ # 分享媒体对象
├── MediaObject.ts
├── TextObject.ts
├── MultiImageObject.ts
├── VideoSourceObject.ts
├── WebPageObject.ts
├── SuperGroupObject.ts
├── WeiboMultiMessage.ts
└── ShareMediaObject.ts
3.1.2 模块配置
oh-package.json5 — 声明模块为 HAR 包:
{
"name": "@react-native-ohos/react-native-share",
"version": "12.3.1-0.0.1",
"description": "React Native Share module for OpenHarmony",
"main": "index.ets",
"author": "jianguo",
"license": "MIT",
"dependencies": {
"@rnoh/react-native-openharmony": "file:../react_native_openharmony"
}
}
module.json5 — 声明模块类型和设备支持:
{
"module": {
"name": "rn_share",
"type": "har",
"deviceTypes": ["default", "tablet", "2in1"]
}
}
3.2 阶段二:核心架构设计与基类实现
3.2.1 类型系统设计(Types.ts)
首先定义整个模块的类型基础。Android/iOS 原生端使用字符串常量表示社交平台,鸿蒙端我们用枚举统一管理:
// Social 枚举:覆盖所有支持的社交平台
export enum Social {
Facebook = 'facebook',
Generic = 'generic',
FacebookStories = 'facebookstories',
Twitter = 'twitter',
Whatsapp = 'whatsapp',
Whatsappbusiness = 'whatsappbusiness',
Instagram = 'instagram',
InstagramStories = 'instagramstories',
Googleplus = 'googleplus',
Email = 'email',
Pinterest = 'pinterest',
Linkedin = 'linkedin',
Sms = 'sms',
Telegram = 'telegram',
Snapchat = 'snapchat',
Messenger = 'messenger',
Viber = 'viber',
Discord = 'discord',
Weibo = 'weibo',
Douyin = 'douyin',
Testshare = 'testshare'
}
// 分享选项接口
export interface ShareOptions {
social?: string;
message?: string;
title?: string;
url?: string;
urls?: string[];
type?: string;
subject?: string;
email?: string;
recipient?: string;
excludedActivityTypes?: string[];
failOnCancel?: boolean;
filename?: string;
filenames?: string[];
saveToFiles?: boolean;
}
export interface ShareSingleResult {
message: string;
success: boolean;
}
3.2.2 分享基类设计(ShareBaseInstance.ts)
这是整个适配最核心的设计决策。Android 原生端每个社交平台是独立的 Intent 构建逻辑,鸿蒙端对应的是 Want。我们抽取共性到基类:
ShareBaseInstance(基类)
├── bundleName: string // 目标应用包名
├── scheme: string // 应用支持的 URL Scheme
├── storeId: string // 应用商店 ID
├── shareUrl?: string // Web 分享链接
│
├── shareSingleWant() // 默认 Want 分享(子类可覆写)
├── openSingleWant() // 通用分享入口(open 调用)
├── shareWant() // 底层 Want 拉起
├── openAppStore() // 应用未安装时跳转应用商店
├── isInstalledPackage() // 检测应用是否安装
├── pastToPasteboard() // 复制到剪贴板
└── getPasteboard() // 读取剪贴板
核心实现 — shareWant():
public async shareWant(want: Want, context: common.UIAbilityContext): Promise<ShareSingleResult> {
try {
await context.startAbility(want);
return { success: true, message: 'success to open App ' + this.bundleName };
} catch (err) {
const error = err as BusinessError;
// 应用未安装,尝试跳转应用商店
if (error.code === 16000050 || error.code === 16000001) {
Logger.info(`${this.bundleName} is not installed, try to open app store`);
return this.openAppStore(context);
}
return { success: false, message: error.message };
}
}
核心实现 — 默认 shareSingleWant():
public async shareSingleWant(options: ShareSingleOptions, context: common.UIAbilityContext): Promise<ShareSingleResult> {
let want: Want = {
deviceId: '',
bundleName: this.bundleName,
uri: options.url,
action: 'ohos.want.action.sendData',
parameters: {
'ability.params.backToOtherMissionStack': true,
'ohos.extra.param.key.shareUrl': options.url,
'ohos.extra.param.key.contentTitle': options.title || '',
'ohos.extra.param.key.shareAbstract': options.subject || options.message || '',
}
};
const res = await this.shareWant(want, context);
return res;
}
设计要点:
shareSingleWant()是虚方法,子类可以覆写以构建特定平台的 Want- 基类提供默认实现,简单的分享平台(WhatsApp、Telegram 等)只需传参构造即可
openAppStore()在目标应用未安装时自动跳转华为应用市场
3.2.3 RNPackage 注册(RNSharePackage.ts)
按照 RNOH TurboModule 规范注册模块:
import type { TurboModule, TurboModuleContext } from '@rnoh/react-native-openharmony/ts';
import { RNPackage, TurboModulesFactory } from '@rnoh/react-native-openharmony/ts';
import { RNShareTurboModule } from './RNShareTurboModule';
class RNShareTurboModuleFactory extends TurboModulesFactory {
createTurboModule(name: string): TurboModule | null {
if (name === 'RNShare') {
return new RNShareTurboModule(this.ctx);
}
return null;
}
}
export class RNSharePackage extends RNPackage {
createTurboModulesFactory(ctx: TurboModuleContext): TurboModulesFactory {
return new RNShareTurboModuleFactory(ctx);
}
}
3.3 阶段三:21 个社交平台逐一实现
根据各平台的技术特征,分享实现可分为三种模式:
| 模式 | 说明 | 代表平台 |
|---|---|---|
| 仅传参构造 | 只需包名+Scheme,使用基类默认 Want | WhatsApp、Telegram、Discord |
| URL 协议分享 | 通过浏览器打开 Web 分享链接 | Facebook、Pinterest、LinkedIn |
| 自定义 Want | 覆写 shareSingleWant 构建特定 Want | InstagramStories、抖音、微博、SMS |
3.3.1 模式一:仅传参构造(最简单)
大多数即时通讯类应用只需声明包名和 Scheme,分享行为由基类默认实现完成:
// WhatsApp
export class WhatsAppShare extends ShareBaseInstance {
constructor() {
super("com.whatsapp", 'whatsapp://', '123')
}
}
// Telegram
export class TeleGramShare extends ShareBaseInstance {
constructor() {
super("org.telegram.messenge", 'tg://', '123')
}
}
// Discord
export class DiscordShare extends ShareBaseInstance {
constructor() {
super("com.discord", 'discord://', '1213')
}
}
这类平台约 12 个,代码量极少,充分体现了基类抽象的价值。
3.3.2 模式二:URL 协议分享
Facebook、Pinterest、LinkedIn 等平台在鸿蒙端没有原生 SDK,但提供了 Web 分享链接。通过 ohos.want.action.viewData 拉起浏览器:
// Facebook 分享
export class FacebookShare extends ShareBaseInstance {
constructor() {
super("com.facebook.katana", 'facebook://', '123',
'https://www.facebook.com/sharer/sharer.php')
}
public async shareSingleWant(options: ShareSingleOptions,
context: common.UIAbilityContext): Promise<ShareSingleResult> {
let want: Want = {
entities: ['entity.system.browsable'],
uri: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(options.url)}`,
action: 'ohos.want.action.viewData'
};
const res = await this.shareWant(want, context);
return res;
}
}
// Pinterest 分享
export class PinterestShare extends ShareBaseInstance {
constructor() {
super("com.pinterest", 'pinterest://', '123',
'https://www.pinterest.com/pin/create/button/')
}
public async shareSingleWant(options: ShareSingleOptions,
context: common.UIAbilityContext): Promise<ShareSingleResult> {
let want: Want = {
entities: ['entity.system.browsable'],
uri: `https://www.pinterest.com/pin/create/button/?url=${encodeURIComponent(options.url)}&title=${options.title}`,
action: 'ohos.want.action.viewData',
parameters: { 'ability.params.backToOtherMissionStack': true }
};
const res = await super.shareWant(want, context);
return res;
}
}
3.3.3 模式三:自定义 Want(最复杂)
Instagram Stories 分享 — 需要处理背景图、贴纸图、背景视频三种素材:
export class InstagramStoriesShare extends ShareBaseInstance {
constructor() {
super("com.instagram.android", 'instagram://story-camera', '123')
}
public async shareSingleWant(options: InstagramStoriesShareSingleOptions,
context: common.UIAbilityContext): Promise<ShareSingleResult> {
const fileUtil = FileUtils.getInstance();
let want: Want = {
deviceId: '',
bundleName: 'com.instagram.android',
abilityName: 'com.instagram.android.storyshare.ShareActivity',
action: 'ohos.want.action.sendData',
flags: wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION
| wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION,
parameters: {
'ability.params.backToOtherMissionStack': true,
'ohos.extra.param.key.stream': fileUtil.hasValidKey("backgroundVideo", options)
? options.backgroundVideo : options.backgroundImage,
},
type: fileUtil.hasValidKey("backgroundVideo", options) ? 'video/*' : 'image/*',
};
const res = await this.shareWant(want, context);
return res;
}
}
抖音分享 — 需要区分图片/视频类型,处理 Base64 和多图场景:
export class DouyinShare extends ShareBaseInstance {
constructor() {
super("com.ss.hm.ugc.aweme", 'snssdk1128://openplatform/share', '123')
}
public async shareSingleWant(options: ShareSingleOptions,
context: common.UIAbilityContext): Promise<ShareSingleResult> {
const type = options.type;
if (!type) {
return { success: false, message: 'type is not define you must define it image or video' };
}
let isImage = type?.startsWith('image');
const fileUtil = FileUtils.getInstance();
let urls: string[] = [];
if (fileUtil.hasValidKey('url', options) && !fileUtil.hasValidKey('urls', options)) {
urls.push(options.url);
}
if (fileUtil.hasValidKey('urls', options)) {
urls = urls.concat(options.urls);
}
// Base64 文件需要先写入本地
const urlsArr = urls.map(async (url) => {
let fileUrl = url;
if (fileUtil.isBase64File(fileUrl)) {
fileUrl = await fileUtil.writeBase64File(context, fileUrl);
}
return fileUrl;
});
let want: Want = {
bundleName: 'com.ss.hm.ugc.aweme',
abilityName: 'MainAbility',
uri: 'snssdk1128://openplatform/share',
flags: wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION
| wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION,
parameters: {
msg: options.subject,
title: options.title,
share_to_publish: 1,
image_list_path: isImage ? urlsArr : null,
video_path: isImage ? null : urlsArr[0]
}
};
const res = await this.shareWant(want, context);
return res;
}
}
微博分享 — 使用自定义媒体对象 WeiboMultiMessage:
export class WeiboShare extends ShareBaseInstance {
public static readonly COMMAND_TO_WEIBO = 1;
public static readonly WEIBO_SDK_FLAG = 0x11;
constructor() {
super("com.sina.weibo", 'sinaweibo://', '123')
}
public async shareSingleWant(options: ShareSingleOptions,
context: common.UIAbilityContext) {
const fileUtil = FileUtils.getInstance();
let message = new WeiboMultiMessage();
if (fileUtil.hasValidKey('message', options)) {
let textObject = new TextObject();
textObject.text = options.message;
message.textObject = textObject;
}
if (fileUtil.hasValidKey('urls', options)) {
let multiImageObject = new MultiImageObject();
multiImageObject.uriStrs = options.urls;
message.multiImageObject = multiImageObject;
}
let uriStrs = message.multiImageObject?.getImageUris() ?? [];
let want: Want = {
bundleName: 'com.sina.weibo',
abilityName: 'EditActivity',
action: 'ohos.want.action.sendData',
flags: wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION
| wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION,
parameters: {
'ability.params.backToOtherMissionStack': true,
_weibo_command_type: WeiboShare.COMMAND_TO_WEIBO,
_weibo_flag: WeiboShare.WEIBO_SDK_FLAG,
_weibo_transaction: systemDateTime.getTime(false) + "",
msg: message,
"ability.params.stream": uriStrs
}
};
const res = await this.shareWant(want, context);
return res;
}
}
SMS 短信分享 — 使用 MMS 应用的特定参数格式:
export class SMSShare extends ShareBaseInstance {
constructor() {
super("com.ohos.mms", '', '123')
}
public async shareSingleWant(options: ShareSingleOptions,
context: common.UIAbilityContext): Promise<ShareSingleResult> {
let contactInfo = [{
contactsName: 'ZhangSan',
telephone: options?.recipient
}];
let content = options?.message + '\n' + options?.url;
let want = {
bundleName: 'com.ohos.mms',
abilityName: 'com.ohos.mms.MainAbility',
parameters: {
contactObjects: JSON.stringify(contactInfo),
pageFlag: 'conversation',
content: content
}
};
const res = await super.shareWant(want, context);
return res;
}
}
3.3.4 GenericShare — 系统分享面板
open() 方法对应的通用分享,需要调用 HarmonyOS 的 SystemShare API 拉起系统分享选择面板:
export class GenericShare extends ShareBaseInstance {
constructor() {
super('', '', '123')
}
// open() 调用入口
public async shareSingleWant(options: ShareSingleOptions,
context: common.UIAbilityContext): Promise<ShareSingleResult> {
return this.openSingleWant(options, context);
}
// 拉起系统分享面板
private async openSystemShare(options: ShareOptions,
context: common.UIAbilityContext): Promise<ShareSingleResult> {
const fileUtil = FileUtils.getInstance();
const { urls, excludedAbilities } = await this.normalizeShareOptions(options, context);
try {
let data: systemShare.SharedData;
urls.forEach((url) => {
let utdType: utd.UniformDataType;
if (fileUtil.isHttpLink(url)) {
utdType = utd.UniformDataType.HYPERLINK;
} else {
let type = fileUtil.getMIMETypeFromExtension(url);
utdType = type as utd.UniformDataType;
}
let shareAppData = systemShare.createShareAppData({ uri: url, utd: utdType });
if (!data) {
data = systemShare.createSharedData(shareAppData);
} else {
data.addRecord(shareAppData);
}
});
let controller = new systemShare.ShareController(data);
controller.share(context,
{ excludedTargets: excludedAbilities },
(err, result) => { /* 回调处理 */ }
);
return { success: true, message: 'share success' };
} catch (err) {
// 降级方案:使用 Want 拉起选择面板
return this.openShareByWant(options, context);
}
}
}
降级策略:当 SystemShare API 不可用时,回退到 ohos.want.action.select 的 Want 方式拉起系统选择器。
3.4 阶段四:JS 层桥接与 TurboModule
3.4.1 TurboModule 实现(RNShareTurboModule.ts)
这是 JS 和原生之间的桥梁,负责路由分发:
export class RNShareTurboModule extends TurboModule implements TM.RNShare.Spec {
private context: common.UIAbilityContext;
constructor(ctx) {
super(ctx);
this.context = this.ctx.uiAbilityContext;
}
// 根据 social 参数路由到对应的分享实例
private getWantShareInstance(options: ShareOptions): ShareBaseInstance {
let share: Social = options.social as Social;
switch (share) {
case Social.Generic: return new GenericShare();
case Social.Facebook: return new FacebookShare();
case Social.FacebookStories: return new FacebookStoriesShare();
case Social.Twitter: return new TwitterShare();
case Social.Whatsapp: return new WhatsAppShare();
case Social.Whatsappbusiness: return new WhatsAppBusinessShare();
case Social.Instagram: return new InstagramShare();
case Social.InstagramStories: return new InstagramStoriesShare();
case Social.Email: return new EmailShare();
case Social.Sms: return new SMSShare();
case Social.Telegram: return new TeleGramShare();
case Social.Discord: return new DiscordShare();
case Social.Weibo: return new WeiboShare();
case Social.Douyin: return new DouyinShare();
// ... 共 21 个平台
default: return null;
}
}
// getConstants() - 导出社交平台常量
public getConstants(): Object {
return {
"FACEBOOK": "facebook",
"TWITTER": "twitter",
"WHATSAPP": "whatsapp",
"INSTAGRAM": "instagram",
"TELEGRAM": "telegram",
"EMAIL": "email",
"SMS": "sms",
"DISCORD": "discord",
// ...
};
}
// open() - 通用分享
public async open(options: ShareOptions): Promise<{ success: boolean; message: string }> {
let shareInstance = this.getWantShareInstance({ ...options, social: Social.Generic });
const res = await shareInstance.openSingleWant(options, this.context);
return res;
}
// shareSingle() - 定向分享
public async shareSingle(options: ShareSingleOptions): Promise<{ success: boolean; message: string }> {
let shareInstance = this.getWantShareInstance(options);
if (!shareInstance) {
return { success: false, message: 'not support share the package' };
}
let res = await shareInstance.shareSingleWant(options, this.context);
return res;
}
// isPackageInstalled() - 应用安装检测
public async isPackageInstalled(packageName: string): Promise<boolean> {
let shareInstance = this.getWantShareInstance({ social: packageName });
return await shareInstance.isInstalledPackage();
}
// isBase64File() - Base64 检测
public async isBase64File(url: string): Promise<boolean> {
return FileUtils.getInstance().isBase64File(url);
}
}
3.4.2 JS 层桥接修复(index.js)
原库的 JS 层需要对 harmony 平台做兼容。关键修改:
shareSingle — 增加平台判断:
// 修改前
if (Platform.OS !== 'android' && Platform.OS !== 'ios') {
throw new Error('Not implemented');
}
// 修改后
if (Platform.OS !== 'android' && Platform.OS !== 'ios' && Platform.OS !== 'harmony') {
throw new Error('Not implemented');
}
isPackageInstalled — 增加平台判断:
// 修改前
if (Platform.OS !== 'android') {
throw new Error('Not implemented');
}
// 修改后
if (Platform.OS !== 'android' && Platform.OS !== 'harmony') {
throw new Error('Not implemented');
}
3.4.3 Spec 定义(NativeRNShare.ts)
TurboModule 的 Spec 声明,定义了 JS 和原生之间的契约:
export interface Spec extends TurboModule {
readonly getConstants: () => {
FACEBOOK?: string;
TWITTER?: string;
WHATSAPP?: string;
INSTAGRAM?: string;
TELEGRAM?: string;
EMAIL?: string;
SMS?: string;
DISCORD?: string;
// ...
};
open: (options: Object) => Promise<{ success: boolean; message: string }>;
shareSingle: (options: Object) => Promise<{ success: boolean; message: string }>;
isPackageInstalled: (packagename: string) => Promise<boolean>;
isBase64File: (url: string) => Promise<boolean>;
}
export default TurboModuleRegistry.get<Spec>('RNShare') as Spec | null;
四、完整代码对照
4.1 目录结构对照
| Android 原生 | HarmonyOS 原生 | 说明 |
|---|---|---|
ShareModule.java |
RNShareTurboModule.ts |
TurboModule 主类 |
SharePackage.java |
RNSharePackage.ts |
RN 包注册 |
| — | Types.ts |
类型定义(鸿蒙新增) |
| 各平台 Intent 构建 | share/*.ts (21个文件) |
分享实现 |
| — | share/ShareBaseInstance.ts |
基类抽象(鸿蒙新增) |
| — | utils/FileUtils.ts |
文件处理(鸿蒙新增) |
| — | share/shareMediaObject/ |
媒体对象模型 |
4.2 核心机制对照
| 能力 | Android | HarmonyOS |
|---|---|---|
| 拉起应用 | Intent + startActivity() |
Want + context.startAbility() |
| 系统分享 | Intent.ACTION_CHOOSER |
systemShare.ShareController |
| 应用检测 | PackageManager.getInstalledPackages() |
bundleManager.getBundleInfoForSelf() |
| 文件传递 | Uri + FileProvider |
fileUri.getUriFromPath() + URI 权限 |
| 剪贴板 | ClipboardManager |
pasteboard.getSystemPasteboard() |
| Base64 处理 | Base64.decode() |
buffer.from(data, 'base64') |
| 应用商店 | Intent(ACTION_VIEW, market://) |
Want(ACTION_VIEW, store://appgallery) |
4.3 文件统计
| 类别 | 文件数 | 代码行数(约) |
|---|---|---|
| 核心模块 | 4 | 420 |
| 分享实现 | 21 | 1,200 |
| 媒体对象 | 8 | 280 |
| 工具类 | 3 | 800 |
| JS/TS 层 | 4 | 210 |
| 配置文件 | 6 | 80 |
| 合计 | 46 | ~3,000 |
五、关键决策说明
决策一:基类抽象 vs 独立实现
选择:抽取 ShareBaseInstance 基类,所有平台继承它。
理由:
- 21 个平台中约 12 个仅需
bundleName + scheme构造,代码重复率极高 - 基类封装了
shareWant()、isInstalledPackage()、openAppStore()、pastToPasteboard()四个通用方法 - 子类只需覆写
shareSingleWant()即可实现自定义 Want 构建 - 新增平台只需 3 行代码(一个构造函数)
决策二:Want vs SystemShare API
选择:定向分享用 Want,通用分享优先用 SystemShare API 并降级到 Want。
理由:
Want可以直接指定bundleName拉起目标应用,适合shareSingle场景SystemShareAPI 提供了更丰富的系统分享面板交互,适合open场景- 但
SystemShare在部分低版本系统上不可用,必须有Want降级方案
决策三:URL 协议降级方案
选择:对于 Facebook、Pinterest、LinkedIn 等没有鸿蒙原生 SDK 的平台,使用 Web 分享链接通过浏览器打开。
理由:
- 鸿蒙生态尚在发展中,多数社交平台没有鸿蒙原生 SDK
- Web 分享链接是跨平台的通用方案,虽然体验不如原生,但功能可用
- 通过
ohos.want.action.viewData+entity.system.browsable即可拉起浏览器
决策四:FileUtils 单例设计
选择:FileUtils 使用单例模式。
理由:
- MIME Type 映射表只需一份实例
- 文件缓存路径全局唯一
- Base64 编解码无状态依赖,无需多实例
六、工具类详解
6.1 FileUtils — 文件处理瑞士军刀
FileUtils 是适配过程中最复杂的工具类(约 735 行),承载了以下能力:
| 方法 | 功能 |
|---|---|
isBase64File() |
判断 URL 是否为 Base64 编码 |
isHttpLink() |
判断是否为 HTTP 链接 |
isLocalFile() |
判断是否为本地文件路径 |
getMIMETypeFromExtension() |
根据文件后缀获取 MIME 类型 |
writeBase64File() |
将 Base64 数据写入本地文件并返回 URI |
writeUriFromBase64URL() |
将 Base64 解码后写入指定 URI |
writeUriFromHttpUrl() |
下载网络文件写入本地 URI |
readFilePic() / readFilePicAsync() |
同步/异步读取文件 |
copyUriFromUrl() |
复制文件 |
saveMultiUrlsDataToDocument() |
保存多个 URL 到文档目录 |
getCacheFile() |
获取缓存目录路径 |
Base64 文件处理流程:
JS 层传入 base64 URL (data:image/png;base64,...)
↓
FileUtils.isBase64File() 检测
↓
FileUtils.writeBase64File() → 写入缓存目录
↓
fileUri.getUriFromPath() → 返回 file:// URI
↓
Want.uri = file:// URI → 传给目标应用
6.2 Logger — 日志工具
轻量级日志封装,统一 TAG 为 RNShare:
export default class Logger {
private static domain: number = 0x0001;
private static tag: string = 'RNShare';
static info(msg: string) {
hilog.info(Logger.domain, Logger.tag, msg);
}
static error(msg: string) {
hilog.error(Logger.domain, Logger.tag, msg);
}
}
6.3 ShareMediaObject — 分享媒体对象
为微博等平台设计的结构化分享数据模型:
MediaObject (基类)
├── TextObject — 文本内容
├── MultiImageObject — 多图分享
├── VideoSourceObject — 视频源
├── WebPageObject — 网页分享
├── SuperGroupObject — 组合分享
└── WeiboMultiMessage — 微博多类型消息
├── textObject
├── multiImageObject
├── videoSourceObject
└── webPageObject
七、踩坑与解决
坑1:Logger 路径不一致
现象:ShareBaseInstance.ts 中 import Logger from '../utils/Logger',但 Logger.ts 实际位于 ets/Logger.ts。
解决:在 ets/utils/ 下创建 Logger.ts 重导出:
export { default } from '../Logger';
坑2:GenericShare 双斜杠路径
现象:import { FileUtils } from '../utils//FileUtils',双斜杠在某些构建环境下报错。
解决:修正为 import { FileUtils } from '../utils/FileUtils'。
坑3:类名大小写不一致
现象:FacebookShare.ts 中导出 FaceBookShare(B 大写),但 Share.ts 和 RNShareTurboModule.ts 中引用的名称不统一。
解决:统一改为 FacebookShare,保持 camelCase 命名规范。
坑4:isPackageInstalled 不支持 harmony
现象:JS 层 isPackageInstalled 只允许 Android 平台调用,harmony 下直接抛出 Not implemented。
解决:修改 index.js 中的平台判断,添加 Platform.OS !== 'harmony' 条件。
坑5:WeiboShare 缺少 FileUtils 导入
现象:WeiboShare.ts 中使用了 FileUtils.getInstance() 但文件头部没有 import。
解决:添加 import { FileUtils } from '../utils/FileUtils'。
八、适配完整清单
| 序号 | 文件 | 说明 |
|---|---|---|
| 1 | Types.ts |
Social 枚举 + 接口定义 |
| 2 | Logger.ts |
日志工具 |
| 3 | utils/Logger.ts |
Logger 重导出 |
| 4 | utils/FileUtils.ts |
文件处理工具(735 行) |
| 5 | share/ShareBaseInstance.ts |
分享基类 |
| 6 | share/GenericShare.ts |
系统分享(231 行) |
| 7 | share/FacebookShare.ts |
|
| 8 | share/FacebookStoriesShare.ts |
Facebook Stories |
| 9 | share/WhatsAppShare.ts |
|
| 10 | share/WhatsAppBusinessShare.ts |
WhatsApp Business |
| 11 | share/InstagramShare.ts |
|
| 12 | share/InstagramStoriesShare.ts |
Instagram Stories |
| 13 | share/TwitterShare.ts |
|
| 14 | share/EmailShare.ts |
邮件 |
| 15 | share/SMSShare.ts |
短信 |
| 16 | share/TelegramShare.ts |
Telegram |
| 17 | share/SnapChatShare.ts |
Snapchat |
| 18 | share/MessengerShare.ts |
Messenger |
| 19 | share/ViberShare.ts |
Viber |
| 20 | share/PinterestShare.ts |
|
| 21 | share/LinkedinShare.ts |
|
| 22 | share/GooglePlusShare.ts |
Google+ |
| 23 | share/DiscordShare.ts |
Discord |
| 24 | share/DouyinShare.ts |
抖音 |
| 25 | share/WeiboShare.ts |
微博 |
| 26 | share/TestShare.ts |
测试分享 |
| 27 | share/Share.ts |
统一导出索引 |
| 28 | share/shareMediaObject/MediaObject.ts |
媒体对象基类 |
| 29 | share/shareMediaObject/TextObject.ts |
文本对象 |
| 30 | share/shareMediaObject/MultiImageObject.ts |
多图对象 |
| 31 | share/shareMediaObject/VideoSourceObject.ts |
视频源对象 |
| 32 | share/shareMediaObject/WebPageObject.ts |
网页对象 |
| 33 | share/shareMediaObject/SuperGroupObject.ts |
组合对象 |
| 34 | share/shareMediaObject/WeiboMultiMessage.ts |
微博消息对象 |
| 35 | share/shareMediaObject/ShareMediaObject.ts |
媒体对象导出 |
| 36 | RNShareTurboModule.ts |
TurboModule 主类 |
| 37 | RNSharePackage.ts |
RN 包注册 |
| 38 | src/NativeRNShare.ts |
Spec 定义 |
| 39 | index.js |
JS 层桥接 |
| 40 | index.d.ts |
TypeScript 类型声明 |
九、总结
本次适配的核心流程可以总结为:
1. 建目录结构 → 参照已适配项目(如 react-native-tts)
2. 设计类型系统 → Social 枚举 + ShareOptions 接口
3. 抽象基类 → ShareBaseInstance 封装 Want/应用检测/应用商店/剪贴板
4. 逐平台实现 → 三种模式(仅构造 / URL协议 / 自定义Want)
5. TurboModule 桥接 → switch-case 路由 + getConstants()
6. JS 层兼容 → Platform.OS 增加 'harmony' 判断
7. 工具类支撑 → FileUtils(Base64/HTTP/缓存)+ Logger
关键经验:
- 基类抽象是降低重复代码的关键,21 个平台中 12 个仅靠构造函数即完成
Want是鸿蒙端对应 AndroidIntent的核心机制,理解其参数结构是适配的基础SystemShareAPI 提供更好的分享面板体验,但必须有Want降级方案- Base64 文件需要先写入本地缓存再通过 URI 传递,不能直接使用 data URL
- JS 层的平台判断需要逐一排查,避免 harmony 平台被误拦截
参考文档
更多推荐
所有评论(0)