了解如何使用 Expo Router 和 Apple Handoff 在 Apple 设备之间无缝地继续应用导航。
Apple Handoff 是一项功能,使用户能够在另一台设备上继续浏览你的应用或网站。Expo Router 可自动执行此功能的所有运行时路由。但是,必须手动设置一次性配置。
¥Apple Handoff is a feature that enables users to continue browsing your app or website on another device. Expo Router automates all of the runtime routing for this feature. However, the one-time configuration must be set up manually.
在 Expo Router 中,底层 iOS API (NSUserActivity
) 需要 webpageUrl
,操作系统建议将其作为切换到应用的当前 URL。expo-router/head
组件有一个可选的原生模块,可以自动将 webpageUrl
设置为 Expo Router 中当前聚焦的路由。
¥In Expo Router, the underlying iOS API (NSUserActivity
) requires a webpageUrl
which the OS recommends as the current URL for switching to your app. The expo-router/head
component has an optional native module that can automatically set the webpageUrl
to the currently focused route in Expo Router.
Android Device | Android Emulator | iOS Device | iOS Simulator | Web |
---|---|---|---|---|
¥Setup
以下限制和注意事项很重要:
¥The following restrictions and considerations are important:
Handoff 仅限 Apple。
¥Handoff is Apple-only.
Handoff 不能在 Expo Go 应用中使用,因为它需要构建时配置。
¥Handoff can not be used in the Expo Go app as it requires build-time configuration.
切换需要配置 通用链接(至少在 iOS 上),并包含 activitycontinuation
对象。
¥Handoff requires universal links to be configured, at least on iOS, and contain the activitycontinuation
object.
切换要求在你想要支持的每个页面上使用 expo-router/head
组件,或者如果你希望所有页面都是连续的,则在根布局中使用 expo-router/head
组件。
¥Handoff requires the expo-router/head
component to be used on each page that you want to support, or in the root layout if you want all pages to be continuous.
为了确保 public/.well-known/apple-app-site-association 文件配置正确,它必须包含 activitycontinuation
密钥和 apps
数组,该数组包含应用的打包包 ID 和格式为 <APPLE_TEAM_ID>.<IOS_BUNDLE_ID>
的团队 ID。例如,QQ57RJ5UTD.app.expo.acme
,其中 QQ57RJ5UTD
是团队 ID,app.expo.acme
是打包包标识符。
¥To ensure that the public/.well-known/apple-app-site-association file is configured correctly, it must include the activitycontinuation
key with an apps
array that contains your app's bundle ID and Team ID formatted as <APPLE_TEAM_ID>.<IOS_BUNDLE_ID>
. For example, QQ57RJ5UTD.app.expo.acme
where QQ57RJ5UTD
is the Team ID and app.expo.acme
is the bundle identifier.
{
"applinks": {
"details": [
{
"appIDs": ["<APPLE_TEAM_ID>.<IOS_BUNDLE_ID>"],
"components": [
{
"/": "*",
"comment": "Matches all routes"
}
]
}
]
},
"activitycontinuation": {
"apps": ["<APPLE_TEAM_ID>.<IOS_BUNDLE_ID>"]
},
"webcredentials": {
"apps": ["<APPLE_TEAM_ID>.<IOS_BUNDLE_ID>"]
}
}
webcredentials
对象是可选的,但建议使用。¥The
webcredentials
object is optional but recommended.
你可以使用以下命令根据你的 应用配置 生成 apple-app-site-association 文件:
¥You can use the following command to generate the apple-app-site-association file based on your app config:
-
npx setup-safari
请参阅 通用链接调试 开发中测试切换指南。
¥See Universal Link debugging guide to test handoff in development.
¥Expo Head setup
确保使用 expo-router
配置插件在 app.config.tsx
文件中设置切换原点。这是当用户切换到你的应用时将用于 webpageUrl
的 URL。
¥Ensure you set the Handoff origin in your app.config.tsx
file using the expo-router
config plugin. This is the URL that will be used for the webpageUrl
when the user switches to your app.
// Be sure to change this to be unique to your project.
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}`,
// Add additional production-URLs here.
// `applinks:example.com`,
// `activitycontinuation:example.com`,
// `webcredentials:example.com`,
],
},
plugins: [
[
'expo-router',
{
// Note: The URL must start with "https://" in "headOrigin"
headOrigin:
process.env.NODE_ENV === 'development'
? `https://${ngrokUrl}`
: 'https://my-website-example.com',
},
],
],
};
测试切换到原生时,请勿使用仅开发的
?mode=developer
后缀。¥Do not use the development-only
?mode=developer
suffix when testing handoff to native.
配置应用配置后,使用以下命令重新生成原生项目:
¥After configuring the app config, regenerate your native project with the following command:
-
npx expo prebuild -p ios
在开发过程中,你必须在设备上安装应用之前启动网站。这是因为当你安装该应用时,操作系统将触发 Apple 的服务器对你的网站执行 ping 操作以获取 .well-known/apple-app-site-association 文件。如果网站未运行,操作系统将无法找到该文件,并且切换将无法进行。如果发生这种情况,请使用 npx expo run:ios -d
重建原生应用。
¥In development, you must start the website before installing the app on your device. This is because when you install the app, the OS will trigger Apple's servers to ping your website for the .well-known/apple-app-site-association file. If the website is not running, the OS will not be able to find the file and handoff will not work. If this happens, rebuild the native app with npx expo run:ios -d
.
¥Usage
在任何你想要支持切换的路由中,请使用 expo-router/head
中的 Head
组件:
¥In any route that you want to support handoff, use the Head
component from expo-router/head
:
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>Hello World</Text>
</>
);
}
¥Meta tags
expo-router/head
组件支持以下元标记:
¥The expo-router/head
component supports the following meta tags:
元标签 | 描述 |
---|---|
expo:handoff | 设置为 true 以启用当前路由的切换。默认为 false 。(仅限 iOS) |
og:title 和 <title> | 设置 NSUserActivity 的标题,该标题在切换时不使用。 |
og:description | 设置 NSUserActivity 的描述,该描述不用于切换。 |
og:url | 设置当用户切换到你的应用时应打开的 URL。默认为应用内的当前 URL,并在 expo-router 配置插件中使用 headOrigin 属性作为基本 URL。传递相对路径会将 headOrigin 附加到路径中。 |
你可能想在平台之间切换值,为此你可以使用 Platform.select:
¥You may want to switch the values between platforms, for that you can use Platform.select:
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>
);
}
¥Debugging
确保你的 Apple 设备具有 启用切换。你可以按照以下步骤进行测试,但将你的应用替换为 Safari。
¥Ensure your Apple devices have Handoff enabled. You can test this by following the steps below but substituting your app with Safari.
打开设备上的原生应用。
¥Open your native application on your device.
导航到应用中支持切换的路由,该路由正在从 Expo Router 渲染 <Head />
元素。
¥Navigate to a route in the app that supports handoff which is rendering the <Head />
element from Expo Router.
要切换到 Mac,请单击 Dock 中应用的“Handoff”图标。
¥To switch to your Mac and click the app's Handoff icon in the Dock.
要切换到 iPhone 或 iPad,请打开应用切换器,然后点击屏幕底部的应用横幅。
¥To switch to your iPhone or iPad, open the App Switcher, and tap the app banner at the bottom of the screen.
如果你在 iPhone 的应用切换器中只看到 Safari 图标,则说明切换不起作用。
¥If you only see the Safari icon in your iPhone's App Switcher, then handoff is not working.
¥Troubleshooting
你可以使用验证器(例如 AASA 验证器)测试 Apple App Site Association 文件 (public/.well-known/apple-app-site-association)。
¥You can test the Apple App Site Association file (public/.well-known/apple-app-site-association) by using a validator such as, AASA Validator.
如果你遇到问题,最好的办法是在应用中启用最激进的切换设置。这确保了任何可能的路由都是可链接的。你可以通过确保 public/.well-known/apple-app-site-association 文件匹配所有路由来做到这一点:
¥If you're having issues, the best thing you can do is enable the most aggressive handoff settings in your app. This ensures that any possible route is linkable. You can do this by making sure that public/.well-known/apple-app-site-association file matches all routes:
{
"applinks": {
"details": [
{
"appIDs": ["<APPLE_TEAM_ID>.<IOS_BUNDLE_ID>"],
"components": [
{
"/": "*",
"comment": "Matches all routes"
}
]
}
]
}
}
在应用中,确保你没有有条件地渲染 <Head />
元素(例如,在 if/else
块中),它必须在你想要支持切换的每个页面上渲染。我们建议将其添加到 根布局 组件中,以确保调试时每个路由都是可链接的。
¥In the application, ensure you are not rendering the <Head />
element conditionally (for example, in an if/else
block), it must be rendered on every page that you want to support handoff. We recommend adding it to the Root Layout component to ensure every route is linkable while debugging.
在设备上安装应用之前,请确保你可以访问 Ngrok URL(例如,通过浏览器)。如果无法访问该 URL,操作系统将无法找到该文件,并且切换将无法进行。
¥Ensure you can access the Ngrok URL (for example, via the browser), before installing the app on your device. If you can't access the URL, the OS will not be able to find the file and handoff will not work.
当关联域设置时,npx expo run:ios
和 Xcode 都会对你的应用进行协同设计,这是切换和通用链接工作所必需的。
¥npx expo run:ios
and Xcode will both codesign your app when associated domains is set up, this is required for handoff and universal links to work.
Expo Go 应用不支持 Mac 和 iPhone/iPad 之间的切换。你必须在设备上构建并安装你的应用。
¥Handoff between your Mac and iPhone/iPad is not supported in the Expo Go app. You must build and install your app on your device.
如果你在 iPhone 上的应用切换器中看到 Safari 图标,则表示切换不起作用。
¥If you see the Safari icon in the App Switcher on your iPhone, then it means handoff is not working.
确保在测试切换到原生时没有使用 ?mode=developer
后缀。
¥Ensure you are not using the ?mode=developer
suffix when testing handoff to native.
另请确保你没有使用本地开发服务器 URL。例如,http://localhost:8081
,因为这不能用作有效的应用站点关联链接,请在浏览器中打开正在运行的 Ngrok URL 进行测试。
¥Also be sure you're not using the local development server URL. For example, http://localhost:8081
as this cannot be used as a valid app site association link, open the running Ngrok URL in your browser to test.
确保你的 public/.well-known/apple-app-site-association 文件包含 activitycontinuation
字段。
¥Ensure your public/.well-known/apple-app-site-association file contains the activitycontinuation
field.
我们观察到,在 iOS 16.3.1 和 macOS 13.0 (Ventura) 中,以 app.
和 io.
开头的包标识符有时不会触发原生应用显示在 iOS 任务切换器中。使用 com.
作为打包包标识符的第一部分。
¥We've observed that in iOS 16.3.1 and macOS 13.0 (Ventura), bundle identifiers starting with app.
and io.
will sometimes not trigger the native app to show up in the iOS task switcher. Use com.
as the first part of your bundle identifier.
你的 public/.well-known/apple-app-site-association 必须通过安全 URL (HTTPS) 提供服务。如果你使用开发隧道,则必须使用 EXPO_TUNNEL_SUBDOMAIN
环境变量来配置开发隧道的子域。开发中的测试需要隧道,因为你需要 SSL 才能使用通用链接,Expo CLI 通过运行 npx expo start --tunnel
对此提供内置支持。
¥Your public/.well-known/apple-app-site-association must be served from a secure URL (HTTPS). If you are using a development tunnel, you must use the EXPO_TUNNEL_SUBDOMAIN
environment variable to configure the subdomain for your development tunnel. The tunnel is required for testing in development because you need SSL to use universal links, Expo CLI provides built-in support for this by running npx expo start --tunnel
.
检查你的 ios/project/project.entitlements 文件,位于 com.apple.developer.associated-domains
键下。这应该包含与你的网络服务器/网站相同的域。URL 不能包含协议 (https://
) 或其他路径名、查询参数或片段。
¥Check your ios/project/project.entitlements file, under the com.apple.developer.associated-domains
key. This should contain the same domains as your web server/website. The URL cannot contain a protocol (https://
) or additional pathname, query parameters, or fragments.
¥Still stuck
这是一个重要但非常难以设置的功能。Expo Router 自动化了许多移动部件,Expo CLI 自动化了大部分配置和托管。然而,硬件设置仍然可能配置错误。
¥This is an important but very difficult feature to set up. Expo Router automates many of the moving parts, Expo CLI automates much of the configuration and hosting. However, hardware settings can still be misconfigured.
如果以上方法均失败,你可以尝试按照 苹果文档.1 中的步骤来调试问题。注意:
¥If all else fails, you can try to debug the issue by following the steps in the Apple Docs. Note that:
“将用户活动表示为 NSUserActivity
的实例。” 由 Expo Head 原生模块执行。
¥"Representing user activities as instances of NSUserActivity
." is performed by the Expo Head native module.
"当用户在你的应用中执行操作时更新活动实例。" 是通过安装/渲染内部带有元标记 <meta property="expo:handoff" content="true" />
的 <Head />
组件来执行的。
¥"Updating the activity instances as the user performs actions in your app." is performed by mounting/rendering the <Head />
component with the meta tag <meta property="expo:handoff" content="true" />
inside.
"在其他设备上的应用中接收来自 Handoff 的活动。" 由 Expo Head 原生模块中的 应用委托订阅者 执行。当你切换到原生应用时,它用于将你重定向到正确的路由。
¥"Receiving activities from Handoff in your app on other devices." is performed by an App Delegate Subscriber in the Expo Head native module. It is used to redirect you to the correct route when you handoff to your native app.
¥Known issues
从 Web 到原生的切换不支持客户端路由。这意味着应用切换器中显示的 URL 将是你单击链接或重新加载页面时所在页面的 URL。这是网络平台的限制,Expo Router 无法解决。
¥Handoff from web to native does not support client-side routing. This means the URL presented in the App Switcher will be the URL of the page you were on when you clicked the link, or reloaded the page. It is a limitation of the web platform and not something that can be fixed by Expo Router.