HarmonyOS开发:动态链接Deep Link

举报
Jack20 发表于 2026/06/25 20:55:14 2026/06/25
【摘要】 HarmonyOS开发:动态链接Deep Link📌 核心要点:Deep Link让外部链接直达应用内特定页面,URL Scheme是基础方案,App Linking是进阶方案,延迟深度链接解决未安装用户的跳转问题。 背景与动机用户在浏览器看到一篇好文章,点链接想打开你的App看完整内容,结果呢?App是打开了,但停在了首页。用户还得自己去找那篇文章——找到算他运气好,找不到直接关了。这...

HarmonyOS开发:动态链接Deep Link

📌 核心要点:Deep Link让外部链接直达应用内特定页面,URL Scheme是基础方案,App Linking是进阶方案,延迟深度链接解决未安装用户的跳转问题。

背景与动机

用户在浏览器看到一篇好文章,点链接想打开你的App看完整内容,结果呢?App是打开了,但停在了首页。用户还得自己去找那篇文章——找到算他运气好,找不到直接关了。

这就是没有Deep Link的后果:链接打开了App,却没打开对的页面。

你以为这就完了?运营在外部分享链接,用户点进去到不了活动页;广告投放的链接,用户点进去到不了商品详情;推送通知里的链接,用户点进去到不了消息对应的页面。每一个环节都在流失用户。

Deep Link要解决的就是这个问题:用户点一个链接,直接跳到应用内对应的页面,中间零摩擦。

但Deep Link不是配个URL Scheme就完事了。你得考虑:用户没装App怎么办?多个App注册了同一个Scheme怎么办?链接被其他应用拦截了怎么办?延迟深度链接怎么处理?这些问题不搞清楚,你的Deep Link就是个半成品。

核心原理

Deep Link的本质是:操作系统根据URL的规则,把链接路由到能处理它的应用,并传递URL中的参数。

flowchart TD
    A[用户点击链接] --> B{链接类型判断}
    B -->|URL Scheme| C[系统查找匹配的App]
    B -->|App Linking/Universal Link| D[系统验证域名关联]
    
    C --> E{找到匹配App?}
    E -->|| F[启动App并传递URL]
    E -->|| G[浏览器打开或报错]
    
    D --> H{验证通过?}
    H -->|| I[直接启动App并传递URL]
    H -->|| J[浏览器打开链接]
    
    F --> K[App解析URL参数]
    I --> K
    K --> L[路由到目标页面]
    
    L --> M{App已安装?}
    M -->|| N[正常跳转]
    M -->|| O[延迟深度链接处理]
    O --> P[引导安装App]
    P --> Q[安装后首次启动恢复跳转]
    
    classDef startEnd fill:#6A1B9A,color:#fff,stroke:#4A148C
    classDef process fill:#1976D2,color:#fff,stroke:#0D47A1
    classDef decision fill:#E65100,color:#fff,stroke:#BF360C
    classDef error fill:#C62828,color:#fff,stroke:#B71C1C
    classDef success fill:#2E7D32,color:#fff,stroke:#1B5E20

    class A,startEnd
    class C,D,F,I,K,L,N,P,Q,process
    class B,E,H,M,decision
    class G,J,error
    class O,success

URL Scheme vs App Linking

这是两种不同的深度链接方案,各有适用场景:

对比项 URL Scheme App Linking
格式 myapp://path https://myapp.com/path
安全性 低,任何App都能注册同一Scheme 高,需要域名验证
未安装处理 打不开,直接报错 可跳应用市场或网页
系统弹窗 可能弹选择框 无弹窗,直接跳
配置复杂度 简单 需要配置域名关联文件

简单说:URL Scheme是快速方案,App Linking是正规方案。 内部跳转用Scheme够了,对外分享和推广必须用App Linking。

延迟深度链接

这是Deep Link的高级形态。用户点链接时还没装你的App,怎么办?

普通Deep Link:用户没装App → 链接打不开 → 用户流失。

