Chapter 07

一条 URL 能通两端

用户在微信收到一条 https://myapp.com/products/42,装了 App 直接打开 App 到详情页,没装 App 打开 Web 同一页——这就是 Universal Links/App Links。Expo Router 因为天然按 URL 组织,配两处域名关联就开箱可用。

三类深链接

类型示例特点
Scheme URLmyapp://products/42私有协议,浏览器不能直接解析,需要中转页
Universal Link (iOS)https://myapp.com/products/42标准 https,装了 App 直达,未装 App 走 Web
App Link (Android)https://myapp.com/products/42同上,安卓实现

步骤一:app.json 配 scheme + 关联域名

{
  "expo": {
    "scheme": "myapp",
    "ios": {
      "bundleIdentifier": "com.gufa.myapp",
      "associatedDomains": ["applinks:myapp.com"]
    },
    "android": {
      "package": "com.gufa.myapp",
      "intentFilters": [
        {
          "action": "VIEW",
          "autoVerify": true,
          "data": [{ "scheme": "https", "host": "myapp.com" }],
          "category": ["BROWSABLE", "DEFAULT"]
        }
      ]
    }
  }
}

步骤二:上传验证文件

iOS 要在 https://myapp.com/.well-known/apple-app-site-association
Android 要在 https://myapp.com/.well-known/assetlinks.json
// apple-app-site-association(注意:无扩展名,content-type: application/json)
{
  "applinks": {
    "details": [{
      "appID": "TEAMID.com.gufa.myapp",
      "paths": ["*"]
    }]
  }
}
// assetlinks.json
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.gufa.myapp",
    "sha256_cert_fingerprints": ["AA:BB:CC:..."]
  }
}]
sha256 指纹从哪拿
EAS 签名的包:eas credentials 能看到。开发版 keystore:keytool -list -v -keystore ~/.android/debug.keystore。苹果 TeamID 在 developer.apple.com 账户里。

步骤三:在 App 里 URL 自动映射

Expo Router 默认把收到的链接根据文件结构匹配——不用写任何 linking 配置:

收到 https://myapp.com/products/42
  → Expo Router 内部路径 /products/42
  → 打开 app/products/[id].tsx,id = '42'
收到 myapp://products/42
  → 同样映射到 /products/42

测试链接

# iOS 模拟器
xcrun simctl openurl booted "https://myapp.com/products/42"
xcrun simctl openurl booted "myapp://products/42"

# Android 模拟器
adb shell am start -W -a android.intent.action.VIEW \
  -d "https://myapp.com/products/42" com.gufa.myapp

# 真机只要装了 App,微信/浏览器点链接即可

动态处理链接

import * as Linking from 'expo-linking';
import { useEffect } from 'react';

useEffect(() => {
  // 冷启动时
  Linking.getInitialURL().then((url) => {
    if (url) console.log('冷启动链接', url);
  });

  // 运行中再收到
  const sub = Linking.addEventListener('url', ({ url }) => {
    console.log('运行时链接', url);
  });

  return () => sub.remove();
}, []);

一般情况不用自己监听——Expo Router 会自动把 URL 转成路由切换,你在屏幕里用 useLocalSearchParams 拿参数就行。

拆解 URL

import * as Linking from 'expo-linking';

const url = 'https://myapp.com/products/42?ref=social';
const parsed = Linking.parse(url);
// {
//   scheme: 'https',
//   hostname: 'myapp.com',
//   path: 'products/42',
//   queryParams: { ref: 'social' }
// }

prefixes:多个域名共存

{
  "ios": {
    "associatedDomains": [
      "applinks:myapp.com",
      "applinks:share.myapp.com",
      "applinks:m.myapp.com"
    ]
  }
}

同时上传三份 apple-app-site-association 到各域名——一个 App 可关联多个域名,营销活动、短链都能直达。

短链服务

Firebase Dynamic Links 2025 年下线后,Expo 社区偏向自建或用 Branch/AppsFlyer:

自建短链
Web 服务 + SQLite/KV:https://s.myapp.com/x8k2 → 302 到 https://myapp.com/products/42。这个 302 目标自己是 Universal Link,直接拉起 App。
Branch.io
老牌归因平台,支持延迟深链(装完 App 第一次打开仍能跳对)。有免费额度。

未装 App 的降级

Universal Link 最大的好处:未装时浏览器正常打开 Web 同一 URL。这意味着 Web 端也得是 Expo Router 站点,或至少有同样的 /products/:id 页面——第 8 章讲三端一体。

常见坑

iOS 安装后不生效
检查 apple-app-site-association 是否 https、content-type 是否 application/json(不是 text/html)、文件名没有 .json 扩展。可用 https://app-site-association.cdn-apple.com/a/v1/myapp.com 查验 Apple 是否抓到。
Android autoVerify 失败
sha256 指纹要和实际签名一致。release/dev 的指纹不同,建议 assetlinks.json 里列多个。
微信内打不开 App
微信会劫持 https 链接在 webview 里打开,用户需要右上角"用浏览器打开"。中国 Apps 常做 URL Scheme 降级 + 提示引导。

本章小结