Apple Handoff

编辑页面

了解如何使用 Expo Router 和 Apple Handoff 在 Apple 设备之间无缝继续应用导航。

iOS (device only)
Web

For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.

Apple Handoff 是一项功能,它使用户能够在另一台设备上继续浏览您的应用或网站。Expo Router 为此功能自动处理所有运行时路由。不过,一次性配置必须手动设置。

在 Expo Router 中,底层的 iOS API(NSUserActivity)需要一个 webpageUrl,操作系统建议将其作为切换到您的应用时的当前 URL。expo-router/head 组件有一个可选的原生模块,可以自动将 webpageUrl 设置为 Expo Router 中当前聚焦的路由。

设置

以下限制和注意事项很重要:

  • Handoff 仅适用于 Apple。
  • Handoff 不能在 Expo Go 应用中使用,因为它需要构建时配置。
  • Handoff 需要配置 universal links,至少在 iOS 上,并且包含 activitycontinuation 对象。
  • Handoff 需要在您希望支持的每个页面上使用 expo-router/head 组件;如果您希望所有页面都可连续接续,则可以在根布局中使用。

为确保 public/.well-known/apple-app-site-association 文件配置正确,它必须包含 activitycontinuation 键,并带有一个 apps 数组,其中包含您的应用的 bundle ID 和 Team ID,格式为 <APPLE_TEAM_ID>.<IOS_BUNDLE_ID>。例如,QQ57RJ5UTD.app.expo.acme,其中 QQ57RJ5UTD 是 Team ID,app.expo.acme 是 bundle 标识符。

public/.well-known/apple-app-site-association
{ "applinks": { "details": [ { "appIDs": ["<APPLE_TEAM_ID>.<IOS_BUNDLE_ID>"], "components": [ { "/": "*", "comment": "匹配所有路由" } ] } ] }, "activitycontinuation": { "apps": ["<APPLE_TEAM_ID>.<IOS_BUNDLE_ID>"] }, "webcredentials": { "apps": ["<APPLE_TEAM_ID>.<IOS_BUNDLE_ID>"] } }

webcredentials 对象是可选的,但建议添加。

您可以使用以下命令,根据您的 app config 生成 apple-app-site-association 文件:

Terminal
npx setup-safari

请参阅 测试深度链接 指南,以在开发环境中测试 handoff。

Expo Head 设置

请确保在 app.config.tsx 文件中使用 expo-router 配置插件设置 Handoff origin。此 URL 将在用户切换到您的应用时用作 webpageUrl

app.config.tsx
// 请务必将其更改为对您的项目唯一的值。 process.env.EXPO_TUNNEL_SUBDOMAIN = 'bacon-router-sandbox'; const ngrokUrl = `${process.env.EXPO_TUNNEL_SUBDOMAIN}.ngrok.io`; /** @type {import('expo/config').ExpoConfig} */ module.exports = { // ... ios: { associatedDomains: [ `applinks:${ngrokUrl}`, `activitycontinuation:${ngrokUrl}`, `webcredentials:${ngrokUrl}`, // 在此处添加额外的生产 URL。 // `applinks:example.com`, // `activitycontinuation:example.com`, // `webcredentials:example.com`, ], }, plugins: [ [ 'expo-router', { // 注意:在 "headOrigin" 中,URL 必须以 "https://" 开头 headOrigin: process.env.NODE_ENV === 'development' ? `https://${ngrokUrl}` : 'https://my-website-example.com', }, ], ], };

在测试 handoff 到原生端时,不要使用仅限开发环境的 ?mode=developer 后缀。

配置完应用配置后,请使用以下命令重新生成您的原生项目:

Terminal
npx expo prebuild -p ios

在开发过程中,您必须在将应用安装到设备之前先启动网站。这是因为当您安装应用时,操作系统会触发 Apple 的服务器去 ping 您的网站,以获取 .well-known/apple-app-site-association 文件。如果网站未运行,操作系统将无法找到该文件,handoff 也将无法工作。如果发生这种情况,请使用 npx expo run:ios -d 重新构建原生应用。

用法

在任何您想支持 handoff 的路由中,使用来自 expo-router/headHead 组件:

src/app/index.tsx
import Head from 'expo-router/head'; import { Text } from 'react-native'; export default function App() { return ( <> <Head> <meta property="expo:handoff" content="true" /> </Head> <Text>你好,世界</Text> </> ); }

Meta 标签

expo-router/head 组件支持以下 meta 标签:

Meta 标签描述
expo:handoff设置为 true 以为当前路由启用 handoff。默认值为 false。(仅 iOS)
og:title<title>NSUserActivity 设置标题;在 handoff 中不使用此项。
og:descriptionNSUserActivity 设置描述;在 handoff 中不使用此项。
og:url设置用户切换到您的应用时应打开的 URL。默认使用应用内当前 URL,并以 expo-router 配置插件中的 headOrigin 属性作为 baseURL。传入相对路径时,会将 headOrigin 追加到该路径前。