延迟深度链接:用户没装App → 引导去应用市场安装 → 安装后首次启动 → 自动跳到链接对应的页面。

关键在于那个"安装后首次启动自动跳转"——这需要服务端记住用户点击的链接信息,App首次启动时去查询。

代码实战

基础用法:URL Scheme配置与处理

先从最基础的URL Scheme开始,在module.json5中配置,然后在Ability中处理跳转。

// module.json5中的配置
// {
//   "module": {
//     "abilities": [
//       {
//         "skills": [
//           {
//             "actions": ["ohos.want.action.VIEW"],
//             "entities": ["entity.system.default", "entity.system.browsable"],
//             "uris": [
//               {
//                 "scheme": "myblog",
//                 "host": "article",
//                 "path": "/detail"
//               }
//             ]
//           }
//         ]
//       }
//     ]
//   }
// }

// DeepLinkHandler.ets
import { AbilityConstant, Want } from '@kit.AbilityKit';
import { router } from '@kit.ArkUI';

export class DeepLinkHandler {
  /**
   * 处理Deep Link跳转
   * 在Ability的onNewWant和onCreate中调用
   */
  static handleDeepLink(want: Want): boolean {
    if (!want.uri) {
      console.warn('[DeepLink] URI为空,忽略');
      return false;
    }

    const uri = want.uri;
    console.info(`[DeepLink] 收到链接: ${uri}`);

    try {
      // 解析URL Scheme: myblog://article/detail?id=123
      const url = new URL(uri);
      const scheme = url.protocol.replace(':', ''); // myblog
      const host = url.hostname;                     // article
      const path = url.pathname;                     // /detail
      const params = url.searchParams;               // id=123

      console.info(`[DeepLink] scheme=${scheme}, host=${host}, path=${path}`);

      // 根据host和path路由到不同页面
      switch (`${host}${path}`) {
        case 'article/detail':
          return DeepLinkHandler.navigateToArticle(params);
        case 'user/profile':
          return DeepLinkHandler.navigateToProfile(params);
        case 'activity/promo':
          return DeepLinkHandler.navigateToActivity(params);
        default:
          console.warn(`[DeepLink] 未知的路由: ${host}${path}`);
          return false;
      }
    } catch (error) {
      console.error(`[DeepLink] 解析URI失败: ${JSON.stringify(error)}`);
      return false;
    }
  }

  /**
   * 跳转到文章详情页
   */
  private static navigateToArticle(params: URLSearchParams): boolean {
    const articleId = params.get('id');
    if (!articleId) {
      console.error('[DeepLink] 文章ID为空');
      return false;
    }

    router.pushUrl({
      url: 'pages/ArticleDetailPage',
      params: { articleId: articleId }
    });

    console.info(`[DeepLink] 跳转到文章: ${articleId}`);
    return true;
  }

  /**
   * 跳转到用户主页
   */
  private static navigateToProfile(params: URLSearchParams): boolean {
    const userId = params.get('uid');
    if (!userId) {
      console.error('[DeepLink] 用户ID为空');
      return false;
    }

    router.pushUrl({
      url: 'pages/UserProfilePage',
      params: { userId: userId }
    });

    return true;
  }

  /**
   * 跳转到活动页
   */
  private static navigateToActivity(params: URLSearchParams): boolean {
    const activityId = params.get('act_id');
    const from = params.get('from') || 'deeplink';

    router.pushUrl({
      url: 'pages/PromotionPage',
      params: { activityId: activityId, from: from }
    });

    return true;
  }
}

进阶用法:App Linking配置与处理

App Linking比URL Scheme安全得多,但配置也更复杂。需要在AGConnect控制台配置域名关联,然后在应用中处理Universal Link。

// AppLinkingHandler.ets
import { Want } from '@kit.AbilityKit';
import { router } from '@kit.ArkUI';
import { appLinking } from '@kit.AppLinkingKit';

export class AppLinkingHandler {
  private static pendingDeepLink: string | null = null;

