HarmonyOS开发:应用内搜索App Linking

举报
Jack20 发表于 2026/06/25 20:56:01 2026/06/25
【摘要】 HarmonyOS开发:应用内搜索App Linking📌 核心要点:App Linking不只是深度链接,它是一套完整的链接创建、管理、分享、分析体系——创建跨平台链接、短链分发、链接数据追踪,一个Kit全搞定。 背景与动机你做了一款电商App,运营说:我要在微信分享一个商品链接,用户点开直接到商品详情页。如果用户没装App,先去应用市场下载,装完打开还能跳到那个商品。你一拍脑袋:这不...

HarmonyOS开发:应用内搜索App Linking

📌 核心要点:App Linking不只是深度链接,它是一套完整的链接创建、管理、分享、分析体系——创建跨平台链接、短链分发、链接数据追踪,一个Kit全搞定。

背景与动机

你做了一款电商App,运营说:我要在微信分享一个商品链接,用户点开直接到商品详情页。如果用户没装App,先去应用市场下载,装完打开还能跳到那个商品。

你一拍脑袋:这不就是Deep Link吗?搞个URL Scheme就行了。

等等——URL Scheme在微信里打不开(微信拦截了自定义Scheme),用户没装App时Scheme直接报错,你还得自己搞短链服务、自己统计点击数据、自己处理跨平台兼容……

这就是App Linking存在的意义:它把Deep Link的所有痛点都封装好了,你只管创建链接,剩下的交给它。

App Linking和Deep Link什么关系?Deep Link是技术概念,App Linking是华为提供的完整解决方案。Deep Link是"自己造轮子",App Linking是"直接开现成的车"。

核心原理

App Linking的运作可以拆成四个环节:创建链接 → 分发链接 → 接收链接 → 分析数据

flowchart TD
    A[创建App Link] --> B{链接类型}
    B -->|长链接| C[直接使用Deep Link URL]
    B -->|短链接| D[调用SDK生成短链]
    
    C --> E[分发链接]
    D --> E
    
    E --> F{分发渠道}
    F -->|社交分享| G[微信/微博/短信]
    F -->|广告投放| H[信息流/SEM]
    F -->|邮件/推送| I[营销触达]
    
    G --> J[用户点击链接]
    H --> J
    I --> J
    
    J --> K{App已安装?}
    K -->|| L[直接打开App跳转目标页]
    K -->|| M[跳转应用市场/网页]
    M --> N[安装后首次启动恢复跳转]
    
    L --> O[上报链接点击事件]
    N --> O
    O --> P[AGC控制台查看分析数据]
    
    classDef create fill:#1565C0,color:#fff,stroke:#0D47A1
    classDef distribute fill:#2E7D32,color:#fff,stroke:#1B5E20
    classDef receive fill:#E65100,color:#fff,stroke:#BF360C
    classDef analyze fill:#6A1B9A,color:#fff,stroke:#4A148C

    class A,B,C,D,create
    class E,F,G,H,I,distribute
    class J,K,L,M,N,receive
    class O,P,analyze

链接类型

App Linking支持两种链接格式:

长链接:包含完整的深度链接信息,格式如https://myapp.drcn.agconnect.link/link?deepLink=https%3A%2F%2Fmyapp.com%2Farticle%3Fid%3D123。长,但信息完整,不需要网络请求就能解析。

短链接:格式如https://myapp.drcn.agconnect.link/AbCdEf。短,适合分享,但需要网络请求解析出真实的深度链接。

链接分享与短链

短链是App Linking的核心价值之一。你把一个几百字符的长链丢到短信里,一条短信就满了。短链只有几十个字符,短信、社交媒体、二维码都能轻松承载。

短链的生成过程:客户端或服务端调用App Linking SDK → 传入长链接和配置信息 → AGC服务端生成短链并存储映射关系 → 返回短链URL。

链接数据分析

每个App Link都自带分析能力。用户点击链接、打开App、到达目标页,这些事件都会被自动上报到AGC控制台。你可以看到:

  • 链接点击次数和独立用户数
  • 不同渠道的点击转化率
  • 延迟深度链接的恢复成功率
  • 用户到达目标页的完成率

这些数据对运营来说就是命根子——投了多少钱,来了多少用户,哪个渠道效果好,一目了然。

代码实战

基础用法:创建App Linking

