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 的应用。
- import { Linking } from 'react-native';
- await Linking.openURL('https://reactnative.cn');
- await Linking.openURL('tel:+861234567890');
- await Linking.openURL('mailto:support@example.com');
复制代码
不要裸调 openURL。更稳妥的做法是先 canOpenURL 判断,再决定是否打开;否则设备不支持时既可能进入 catch,也会让用户体验变差。
- const handlePress = async () => {
- const supported = await Linking.canOpenURL(url);
- if (supported) {
- await Linking.openURL(url);
- } else {
- Alert.alert('提示', '当前设备不支持此操作');
- }
- };
复制代码
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 事件。两者要搭配使用,才能覆盖完整链路。
- useEffect(() => {
- Linking.getInitialURL().then((url) => {
- if (url) {
- handleDeepLink(url);
- } else {
- goToHomePage();
- }
- });
- }, []);
- useEffect(() => {
- const sub = Linking.addEventListener('url', ({ url }) => {
- handleDeepLink(url);
- });
- return () => sub.remove();
- }, []);
复制代码
调试时要注意:debug 模式下 getInitialURL 可能一直返回 null,需要关掉 debugger 后在真机上测试。鸿蒙上 getInitialURL 的返回时机也可能比预期晚,如果在组件初始化时就调用,可能暂时拿不到 URL。可以用重试机制兜底:
- const getInitialURLWithRetry = async (retries = 3): Promise<string | null> => {
- for (let i = 0; i < retries; i++) {
- const url = await Linking.getInitialURL();
- if (url) return url;
- await new Promise((resolve) => setTimeout(resolve, 500));
- }
- return null;
- };
复制代码
三、sendIntent 与鸿蒙原生配置
sendIntent 只支持 Android 和鸿蒙,用来发送 Android Intent。鸿蒙上的支持程度取决于 RNOH 实现,在 0.84 版本上部分 intent action 可用,但不是全部。
- import { Platform, Linking } from 'react-native';
- if (Platform.OS === 'android' || Platform.OS === 'harmony') {
- await Linking.sendIntent('android.intent.action.VIEW', [
- { key: 'query', value: 'hello' },
- ]);
- }
复制代码
如果某个 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。
- const safeOpenURL = async (url: string) => {
- const supported = await Linking.canOpenURL(url);
- if (supported) {
- try {
- await Linking.openURL(url);
- } catch (e) {
- console.log('canOpenURL 返回 true 但 openURL 失败:', url);
- }
- }
- };
复制代码
第二,sendIntent 兼容性有限,某些 Android 可用的 action 在鸿蒙上不可用,要在真机上验证,并保留降级路径。第三,getInitialURL 返回时机可能偏晚,建议加轮询重试。第四,鸿蒙模拟器可能无法模拟 Deep Link,测试要回到真机,可通过浏览器输入自定义 scheme 链接或通过短信发送链接来触发;这一点与 Android 模拟器不同,Android 模拟器可以用 adb 命令模拟 Deep Link。
另外,openURL 打开第三方应用后,如果用户返回时第三方应用 crashed,Linking 的 Promise 可能既不 resolve 也不 reject,一直 pending。业务侧最好加超时处理,避免 UI 一直等待。
五、统一封装与工程建议
业务代码里不建议到处裸调 Linking,可以统一封装成一个安全工具,让调用方只关心返回结果。例如:
- const safeLinking = {
- openURL: async (url: string): Promise<boolean> => {
- try {
- const canOpen = await Linking.canOpenURL(url);
- if (!canOpen) {
- console.warn('无法打开 URL:', url);
- return false;
- }
- await Linking.openURL(url);
- return true;
- } catch (error) {
- console.error('打开 URL 失败:', url, error);
- return false;
- }
- },
- getInitialURL: async (): Promise<string | null> => {
- try {
- return await Linking.getInitialURL();
- } catch (error) {
- console.error('获取初始 URL 失败', error);
- return null;
- }
- },
- };
复制代码
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,不同版本之间可能存在差异,以实际测试结果为准。 |