  /**
   * 处理App Linking跳转
   * 支持https格式的Universal Link
   */
  static handleAppLinking(want: Want): boolean {
    const uri = want.uri;
    if (!uri) return false;

    // App Linking的URI格式: https://myapp.drcn.agconnect.link/xxxxx
    console.info(`[AppLinking] 收到链接: ${uri}`);

    // 判断是否是App Linking的短链
    if (uri.includes('agconnect.link')) {
      return AppLinkingHandler.handleShortLink(uri);
    }

    // 处理自定义域名的Universal Link
    if (uri.startsWith('https://myapp.com')) {
      return AppLinkingHandler.handleUniversalLink(uri);
    }

    return false;
  }

  /**
   * 处理App Linking短链
   * 短链需要先解析获取真实的深度链接
   */
  private static async handleShortLink(shortLink: string): Promise<boolean> {
    try {
      // 解析短链,获取实际的深度链接信息
      const linkInfo = await appLinking.parseLink(shortLink);
      
      if (linkInfo && linkInfo.deepLink) {
        console.info(`[AppLinking] 短链解析成功: ${linkInfo.deepLink}`);
        return AppLinkingHandler.routeToPage(linkInfo.deepLink, linkInfo);
      }

      console.warn('[AppLinking] 短链解析结果为空');
      return false;
    } catch (error) {
      console.error(`[AppLinking] 短链解析失败: ${JSON.stringify(error)}`);
      return false;
    }
  }

  /**
   * 处理Universal Link
   * 直接解析https链接中的路径和参数
   */
  private static handleUniversalLink(uri: string): boolean {
    try {
      const url = new URL(uri);
      // https://myapp.com/article/detail?id=123
      const path = url.pathname;     // /article/detail
      const params = url.searchParams;

      return AppLinkingHandler.routeToPage(uri, { deepLink: uri, socialTitle: '' });
    } catch (error) {
      console.error(`[AppLinking] Universal Link解析失败: ${error}`);
      return false;
    }
  }

  /**
   * 统一路由方法
   */
  private static routeToPage(deepLink: string, linkInfo: appLinking.LinkInfo): boolean {
    try {
      const url = new URL(deepLink);
      const path = url.pathname;
      const params = url.searchParams;

      // 提取UTM参数,用于归因统计
      const utmSource = params.get('utm_source') || '';
      const utmMedium = params.get('utm_medium') || '';
      const utmCampaign = params.get('utm_campaign') || '';

      // 记录归因信息
      AppLinkingHandler.logAttribution(utmSource, utmMedium, utmCampaign);

      // 路由到对应页面
      if (path.startsWith('/article')) {
        const articleId = params.get('id');
        router.pushUrl({
          url: 'pages/ArticleDetailPage',
          params: {
            articleId: articleId,
            utmSource: utmSource,
            utmCampaign: utmCampaign
          }
        });
        return true;
      }

      if (path.startsWith('/activity')) {
        const actId = params.get('act_id');
        router.pushUrl({
          url: 'pages/PromotionPage',
          params: {
            activityId: actId,
            utmSource: utmSource
          }
        });
        return true;
      }

      console.warn(`[AppLinking] 未匹配的路由: ${path}`);
      return false;
    } catch (error) {
      console.error(`[AppLinking] 路由失败: ${error}`);
      return false;
    }
  }

  /**
   * 记录归因信息
   */
  private static logAttribution(source: string, medium: string, campaign: string): void {
    if (!source) return;
    console.info(`[AppLinking] 归因: source=${source}, medium=${medium}, campaign=${campaign}`);
    // 上报到分析服务
  }

  /**
   * 保存延迟深度链接
   * 用户未安装App时,安装后首次启动恢复
   */
  static setPendingDeepLink(link: string): void {
    AppLinkingHandler.pendingDeepLink = link;
    // 持久化到本地存储
    AppStorage.setOrCreate('pending_deep_link', link);
  }

  /**
   * 恢复延迟深度链接
   * 在App首次启动时调用
   */
  static restorePendingDeepLink(): string | null {
    const link = AppStorage.get<string>('pending_deep_link');
    if (link) {
      // 用完即清,避免重复跳转
      AppStorage.delete('pending_deep_link');
      AppLinkingHandler.pendingDeepLink = null;
    }
    return link || null;
  }
}

