深度链接 #

一、深度链接概述 #

1.1 链接类型 #

类型 平台 说明
URL Scheme iOS/Android 自定义协议,如 myapp://
Universal Links iOS HTTPS链接,无缝跳转
App Links Android HTTPS链接,无缝跳转

1.2 使用场景 #

  • 分享链接打开应用特定页面
  • 推送通知跳转
  • 营销活动链接
  • 第三方登录回调

二、URL Scheme #

2.1 iOS配置 #

xml
<!-- ios/App/App/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
        <key>CFBundleURLName</key>
        <string>com.company.myapp</string>
    </dict>
</array>

2.2 Android配置 #

xml
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity" ...>
    
    <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>

2.3 使用URL Scheme #

html
<!-- 网页链接 -->
<a href="myapp://profile/123">打开用户资料</a>
<a href="myapp://product?id=456">查看商品</a>

2.4 JavaScript处理 #

typescript
import { App } from '@capacitor/app';

// 监听URL打开
App.addListener('appUrlOpen', (data) => {
    console.log('URL opened:', data.url);
    
    // 解析URL
    const url = new URL(data.url);
    const path = url.pathname;
    const params = url.searchParams;
    
    // 根据路径导航
    if (path.startsWith('/profile/')) {
        const userId = path.split('/')[2];
        router.push(`/profile/${userId}`);
    } else if (path.startsWith('/product')) {
        const productId = params.get('id');
        router.push(`/product/${productId}`);
    }
});

3.1 配置步骤 #

1. 开启Associated Domains #

  1. 登录Apple Developer
  2. 选择App ID
  3. 启用Associated Domains

2. Xcode配置 #

text
Target → Signing & Capabilities → + Capability → Associated Domains

添加: applinks:example.com

3. 创建apple-app-site-association文件 #

json
// https://example.com/.well-known/apple-app-site-association
{
    "applinks": {
        "details": [
            {
                "appID": "TEAMID.com.company.myapp",
                "paths": ["*"]
            }
        ]
    }
}

4. 服务器配置 #

nginx
# Nginx配置
location /.well-known/apple-app-site-association {
    default_type application/json;
    alias /var/www/.well-known/apple-app-site-association;
}

# 确保HTTPS
server {
    listen 443 ssl;
    server_name example.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
}
swift
// ios/App/App/AppDelegate.swift
import UIKit
import Capacitor

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication, 
                     continue userActivity: NSUserActivity, 
                     restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
           let url = userActivity.webpageURL {
            
            // 通知Capacitor
            ApplicationDelegateProxy.shared.application(
                application,
                continue: userActivity,
                restorationHandler: restorationHandler
            )
            
            return true
        }
        
        return false
    }
}
typescript
// JavaScript处理
import { App } from '@capacitor/app';

App.addListener('appUrlOpen', (data) => {
    // data.url: https://example.com/profile/123
    handleDeepLink(data.url);
});

function handleDeepLink(url: string) {
    const parsedUrl = new URL(url);
    
    // 路由处理
    switch (parsedUrl.pathname) {
        case '/profile':
            const userId = parsedUrl.searchParams.get('id');
            router.push(`/profile/${userId}`);
            break;
        case '/product':
            const productId = parsedUrl.searchParams.get('id');
            router.push(`/product/${productId}`);
            break;
        default:
            router.push(parsedUrl.pathname);
    }
}

4.1 配置步骤 #

1. 创建assetlinks.json #

json
// https://example.com/.well-known/assetlinks.json
[{
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
        "namespace": "android_app",
        "package_name": "com.company.myapp",
        "sha256_cert_fingerprints": [
            "14:6D:E9:83:C5:CE:..."
        ]
    }
}]

2. 获取SHA256指纹 #

bash
keytool -list -v -keystore my-release-key.jks

3. AndroidManifest配置 #

xml
<activity android:name=".MainActivity" ...>
    
    <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" />
        <data android:host="example.com" />
    </intent-filter>
    
</activity>
java
// MainActivity.java
@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    
    if (Intent.ACTION_VIEW.equals(intent.getAction())) {
        Uri uri = intent.getData();
        if (uri != null) {
            // 处理深度链接
            handleDeepLink(uri.toString());
        }
    }
}
typescript
// JavaScript处理
import { App } from '@capacitor/app';

App.addListener('appUrlOpen', (data) => {
    handleDeepLink(data.url);
});

五、深度链接服务 #