如果您想在不同平台之间切换这些值,可以使用 Platform.select

src/app/index.tsx
import Head from 'expo-router/head'; export default function App() { return ( <Head> <meta property="og:url" content={Platform.select({ web: 'https://expo.dev', default: null })} /> </Head> ); }

调试

确保你的 Apple 设备已启用接力。你可以按照下面的步骤进行测试,但将你的应用替换为 Safari。

  1. 在你的设备上打开原生应用。
  2. 导航到应用中支持接力的路由,该路由正在渲染 Expo Router 的 <Head /> 元素。
  3. 切换到你的 Mac,并点击 Dock 中该应用的接力图标。
  4. 切换到你的 iPhone 或 iPad,打开 App 切换器,然后点击屏幕底部的应用横幅。

如果你在 iPhone 的 App 切换器中只看到了 Safari 图标,那么说明接力未正常工作。

故障排查

你可以使用验证器(例如 AASA Validator)来测试 Apple App Site Association 文件(public/.well-known/apple-app-site-association)。

如果你遇到问题,最好的做法是在应用中启用最激进的接力设置。这可以确保任何可能的路由都可链接。你可以通过确保 public/.well-known/apple-app-site-association 文件匹配所有路由来实现:

public/.well-known/apple-app-site-association
{ "applinks": { "details": [ { "appIDs": ["<APPLE_TEAM_ID>.<IOS_BUNDLE_ID>"], "components": [ { "/": "*", "comment": "匹配所有路由" } ] } ] } }

在应用中,确保你没有条件性地渲染 <Head /> 元素(例如在 if/else 块中),它必须在你想要支持接力的每个页面上都被渲染。我们建议将其添加到 根布局 组件中,以确保在调试时每条路由都可链接。

在设备上安装应用之前,确保你可以访问 Ngrok URL(例如通过浏览器访问)。如果你无法访问该 URL,操作系统将无法找到文件,接力也将无法工作。

当配置了关联域时,npx expo run:ios 和 Xcode 都会对你的应用进行代码签名,这是接力和通用链接正常工作的必要条件。

Expo Go 应用不支持 Mac 与 iPhone/iPad 之间的接力。你必须在设备上构建并安装你的应用。

如果你在 iPhone 的 App 切换器中看到了 Safari 图标,那么这意味着接力未正常工作。

  • 测试到原生端的接力时,确保你没有使用 ?mode=developer 后缀。
  • 另外,确保你没有使用本地开发服务器 URL。例如,http://localhost:8081 不能作为有效的 app site association 链接使用;请在浏览器中打开正在运行的 Ngrok URL 进行测试。
  • 确保你的 public/.well-known/apple-app-site-association 文件包含 activitycontinuation 字段。
  • 我们观察到,在 iOS 16.3.1 和 macOS 13.0(Ventura)中,以 app.io. 开头的 bundle identifier 有时不会触发原生应用在 iOS 任务切换器中显示。请使用 com. 作为 bundle identifier 的第一部分。

你的 public/.well-known/apple-app-site-association 必须通过安全 URL(HTTPS)提供。如果你使用开发隧道,你必须使用 EXPO_TUNNEL_SUBDOMAIN 环境变量来配置开发隧道的子域名。开发环境下测试需要使用隧道,因为你需要 SSL 才能使用通用链接;Expo CLI 通过运行 npx expo start --tunnel 提供了内置支持。

检查你的 ios/project/project.entitlements 文件中 com.apple.developer.associated-domains 键下的内容。这应当包含与你的网站服务器/网站相同的域名。URL 不能包含协议(https://)或额外的路径名、查询参数或片段。

仍然卡住

这是一个很重要但也非常难配置的功能。Expo Router 自动化了许多相关环节,Expo CLI 自动化了大部分配置和托管。不过,硬件设置仍然可能被配置错误。

如果一切都失败了,你可以尝试按照 Apple 文档 中的步骤来调试该问题。请注意:

  • “将用户活动表示为 NSUserActivity 的实例。” 由 Expo Head 原生模块执行。
  • “当用户在应用中执行操作时更新活动实例。” 由挂载/渲染内部包含元标签 <meta property="expo:handoff" content="true" /><Head /> 组件来执行。
  • “在你的应用中的其他设备上接收来自接力的活动。” 由 Expo Head 原生模块中的 App Delegate 订阅者 执行。它用于在你接力到原生应用时将你重定向到正确的路由。

已知问题

从网页到原生的接力不支持客户端路由。这意味着 App 切换器中显示的 URL 将是你点击链接或重新加载页面时所在页面的 URL。这是 Web 平台的限制,Expo Router 无法修复。