完整示例:在EntryAbility中集成Deep Link全流程

把URL Scheme、App Linking和延迟深度链接统一集成到EntryAbility中,覆盖所有入口场景。

// EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { DeepLinkHandler } from '../utils/DeepLinkHandler';
import { AppLinkingHandler } from '../utils/AppLinkingHandler';

export default class EntryAbility extends UIAbility {
  // 标记是否是冷启动(首次启动)
  private isColdStart: boolean = true;

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    console.info('[EntryAbility] onCreate');

    // 冷启动时处理Deep Link
    if (want.uri) {
      this.handleIncomingLink(want);
    }
  }

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    console.info('[EntryAbility] onNewWant');

    // 热启动时处理Deep Link
    // App已在后台,通过Deep Link唤起到前台
    if (want.uri) {
      this.handleIncomingLink(want);
    }
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    console.info('[EntryAbility] onWindowStageCreate');

    // 加载主页面
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        console.error(`[EntryAbility] 加载主页面失败: ${JSON.stringify(err)}`);
        return;
      }

      // 页面加载完成后,处理延迟深度链接
      this.handleDeferredDeepLink();
    });
  }

  /**
   * 统一处理传入的链接
   */
  private handleIncomingLink(want: Want): void {
    const uri = want.uri;
    if (!uri) return;

    console.info(`[EntryAbility] 处理链接: ${uri}`);

    // 判断链接类型并分别处理
    if (uri.startsWith('https://')) {
      // App Linking / Universal Link
      AppLinkingHandler.handleAppLinking(want);
    } else {
      // URL Scheme
      DeepLinkHandler.handleDeepLink(want);
    }

    this.isColdStart = false;
  }

  /**
   * 处理延迟深度链接
   * 用户通过链接安装App后,首次启动时恢复跳转
   */
  private handleDeferredDeepLink(): void {
    // 检查是否有待处理的延迟深度链接
    const pendingLink = AppLinkingHandler.restorePendingDeepLink();
    if (pendingLink) {
      console.info(`[EntryAbility] 恢复延迟深度链接: ${pendingLink}`);

      // 延迟一点执行,等主页面完全加载
      setTimeout(() => {
        const want: Want = { uri: pendingLink };
        if (pendingLink.startsWith('https://')) {
          AppLinkingHandler.handleAppLinking(want);
        } else {
          DeepLinkHandler.handleDeepLink(want);
        }
      }, 500);
    }
  }
}

// 在页面组件中使用Deep Link构建器
@Component
struct ShareButton {
  // 构建分享用的Deep Link
  buildArticleLink(articleId: string): string {
    // URL Scheme格式:简单直接
    const schemeLink = `myblog://article/detail?id=${articleId}`;
    
    // App Linking格式:更安全,支持未安装用户
    const appLink = `https://myapp.com/article?id=${articleId}&utm_source=share&utm_medium=button`;
    
    return appLink; // 推荐使用App Linking
  }

  build() {
    Button('分享文章')
      .onClick(() => {
        const link = this.buildArticleLink('12345');
        // 调用系统分享
        // 或者复制到剪贴板
      })
  }
}

踩坑与注意事项

坑1:URL Scheme冲突

你注册了myapp://,别的App也注册了myapp://,用户点链接时系统弹个选择框让用户选——体验极差。更糟糕的是,有些恶意App故意注册热门Scheme来截流。

解决方案:Scheme名用包名的逆序,比如com.mycompany.myapp://,虽然丑但不会冲突。对外分享用App Linking,不要用URL Scheme。

坑2:onNewWant和onCreate都要处理

App在后台时,Deep Link触发的是onNewWant;App没启动时,触发的是onCreate。很多人只在onCreate里处理了,App在后台时点链接就没反应。

必须两个回调都处理,而且处理逻辑要一致。建议抽成统一方法,两个回调都调用。

