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 场景
  • SystemShare API 提供了更丰富的系统分享面板交互,适合 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.tsimport 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.tsRNShareTurboModule.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 Facebook
8 share/FacebookStoriesShare.ts Facebook Stories
9 share/WhatsAppShare.ts WhatsApp
10 share/WhatsAppBusinessShare.ts WhatsApp Business
11 share/InstagramShare.ts Instagram
12 share/InstagramStoriesShare.ts Instagram Stories
13 share/TwitterShare.ts Twitter
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 Pinterest
21 share/LinkedinShare.ts LinkedIn
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 是鸿蒙端对应 Android Intent 的核心机制,理解其参数结构是适配的基础
  • SystemShare API 提供更好的分享面板体验,但必须有 Want 降级方案
  • Base64 文件需要先写入本地缓存再通过 URI 传递,不能直接使用 data URL
  • JS 层的平台判断需要逐一排查,避免 harmony 平台被误拦截

参考文档

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