5.1 封装深度链接服务 #

typescript
// src/services/deeplink.service.ts
import { App } from '@capacitor/app';
import { Capacitor } from '@capacitor/core';

type DeepLinkHandler = (params: Record<string, string>) => void;

interface DeepLinkRoute {
    path: string | RegExp;
    handler: DeepLinkHandler;
}

class DeepLinkService {
    private routes: DeepLinkRoute[] = [];
    private initialized = false;
    
    async init(): Promise<void> {
        if (this.initialized) return;
        
        // 监听应用打开URL
        await App.addListener('appUrlOpen', (data) => {
            this.handleUrl(data.url);
        });
        
        // 检查冷启动时的URL
        const launchUrl = await this.getLaunchUrl();
        if (launchUrl) {
            this.handleUrl(launchUrl);
        }
        
        this.initialized = true;
    }
    
    private async getLaunchUrl(): Promise<string | null> {
        try {
            const { url } = await App.getLaunchUrl();
            return url;
        } catch {
            return null;
        }
    }
    
    register(path: string | RegExp, handler: DeepLinkHandler): void {
        this.routes.push({ path, handler });
    }
    
    private handleUrl(url: string): void {
        console.log('Deep link URL:', url);
        
        try {
            const parsedUrl = new URL(url);
            const pathname = parsedUrl.pathname;
            const params = Object.fromEntries(parsedUrl.searchParams);
            
            // 匹配路由
            for (const route of this.routes) {
                if (this.matchPath(route.path, pathname)) {
                    route.handler(params);
                    return;
                }
            }
            
            console.warn('No handler for deep link:', pathname);
        } catch (error) {
            console.error('Error parsing deep link:', error);
        }
    }
    
    private matchPath(pattern: string | RegExp, pathname: string): boolean {
        if (pattern instanceof RegExp) {
            return pattern.test(pathname);
        }
        
        return pathname === pattern || pathname.startsWith(pattern + '/');
    }
}

export const deepLinkService = new DeepLinkService();

5.2 使用示例 #

typescript
// 初始化
await deepLinkService.init();

// 注册路由
deepLinkService.register('/profile', (params) => {
    const userId = params.id;
    router.push(`/profile/${userId}`);
});

deepLinkService.register('/product', (params) => {
    const productId = params.id;
    router.push(`/product/${productId}`);
});

deepLinkService.register(/^\/article\/\d+$/, (params) => {
    // 匹配 /article/123 格式
    router.push(window.location.pathname);
});

六、测试深度链接 #

6.1 iOS测试 #

bash
# 测试URL Scheme
xcrun simctl openurl booted "myapp://profile/123"

# 测试Universal Links
xcrun simctl openurl booted "https://example.com/profile/123"

6.2 Android测试 #

bash
# 测试URL Scheme
adb shell am start -a android.intent.action.VIEW \
    -d "myapp://profile/123"

# 测试App Links
adb shell am start -a android.intent.action.VIEW \
    -d "https://example.com/profile/123"

6.3 验证配置 #

bash
# iOS验证
curl -v https://example.com/.well-known/apple-app-site-association

# Android验证
curl -v https://example.com/.well-known/assetlinks.json

七、延迟深度链接 #

7.1 实现延迟深度链接 #

typescript
class DeferredDeepLinkService {
    private storageKey = 'deferred_deep_link';
    
    async saveDeferredLink(url: string): Promise<void> {
        await Preferences.set({
            key: this.storageKey,
            value: JSON.stringify({
                url,
                timestamp: Date.now()
            })
        });
    }
    
    async getDeferredLink(): Promise<string | null> {
        const { value } = await Preferences.get({ key: this.storageKey });
        
        if (!value) return null;
        
        const data = JSON.parse(value);
        
        // 检查是否过期(24小时)
        if (Date.now() - data.timestamp > 24 * 60 * 60 * 1000) {
            await this.clearDeferredLink();
            return null;
        }
        
        return data.url;
    }
    
    async clearDeferredLink(): Promise<void> {
        await Preferences.remove({ key: this.storageKey });
    }
}

八、总结 #

8.1 配置要点 #

平台 配置文件 验证文件
iOS URL Scheme Info.plist -
iOS Universal Links Associated Domains apple-app-site-association
Android URL Scheme AndroidManifest.xml -
Android App Links AndroidManifest.xml assetlinks.json

8.2 下一步 #

了解深度链接后,让我们学习 后台任务

最后更新:2026-03-28