先学会创建链接,包括长链接和短链接两种方式。

// AppLinkCreator.ets
import { appLinking } from '@kit.AppLinkingKit';

export class AppLinkCreator {
  /**
   * 创建长链接
   * 不需要网络请求,本地直接拼接
   */
  static createLongLink(articleId: string, utmSource?: string): string {
    // 构建深度链接——这是用户最终要到达的页面
    const deepLink = `https://myapp.com/article?id=${articleId}`;
    
    // 构建长链接的参数
    const linkBuilder = new appLinking.AppLinkingBuilder();
    linkBuilder.setDeepLink(deepLink);                        // 深度链接
    linkBuilder.setAndroidDeepLink(deepLink);                 // Android端深度链接
    linkBuilder.setHarmonyOSDeepLink(deepLink);               // HarmonyOS端深度链接
    
    // 设置社交分享预览信息
    linkBuilder.setSocialTitle('精彩文章推荐');                 // 分享标题
    linkBuilder.setSocialDescription('点击查看完整内容');        // 分享描述
    linkBuilder.setSocialImageUrl('https://myapp.com/share.png'); // 分享缩略图
    
    // 设置UTM参数,用于渠道归因
    if (utmSource) {
      linkBuilder.setCampaign(utmSource, 'share', 'article_share');
    }
    
    // 设置未安装时的回退行为
    linkBuilder.setFallbackType(appLinking.FallbackType.APP_STORE); // 跳应用市场
    
    // 生成长链接
    const longLink = linkBuilder.buildLongLink();
    console.info(`[AppLinking] 长链接: ${longLink}`);
    
    return longLink;
  }

  /**
   * 创建短链接
   * 需要网络请求,AGC服务端生成
   */
  static async createShortLink(articleId: string, utmSource?: string): Promise<string> {
    const deepLink = `https://myapp.com/article?id=${articleId}`;
    
    const linkBuilder = new appLinking.AppLinkingBuilder();
    linkBuilder.setDeepLink(deepLink);
    linkBuilder.setSocialTitle('精彩文章推荐');
    linkBuilder.setSocialDescription('点击查看完整内容');
    linkBuilder.setSocialImageUrl('https://myapp.com/share.png');
    linkBuilder.setFallbackType(appLinking.FallbackType.APP_STORE);
    
    if (utmSource) {
      linkBuilder.setCampaign(utmSource, 'share', 'article_share');
    }
    
    try {
      // 生成短链接——需要网络请求
      const shortLink = await linkBuilder.buildShortLink();
      console.info(`[AppLinking] 短链接: ${shortLink}`);
      return shortLink;
    } catch (error) {
      console.error(`[AppLinking] 短链接生成失败: ${JSON.stringify(error)}`);
      // 降级:返回长链接
      return linkBuilder.buildLongLink();
    }
  }
}

进阶用法:跨应用链接跳转与链接解析

链接创建好了,怎么在接收端解析和跳转?怎么处理跨应用的链接传递?

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

// 链接路由表——集中管理所有路由规则
interface RouteRule {
  pattern: RegExp;
  targetPage: string;
  paramMapping: Record<string, string>; // URL参数名 → 页面参数名
}

const ROUTE_TABLE: RouteRule[] = [
  {
    pattern: /\/article/,
    targetPage: 'pages/ArticleDetailPage',
    paramMapping: { 'id': 'articleId', 'from': 'fromSource' }
  },
  {
    pattern: /\/product/,
    targetPage: 'pages/ProductDetailPage',
    paramMapping: { 'pid': 'productId', 'sku': 'skuId' }
  },
  {
    pattern: /\/activity/,
    targetPage: 'pages/PromotionPage',
    paramMapping: { 'act_id': 'activityId' }
  },
  {
    pattern: /\/invite/,
    targetPage: 'pages/InvitePage',
    paramMapping: { 'code': 'inviteCode', 'uid': 'inviterId' }
  }
];