坑3:冷启动时页面还没加载完

onCreate里收到Deep Link,但此时页面还没加载完,你调router.pushUrl可能失败或者页面栈混乱。

解决方案:在onWindowStageCreateloadContent回调里再处理跳转,或者用setTimeout延迟一小段时间。更好的做法是把待跳转的链接存起来,等主页面aboutToAppear时再执行。

坑4:参数编码问题

URL中的中文参数、特殊字符没做编码,解析时直接报错。myblog://article/detail?title=Hello World,空格没编码,URL解析失败。

必须用encodeURIComponent编码参数,用decodeURIComponent解码。不要偷懒。

坑5:App Linking的域名验证失败

App Linking要求在你的服务器上放一个验证文件(assetlinks.json),系统安装App时会去验证这个文件。验证失败,App Linking降级为普通URL,在浏览器打开。

常见原因:

  • 验证文件路径不对(必须在/.well-known/assetlinks.json
  • HTTPS证书有问题
  • 文件中的包名和签名与App不匹配
  • 服务器设置了重定向

调试方法:用adb shell am start -a android.intent.action.VIEW -d "https://myapp.com/article?id=1"测试,看日志中是否有验证失败的提示。

坑6:延迟深度链接的时效性

延迟深度链接不是永久有效的。用户点链接后7天内安装App,可以恢复跳转;超过7天,链接信息过期,首次启动不会跳转。

另外,延迟深度链接依赖设备上的标识符匹配。用户清除了浏览器数据,或者用了隐私模式,匹配可能失败。

HarmonyOS 6适配说明

HarmonyOS 6对Deep Link和App Linking做了重要增强:

  1. 统一链接框架:HarmonyOS 6引入了统一的Link Router框架,URL Scheme和App Linking共用一套路由表,不用再写两套处理逻辑。在module.json5中配置linkRouter,系统自动根据链接类型分发。
// HarmonyOS 6的linkRouter配置
// "linkRouter": {
//   "rules": [
//     {
//       "pattern": "myblog://article/detail",
//       "target": "pages/ArticleDetailPage",
//       "params": ["id"]
//     },
//     {
//       "pattern": "https://myapp.com/article",
//       "target": "pages/ArticleDetailPage",
//       "params": ["id"]
//     }
//   ]
// }
  1. 延迟深度链接增强:新增了DeferredLinkManager,自动处理"点击链接→安装App→首次启动恢复跳转"的全流程。不再需要你手动保存和恢复pending link。

  2. 链接安全校验加强:App Linking的域名验证文件增加了签名算法要求,SHA-256指纹必须匹配。之前有些场景下SHA-1也能通过,HarmonyOS 6只认SHA-256。

  3. 剪贴板链接识别:HarmonyOS 6新增了剪贴板链接识别能力。用户复制了一个Deep Link,系统会在App顶部显示一个智能横幅,点击即可跳转。不需要用户手动打开浏览器再点链接。

  4. 链接点击归因:新增LinkAttribution API,可以获取用户是通过哪个渠道(微信分享、短信、广告等)点击的链接,方便做精细化运营分析。

总结

Deep Link是连接外部世界和你App的桥梁。URL Scheme简单粗暴但不够安全,App Linking正规可靠但配置复杂,延迟深度链接是未安装用户的救命稻草。

核心记住三点:

  • 对外用App Linking,对内用URL Scheme,别搞反了
  • onCreate和onNewWant都要处理,漏一个就丢一半用户
  • 参数必须编码,中文和特殊字符不编码就是给自己埋雷
评估维度 说明
学习难度 ⭐⭐⭐⭐ URL Scheme简单,App Linking配置复杂,延迟深度链接需要服务端配合
使用频率 ⭐⭐⭐⭐ 分享、推送、广告投放都离不开
重要程度 ⭐⭐⭐⭐⭐ 没有Deep Link,外部分享就是摆设

Deep Link不是可选项。用户从外部进来,能不能一步到位,直接影响留存和转化。你不想让用户在首页迷路吧?

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。