查看: 352|回复: 0

鸿蒙 RN Linking 适配与 Deep Link 排障

[复制链接]
发表于 4 小时前 | 显示全部楼层 |阅读模式
Linking 是 React Native 中用于打开外部链接、拨号、发邮件、跳转地图、唤起 Deep Link 以及打开系统设置的模块。它的方法名和参数在 iOS、Android、鸿蒙上大体一致,但鸿蒙端在 canOpenURL 判断、sendIntent 支持程度、Deep Link 配置和 getInitialURL 返回时机上有自己的细节。本文结合 React Native 0.84 与 RNOH 0.84.1、HarmonyOS 6.0 的实践,梳理一套可落地的适配和排障思路。

一、openURL 与 canOpenURL
openURL 是最常用的入口,可以打开网页、tel 拨号、mailto 邮件等。它返回 Promise:成功 resolve,失败 reject,例如设备上没有能处理该 scheme 的应用。
  1. import { Linking } from 'react-native';
  2. await Linking.openURL('https://reactnative.cn');
  3. await Linking.openURL('tel:+861234567890');
  4. await Linking.openURL('mailto:support@example.com');
复制代码

不要裸调 openURL。更稳妥的做法是先 canOpenURL 判断,再决定是否打开;否则设备不支持时既可能进入 catch,也会让用户体验变差。
  1. const handlePress = async () => {
  2.   const supported = await Linking.canOpenURL(url);
  3.   if (supported) {
  4.     await Linking.openURL(url);
  5.   } else {
  6.     Alert.alert('提示', '当前设备不支持此操作');
  7.   }
  8. };
复制代码

canOpenURL 在 iOS 9+ 有查询次数限制:同一 scheme 最多查询 50 次,超过后返回 false。RNOH 在某些版本复用了 iOS 的 Linking 实现,因此鸿蒙也可能受这个限制影响。鸿蒙上对 http、https、tel、mailto 等常见 scheme 通常返回 true;自定义 scheme 则取决于系统里是否有注册的应用处理。geo 这类 scheme 还要看是否安装了关联地图应用,没有地图应用时 canOpenURL 会返回 false。

二、Deep Link 的启动与前台监听
Deep Link 分两种场景:App 未启动时通过 Deep Link 拉起,用 getInitialURL 获取链接;App 已在前台时收到链接,用 addEventListener 监听 url 事件。两者要搭配使用,才能覆盖完整链路。
  1. useEffect(() => {
  2.   Linking.getInitialURL().then((url) => {
  3.     if (url) {
  4.       handleDeepLink(url);
  5.     } else {
  6.       goToHomePage();
  7.     }
  8.   });
  9. }, []);
  10. useEffect(() => {
  11.   const sub = Linking.addEventListener('url', ({ url }) => {
  12.     handleDeepLink(url);
  13.   });
  14.   return () => sub.remove();
  15. }, []);
复制代码

调试时要注意:debug 模式下 getInitialURL 可能一直返回 null,需要关掉 debugger 后在真机上测试。鸿蒙上 getInitialURL 的返回时机也可能比预期晚,如果在组件初始化时就调用,可能暂时拿不到 URL。可以用重试机制兜底:
  1. const getInitialURLWithRetry = async (retries = 3): Promise<string | null> => {
  2.   for (let i = 0; i < retries; i++) {
  3.     const url = await Linking.getInitialURL();
  4.     if (url) return url;
  5.     await new Promise((resolve) => setTimeout(resolve, 500));
  6.   }
  7.   return null;
  8. };
复制代码

三、sendIntent 与鸿蒙原生配置
sendIntent 只支持 Android 和鸿蒙,用来发送 Android Intent。鸿蒙上的支持程度取决于 RNOH 实现,在 0.84 版本上部分 intent action 可用,但不是全部。
  1. import { Platform, Linking } from 'react-native';
  2. if (Platform.OS === 'android' || Platform.OS === 'harmony') {
  3.   await Linking.sendIntent('android.intent.action.VIEW', [
  4.     { key: 'query', value: 'hello' },
  5.   ]);
  6. }
复制代码