export class AppLinkReceiver {
  /**
   * 解析App Linking链接
   * 支持短链和长链两种格式
   */
  static async parseAndRoute(want: Want): Promise<boolean> {
    const uri = want.uri;
    if (!uri) return false;

    console.info(`[AppLinkReceiver] 收到链接: ${uri}`);

    try {
      let deepLink: string = '';

      // 判断是短链还是长链
      if (AppLinkReceiver.isShortLink(uri)) {
        // 短链需要解析
        const linkInfo = await appLinking.parseLink(uri);
        if (linkInfo && linkInfo.deepLink) {
          deepLink = linkInfo.deepLink;
          // 提取社交分享预览信息
          console.info(`[AppLinkReceiver] 标题: ${linkInfo.socialTitle}`);
        } else {
          console.error('[AppLinkReceiver] 短链解析结果为空');
          return false;
        }
      } else {
        // 长链直接使用
        deepLink = uri;
      }

      // 根据路由表跳转
      return AppLinkReceiver.routeByTable(deepLink);
    } catch (error) {
      console.error(`[AppLinkReceiver] 解析失败: ${JSON.stringify(error)}`);
      return false;
    }
  }

  /**
   * 判断是否是App Linking短链
   */
  private static isShortLink(uri: string): boolean {
    return uri.includes('agconnect.link') && uri.length < 100;
  }

