name: deep-linking description: 用于实现iOS Universal Links、Android App Links、自定义URL方案以及跨移动平台延迟深度链接的通用链接和深度链接技能。 allowed-tools: Read, Grep, Write, Bash, Edit, Glob, WebFetch
深度链接技能
为iOS和Android提供全面的深度链接实现,包括通用链接、应用链接、自定义URL方案和延迟深度链接。
概述
此技能提供了跨移动平台实现深度链接的能力,使用户能够从外部来源(如网页链接、通知、电子邮件和其他应用)直接导航到您应用内的特定内容。
能力
iOS通用链接
- 配置apple-app-site-association (AASA) 文件
- 设置关联域权限
- 实现NSUserActivity处理
- 验证通用链接配置
- 处理回退到App Store
Android应用链接
- 配置assetlinks.json (数字资产链接)
- 为应用链接设置意图过滤器
- 实现深度链接处理
- 验证应用链接配置
- 处理回退到Play Store
自定义URL方案
- 注册自定义URL方案
- 处理URL方案回调
- 解析URL参数
- 实现方案验证
- 跨应用通信
延迟深度链接
- 配置Branch.io或Firebase动态链接
- 处理首次打开归因
- 通过安装传递深度链接数据
- 跟踪深度链接转化
- 实现回退流程
深度链接路由
- 设计URL结构和路由
- 实现应用内导航
- 处理认证要求
- 管理深度链接状态持久化
- 跟踪深度链接分析
先决条件
iOS开发
# 在Xcode中启用关联域能力
# Signing & Capabilities > + Capability > Associated Domains
# 添加域名: applinks:example.com
Android开发
// 基本应用链接无需额外依赖
// 对于Firebase动态链接:
dependencies {
implementation platform('com.google.firebase:firebase-bom:32.7.0')
implementation 'com.google.firebase:firebase-dynamic-links'
}
// 对于Branch.io:
dependencies {
implementation 'io.branch.sdk.android:library:5.+'
}
Web服务器要求
# iOS: 在以下位置托管AASA文件
# https://example.com/.well-known/apple-app-site-association
# Android: 在以下位置托管assetlinks.json文件
# https://example.com/.well-known/assetlinks.json
使用模式
Apple应用网站关联 (AASA) 文件
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.example.app",
"paths": [
"/products/*",
"/users/*",
"/orders/*",
"NOT /admin/*"
]
}
]
},
"webcredentials": {
"apps": ["TEAMID.com.example.app"]
}
}
iOS通用链接实现 (SwiftUI)
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
handleDeepLink(url)
}
}
}
func handleDeepLink(_ url: URL) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let host = components.host else {
return
}
let path = components.path
let queryItems = components.queryItems ?? []
// 基于路径路由
switch (host, path) {
case ("example.com", let p) where p.hasPrefix("/products/"):
let productId = String(p.dropFirst("/products/".count))
DeepLinkRouter.shared.navigateTo(.product(id: productId))
case ("example.com", let p) where p.hasPrefix("/users/"):
let userId = String(p.dropFirst("/users/".count))
DeepLinkRouter.shared.navigateTo(.profile(userId: userId))
case ("example.com", "/orders"):
DeepLinkRouter.shared.navigateTo(.orders)
default:
DeepLinkRouter.shared.navigateTo(.home)
}
}
}
// 深度链接路由器
class DeepLinkRouter: ObservableObject {
static let shared = DeepLinkRouter()
@Published var currentDestination: Destination = .home
enum Destination: Equatable {
case home
case product(id: String)
case profile(userId: String)
case orders
}
func navigateTo(_ destination: Destination) {
DispatchQueue.main.async {
self.currentDestination = destination
}
}
}
iOS通用链接 (UIKit)
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return
}
handleUniversalLink(url)
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
handleCustomScheme(url)
}
private func handleUniversalLink(_ url: URL) {
// 路由到适当的视图控制器
let router = DeepLinkRouter.shared
router.route(url: url)
}
private func handleCustomScheme(_ url: URL) {
// 处理myapp://方案
guard url.scheme == "myapp" else { return }
let router = DeepLinkRouter.shared
router.route(url: url)
}
}
Android assetlinks.json
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.app",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
]
}
}
]
Android应用链接实现 (Kotlin)
// AndroidManifest.xml
/*
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="example.com"
android:pathPrefix="/products" />
<data android:scheme="https"
android:host="example.com"
android:pathPrefix="/users" />
</intent-filter>
<!-- 自定义URL方案 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
</activity>
*/
// MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 处理冷启动时的深度链接
handleIntent(intent)
setContent {
MyApp()
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// 处理应用已在运行时的深度链接
handleIntent(intent)
}
private fun handleIntent(intent: Intent?) {
val action = intent?.action
val data = intent?.data
if (action == Intent.ACTION_VIEW && data != null) {
handleDeepLink(data)
}
}
private fun handleDeepLink(uri: Uri) {
val path = uri.path ?: return
val host = uri.host
when {
path.startsWith("/products/") -> {
val productId = path.removePrefix("/products/")
navigateToProduct(productId)
}
path.startsWith("/users/") -> {
val userId = path.removePrefix("/users/")
navigateToProfile(userId)
}
path == "/orders" -> {
navigateToOrders()
}
else -> {
navigateToHome()
}
}
}
}
Jetpack Compose导航与深度链接
import androidx.navigation.compose.*
import androidx.navigation.navDeepLink
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") {
HomeScreen()
}
composable(
route = "product/{productId}",
deepLinks = listOf(
navDeepLink {
uriPattern = "https://example.com/products/{productId}"
},
navDeepLink {
uriPattern = "myapp://product/{productId}"
}
)
) { backStackEntry ->
val productId = backStackEntry.arguments?.getString("productId")
ProductScreen(productId = productId)
}
composable(
route = "profile/{userId}",
deepLinks = listOf(
navDeepLink {
uriPattern = "https://example.com/users/{userId}"
}
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
ProfileScreen(userId = userId)
}
}
}
React Native深度链接
// App.js
import { Linking } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
const linking = {
prefixes: ['https://example.com', 'myapp://'],
config: {
screens: {
Home: '',
Product: 'products/:productId',
Profile: 'users/:userId',
Orders: 'orders',
},
},
};
function App() {
return (
<NavigationContainer linking={linking}>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Product" component={ProductScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Orders" component={OrdersScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
// 手动处理深度链接
useEffect(() => {
const handleDeepLink = (event) => {
const url = event.url;
// 解析并导航
};
Linking.addEventListener('url', handleDeepLink);
// 检查初始URL(冷启动)
Linking.getInitialURL().then((url) => {
if (url) {
handleDeepLink({ url });
}
});
return () => {
Linking.removeEventListener('url', handleDeepLink);
};
}, []);
与Babysitter SDK集成
任务定义示例
const deepLinkTask = defineTask({
name: 'deep-link-setup',
description: '为移动应用配置深度链接',
inputs: {
platform: { type: 'string', required: true, enum: ['ios', 'android', 'both'] },
domain: { type: 'string', required: true },
paths: { type: 'array', items: { type: 'string' }, required: true },
customScheme: { type: 'string' },
projectPath: { type: 'string', required: true }
},
outputs: {
aasaFile: { type: 'string' },
assetlinksFile: { type: 'string' },
appConfiguration: { type: 'object' },
verificationSteps: { type: 'array' }
},
async run(inputs, taskCtx) {
return {
kind: 'skill',
title: `为${inputs.domain}配置深度链接`,
skill: {
name: 'deep-linking',
context: {
operation: 'configure',
platform: inputs.platform,
domain: inputs.domain,
paths: inputs.paths,
customScheme: inputs.customScheme,
projectPath: inputs.projectPath
}
},
io: {
inputJsonPath: `tasks/${taskCtx.effectId}/input.json`,
outputJsonPath: `tasks/${taskCtx.effectId}/result.json`
}
};
}
});
验证命令
iOS通用链接验证
# 验证AASA文件
curl -I https://example.com/.well-known/apple-app-site-association
# 检查AASA内容
curl https://example.com/.well-known/apple-app-site-association | jq
# 使用Apple的CDN验证器
curl "https://app-site-association.cdn-apple.com/a/v1/example.com"
# 在设备上测试 (Console.app)
# 通过"swcd"过滤查看通用链接调试信息
Android应用链接验证
# 验证assetlinks.json
curl -I https://example.com/.well-known/assetlinks.json
# 检查内容
curl https://example.com/.well-known/assetlinks.json | jq
# 在设备上验证
adb shell pm get-app-links com.example.app
# 重置验证状态
adb shell pm set-app-links --package com.example.app 0 all
adb shell pm verify-app-links --re-verify com.example.app
最佳实践
- 验证服务器配置:确保AASA和assetlinks.json正确提供
- 处理所有状态:深度链接应在前台、后台和冷启动时都能工作
- 实现回退机制:如果应用未安装,重定向到网页或应用商店
- 保护深度链接:在操作前验证深度链接参数
- 跟踪分析:记录深度链接来源和转化
- 全面测试:部署前测试所有路径和边界情况