如果某个 action 在鸿蒙上不可用,需要准备 fallback。Deep Link 的原生注册同样不能忽略:Android 在 AndroidManifest.xml 中通过 intent filter 声明 scheme;iOS 在 Info.plist 的 LSApplicationQueriesSchemes 中配置;鸿蒙在 module.json5 的 abilities/skills 中配置 actions 和 entities,类似 Android 的 intent filter。自定义 scheme 还需要在资源文件中声明,前台收到链接则通过 Ability 的 onNewWant 回调处理,链接参数通过 Want 对象的 uri 字段传递。不同 RNOH 版本的写法有差异,应以对应版本文档为准。

四、鸿蒙上容易踩的坑
第一,canOpenURL 判断不准。遇到过 http 链接 canOpenURL 返回 true,但 openURL 仍失败的情况。因此即使判断通过,也要给 openURL 加 try-catch。
  1. const safeOpenURL = async (url: string) => {
  2.   const supported = await Linking.canOpenURL(url);
  3.   if (supported) {
  4.     try {
  5.       await Linking.openURL(url);
  6.     } catch (e) {
  7.       console.log('canOpenURL 返回 true 但 openURL 失败:', url);
  8.     }
  9.   }
  10. };
复制代码

第二,sendIntent 兼容性有限,某些 Android 可用的 action 在鸿蒙上不可用,要在真机上验证,并保留降级路径。第三,getInitialURL 返回时机可能偏晚,建议加轮询重试。第四,鸿蒙模拟器可能无法模拟 Deep Link,测试要回到真机,可通过浏览器输入自定义 scheme 链接或通过短信发送链接来触发;这一点与 Android 模拟器不同,Android 模拟器可以用 adb 命令模拟 Deep Link。

另外,openURL 打开第三方应用后,如果用户返回时第三方应用 crashed,Linking 的 Promise 可能既不 resolve 也不 reject,一直 pending。业务侧最好加超时处理,避免 UI 一直等待。

五、统一封装与工程建议
业务代码里不建议到处裸调 Linking,可以统一封装成一个安全工具,让调用方只关心返回结果。例如:
  1. const safeLinking = {
  2.   openURL: async (url: string): Promise<boolean> => {
  3.     try {
  4.       const canOpen = await Linking.canOpenURL(url);
  5.       if (!canOpen) {
  6.         console.warn('无法打开 URL:', url);
  7.         return false;
  8.       }
  9.       await Linking.openURL(url);
  10.       return true;
  11.     } catch (error) {
  12.       console.error('打开 URL 失败:', url, error);
  13.       return false;
  14.     }
  15.   },
  16.   getInitialURL: async (): Promise<string | null> => {
  17.     try {
  18.       return await Linking.getInitialURL();
  19.     } catch (error) {
  20.       console.error('获取初始 URL 失败', error);
  21.       return null;
  22.     }
  23.   },
  24. };
复制代码

Deep Link 解析和路由也应集中管理,不要让 URL 解析逻辑散落在多个页面。渠道归因场景尤其要注意:从 utm_campaign、utm_source、utm_medium、utm_content 等参数拿到来源后,要尽早上报并保存到全局状态或持久化存储中。如果 App 启动后继续发生页面跳转,后取的参数可能已经丢失。

如果 AndroidManifest 中把 launchMode 设为 singleTask,Deep Link 启动时不会重新创建 Activity,而是复用已有实例,并通过 addEventListener 传递 URL;standard 模式每次都会创建新的 Activity。原文建议 Deep Link 相关页面统一使用 singleTask,避免重复创建。

六、结论
Linking 的 API 本身不复杂,但跨平台适配的关键在细节。打开 URL 前先 canOpenURL;Deep Link 一定在真机测;getInitialURL 和 addEventListener 搭配覆盖启动与前台;sendIntent 在鸿蒙上实测,不要只依赖文档;统一封装异常处理和 URL 解析,能显著减少业务层重复代码。本文基于 React Native 0.84 + RNOH 0.84.1、HarmonyOS 6.0,不同版本之间可能存在差异,以实际测试结果为准。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-15 16:27 , Processed in 0.018743 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部