  /**
   * 根据路由表跳转
   */
  private static routeByTable(deepLink: string): boolean {
    try {
      const url = new URL(deepLink);
      const path = url.pathname;

      // 匹配路由规则
      for (const rule of ROUTE_TABLE) {
        if (rule.pattern.test(path)) {
          // 根据参数映射构建页面参数
          const pageParams: Record<string, string> = {};
          for (const [urlParam, pageParam] of Object.entries(rule.paramMapping)) {
            const value = url.searchParams.get(urlParam);
            if (value) {
              pageParams[pageParam] = value;
            }
          }

          // 提取UTM参数
          const utmSource = url.searchParams.get('utm_source');
          const utmMedium = url.searchParams.get('utm_medium');
          const utmCampaign = url.searchParams.get('utm_campaign');
          if (utmSource) {
            pageParams['utmSource'] = utmSource;
            pageParams['utmMedium'] = utmMedium || '';
            pageParams['utmCampaign'] = utmCampaign || '';
          }

          // 执行跳转
          router.pushUrl({
            url: rule.targetPage,
            params: pageParams
          });

          console.info(`[AppLinkReceiver] 跳转到: ${rule.targetPage}, 参数: ${JSON.stringify(pageParams)}`);
          return true;
        }
      }

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

  /**
   * 构建跨应用链接
   * 用于从App A跳转到App B的特定页面
   */
  static buildCrossAppLink(targetAppId: string, targetPage: string, params: Record<string, string>): string {
    const queryString = Object.entries(params)
      .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
      .join('&');
    
    return `https://targetapp.drcn.agconnect.link/link?deepLink=${encodeURIComponent(`https://targetapp.com/${targetPage}?${queryString}`)}&appId=${targetAppId}`;
  }
}

完整示例:带数据分析的链接分享系统

把链接创建、分享、解析、数据上报串成完整链路。

// LinkShareSystem.ets
import { appLinking } from '@kit.AppLinkingKit';
import { analytics } from '@kit.AnalyticsKit';

// 分享渠道枚举
enum ShareChannel {
  WECHAT = 'wechat',
  WEIBO = 'weibo',
  SMS = 'sms',
  EMAIL = 'email',
  QR_CODE = 'qrcode',
  COPY_LINK = 'copy'
}

// 分享内容模型
interface ShareContent {
  title: string;
  description: string;
  imageUrl: string;
  deepLink: string;
  channel: ShareChannel;
}

export class LinkShareSystem {
  /**
   * 创建分享链接并记录归因
   */
  static async createShareLink(
    contentType: string,   // 内容类型:article/product/activity
    contentId: string,     // 内容ID
    channel: ShareChannel, // 分享渠道
    extraParams?: Record<string, string>
  ): Promise<string> {
    // 构建深度链接
    const deepLink = LinkShareSystem.buildDeepLink(contentType, contentId, extraParams);
    
    // 创建App Linking
    const linkBuilder = new appLinking.AppLinkingBuilder();
    linkBuilder.setDeepLink(deepLink);
    linkBuilder.setHarmonyOSDeepLink(deepLink);
    
    // 根据内容类型设置分享预览
    const shareInfo = LinkShareSystem.getSharePreview(contentType, contentId);
    linkBuilder.setSocialTitle(shareInfo.title);
    linkBuilder.setSocialDescription(shareInfo.description);
    linkBuilder.setSocialImageUrl(shareInfo.imageUrl);
    
    // 设置渠道归因
    linkBuilder.setCampaign(channel, 'share', `${contentType}_share`);
    
    // 设置回退行为
    linkBuilder.setFallbackType(appLinking.FallbackType.APP_STORE);
    
    // 设置链接有效期(7天)
    linkBuilder.setExpirationHour(168);
    
    try {
      // 生成短链接
      const shortLink = await linkBuilder.buildShortLink();
      
      // 记录链接创建事件
      LinkShareSystem.logLinkCreated(contentType, contentId, channel, shortLink);
      
      return shortLink;
    } catch (error) {
      console.error(`[LinkShare] 短链生成失败: ${JSON.stringify(error)}`);
      // 降级到长链接
      const longLink = linkBuilder.buildLongLink();
      return longLink;
    }
  }

  /**
   * 构建深度链接URL
   */
  private static buildDeepLink(
    contentType: string,
    contentId: string,
    extraParams?: Record<string, string>
  ): string {
    const baseUrl = 'https://myapp.com';
    let path = '';
    const params: Record<string, string> = {};

    switch (contentType) {
      case 'article':
        path = '/article';
        params['id'] = contentId;
        break;
      case 'product':
        path = '/product';
        params['pid'] = contentId;
        break;
      case 'activity':
        path = '/activity';
        params['act_id'] = contentId;
        break;
      default:
        path = '/index';
    }

    // 附加额外参数
    if (extraParams) {
      Object.assign(params, extraParams);
    }

    const queryString = Object.entries(params)
      .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
      .join('&');

    return `${baseUrl}${path}?${queryString}`;
  }

  /**
   * 获取分享预览信息
   */
  private static getSharePreview(
    contentType: string,
    contentId: string
  ): { title: string; description: string; imageUrl: string } {
    // 实际项目中从服务端获取,这里用默认值
    const previews: Record<string, { title: string; description: string; imageUrl: string }> = {
      'article': {
        title: '精彩文章推荐',
        description: '点击查看完整内容',
        imageUrl: 'https://myapp.com/share/article.png'
      },
      'product': {
        title: '好物推荐',
        description: '限时优惠,点击查看',
        imageUrl: 'https://myapp.com/share/product.png'
      },
      'activity': {
        title: '热门活动',
        description: '参与活动赢大奖',
        imageUrl: 'https://myapp.com/share/activity.png'
      }
    };

    return previews[contentType] || previews['article'];
  }

  /**
   * 记录链接创建事件
   */
  private static logLinkCreated(
    contentType: string,
    contentId: string,
    channel: ShareChannel,
    link: string
  ): void {
    console.info(`[LinkShare] 链接已创建: type=${contentType}, id=${contentId}, channel=${channel}`);
    
    // 上报到Analytics
    try {
      analytics.onEvent({
        eventId: 'link_created',
        params: {
          'content_type': contentType,
          'content_id': contentId,
          'share_channel': channel,
          'link_url': link
        }
      });
    } catch (e) {
      console.warn(`[LinkShare] 上报失败: ${e}`);
    }
  }

  /**
   * 记录链接点击事件
   * 在App Linking接收端调用
   */
  static logLinkOpened(
    contentType: string,
    contentId: string,
    utmSource?: string,
    utmCampaign?: string
  ): void {
    console.info(`[LinkShare] 链接已打开: type=${contentType}, id=${contentId}, source=${utmSource}`);
    
    try {
      analytics.onEvent({
        eventId: 'link_opened',
        params: {
          'content_type': contentType,
          'content_id': contentId,
          'utm_source': utmSource || 'unknown',
          'utm_campaign': utmCampaign || 'unknown'
        }
      });
    } catch (e) {
      console.warn(`[LinkShare] 上报失败: ${e}`);
    }
  }
}

// 页面中使用
@Entry
@Component
struct ArticleDetailPage {
  @State articleId: string = '';
  @State shareLink: string = '';

  async aboutToAppear() {
    // 获取页面参数
    const params = router.getParams() as Record<string, string>;
    this.articleId = params?.articleId || '';

    // 记录链接打开事件
    if (params?.utmSource) {
      LinkShareSystem.logLinkOpened(
        'article',
        this.articleId,
        params.utmSource,
        params.utmCampaign
      );
    }
  }

  build() {
    Column() {
      // 文章内容...

      // 分享按钮
      Row() {
        Button('微信分享')
          .onClick(async () => {
            this.shareLink = await LinkShareSystem.createShareLink(
              'article', this.articleId, ShareChannel.WECHAT
            );
            // 调用微信分享SDK...
          })

        Button('复制链接')
          .onClick(async () => {
            this.shareLink = await LinkShareSystem.createShareLink(
              'article', this.articleId, ShareChannel.COPY_LINK
            );
            // 复制到剪贴板...
          })
      }
    }
  }
}

踩坑与注意事项

坑1:短链生成是异步操作,别在UI线程阻塞

buildShortLink()需要网络请求,在主线程调用可能导致ANR。务必用async/await,并且加超时控制。

// 加超时的短链生成
async createShortLinkWithTimeout(builder: appLinking.AppLinkingBuilder, timeoutMs: number = 5000): Promise<string> {
  return Promise.race([
    builder.buildShortLink(),
    new Promise<string>((_, reject) => 
      setTimeout(() => reject(new Error('超时')), timeoutMs)
    )
  ]);
}

坑2:社交分享预览图的大小限制

setSocialImageUrl设置的图片有大小限制:不超过1MB,建议尺寸1200×630px。图片太大加载慢,太小模糊。另外图片必须是HTTPS链接,HTTP的不会显示。

坑3:链接有效期别设太短

setExpirationHour设置链接有效期,默认30天。如果你设了1小时,用户1小时后点链接就打不开了——分享到社交媒体的链接,传播周期远比你想象的长。建议至少7天,活动类链接设30天。

坑4:短链解析失败的处理

短链解析依赖网络。网络不好时parseLink()可能超时或失败。你必须在解析失败时有降级方案——要么跳首页,要么显示一个"正在加载"的过渡页,千万别白屏。

坑5:UTM参数要统一命名

不同渠道的UTM参数命名要统一。微信分享用utm_source=wechat,微博分享用utm_source=weibo,别一个用wx一个用wechat,数据分析时你会哭的。

坑6:AGC控制台的链接前缀配置

App Linking的链接前缀(如myapp.drcn.agconnect.link)在AGC控制台创建项目时自动生成,全局唯一。如果你有多个环境(开发、测试、生产),每个环境需要不同的链接前缀,别搞混了。

HarmonyOS 6适配说明

HarmonyOS 6对App Linking Kit做了以下更新:

  1. 短链域名自定义:之前短链域名只能是*.drcn.agconnect.link格式,HarmonyOS 6支持绑定自定义域名。你可以用link.myapp.com作为短链域名,品牌一致性更好。

  2. 链接预览增强:新增setPreviewType()方法,支持设置链接预览的类型(文章、商品、视频等),社交平台分享时会展示不同样式的预览卡片。

  3. A/B测试支持:App Linking新增了A/B测试能力。同一个短链,不同用户可能跳转到不同的页面,用于测试哪个页面的转化率更高。配合Remote Config的条件变体使用。

  4. 链接安全增强:新增链接签名验证。创建链接时可以设置签名密钥,接收端验证签名后才跳转,防止链接被篡改。

// HarmonyOS 6链接签名验证
const linkBuilder = new appLinking.AppLinkingBuilder();
linkBuilder.setDeepLink(deepLink);
linkBuilder.enableSignatureVerification('your_secret_key'); // 启用签名验证
  1. 分析数据实时化:之前链接点击数据有1-2小时的延迟,HarmonyOS 6实现了近实时上报,延迟降低到5分钟以内。

总结

App Linking是Deep Link的"全副武装版"。你不用自己搞短链服务、不用自己处理跨平台兼容、不用自己搭归因系统,一个Kit全搞定。

核心记住三点:

  • 对外分享必须用短链,长链在短信和社交媒体里根本没法用
  • UTM参数是运营的生命线,每个链接都要带渠道归因信息
  • 链接解析失败必须有降级方案,白屏是最差的用户体验
评估维度 说明
学习难度 ⭐⭐⭐ 创建链接简单,路由表设计和数据分析需要经验
使用频率 ⭐⭐⭐⭐⭐ 分享、推广、广告投放全靠它
重要程度 ⭐⭐⭐⭐⭐ 没有App Linking,外部分享就是"裸奔"

App Linking不是锦上添花,是运营驱动的核心基础设施。你的App要增长,离不开它。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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