如何隔空传送打开App Linking链接直达应用
=========================

#### 问题现象

设备A如何使用隔空传送的传递App Linking链接直达设备B打开？

#### 背景知识

*   [App Linking Kit（应用链接服务）](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/applinking-introduction)：通过App Linking应用链接拉起指定应用，实现应用间跳转。当应用已安装时，优先通过应用展示内容；若应用未安装，则通过系统浏览器展示网页版内容。
*   [隔空传送](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/gestures-share-overview)：用户通过“一抓一放”手势实现跨设备文件分享（图片、视频、文档等）以及跨设备链接分享。

#### 解决方案

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/da/v3/j8to7OJKRSW34eT2-8UMxA/zh-cn_image_0000002664282537.png?HW-CC-KV=V1&HW-CC-Date=20260709T134316Z&HW-CC-Expire=86400&HW-CC-Sign=ECE50086EC8296B84297456BA46E0036FC72629CD52FBB62EFF21F88A3A84FBC "点击放大")

实现跨设备链接隔空传送分享流程如上，其中A设备应用为宿主应用，B设备应用为目标应用。需要分别实现目标方和宿主方应用配置。

**目标应用配置：**

1.  关联的网址域名的配置。目标应用侧需要在[AppGallery Connect](https://developer.huawei.com/consumer/cn/service/josp/agc/index.html)上完成关联的网址域名的配置，具体步骤参考[目标方应用配置应用链接能力](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-linking-startupapp#配置应用链接能力)并[在AGC为应用创建关联的网址域名](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-linking-startupapp#在agc为应用创建关联的网址域名)。
2.  在应用module.json5的skills中配置应用直达链接域名。App Linking链接格式通常为：scheme://host/path。具体步骤参考[在module.json5中配置关联的网址域名](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-linking-startupapp#在modulejson5中配置关联的网址域名)，代码设置如下：
    
    {
      "entities": \[
        _// entities必须包含"entity.system.browsable"_
        "entity.system.browsable"
      \],
      "actions": \[
       _ // actions必须包含"ohos.want.action.viewData"_
        "ohos.want.action.viewData"
      \],
      "uris": \[
        {
       _   // scheme须配置为https_
          "scheme": "https",
          _// host须配置为关联的网址域名，实际运行时替换为真实的App Linking链接域名_
          "host": "www.example.com",
      _    // path可选，表示域名服务器上的目录或文件路径，例如www.example.com/path1中的path1_
          _// 如果应用只能处理部分特定的path，则此处应该配置应用所支持的path，避免出现应用不能处理的path链接也被引流到应用中的问题_
          "path": "path1"
        }
      \],
      _// domainVerify须设置为true_
      "domainVerify": true
    }
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/2e/v3/s5vVLjlwSkOYeeHboF0XOw/notice_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134316Z&HW-CC-Expire=86400&HW-CC-Sign=27D43CA8A67EC53F45B605BB35BD7D57E1432806535A2806689C397CD9982B0A)
    
    *   path、pathStartWith、pathRegex的取值前后均不需要加斜杠/。
    *   建议scheme和host中不要包含大写字母。
    
3.  在UIAbility的onCreate()方法和onNewWant()方法中接收处理收到的分享链接，代码如下：
    
    onCreate(want: Want): void {
      try {
        this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR\_MODE\_NOT\_SET);
      } catch (err) {
        hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
      }
      hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
      let uri = want?.uri;
      if (uri) {
        let urlObject = url.URL.parseURL(uri);
        let index = urlObject.params.get('index') as string;
        AppStorage.setOrCreate('GesturesShare\_shareIndex', 'Result:' + index);
        AppStorage.setOrCreate('GesturesShare\_isShareLink', true);
        hilog.info(DOMAIN, 'testTag', 'EntryAbility onCreate invoked. uri: %{public}s', uri);
      }
    }
    
    onNewWant(want: Want): void {
      let uri = want?.uri;
      if (uri) {
        let urlObject = url.URL.parseURL(uri);
        let index = urlObject.params.get('index') as string;
        AppStorage.setOrCreate('GesturesShare\_shareIndex', 'Result:' + index);
        AppStorage.setOrCreate('GesturesShare\_isShareLink', true);
        hilog.info(DOMAIN, 'testTag', 'EntryAbility onCreate invoked. uri: %{public}s', uri);
      }
    }
    
    在Page页面显示接收的分享内容：
    
    @StorageLink('GesturesShare\_isShareLink') isShareLink: boolean = false;
    @StorageLink('GesturesShare\_shareIndex') shareResult: string = '隔空投送分享AppLinking';
    
    build() {
      RelativeContainer() {
        Text(this.shareResult)
          .fontSize('35fp')
          .fontWeight(FontWeight.Bold)
          .alignRules({
            center: { anchor: '\_\_container\_\_', align: VerticalAlign.Center },
            middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
          })
      }
      .height('100%')
      .width('100%')
    }
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/53/v3/NWzlyCHQTzuIJMgwN_cjgg/notice_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134316Z&HW-CC-Expire=86400&HW-CC-Sign=26D621446AFD4C10A6B4D6D8EE538D10E1F613C5EA4F740FE47BE0541F1D96C5)
    
    不能使用DevEco Studio的自动签名功能，必须使用[手动签名](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-signing#section297715173233)，否则无法拉起应用。
    

**宿主应用配置：**

1.  构造华为分享事件触发后的回调[SharableTarget](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/share-harmony-share#section9147845115614)。设置utd类型为utd.UniformDataType.HYPERLINK,表示分享内容为链接。分享链接通过目标方应用关联的网址域名和自定义参数拼接而成。代码如下：
    
    private immersiveCallback = (sharableTarget: harmonyShare.SharableTarget) => {
      let filePath = this.context.filesDir + '/gestures\_immersive.png'; _// 仅为示例 请替换正确的文件路径_
      let shareData: systemShare.SharedData = new systemShare.SharedData({
        utd: utd.UniformDataType.HYPERLINK,
        content: \`App Linking链接?index=${this.shareIndex}\`, _// 须替换为真实的App Linking链接，拼接自定义参数。_
        thumbnailUri: fileUri.getUriFromPath(filePath),
        title: '隔空传送分享卡片标题',
        description: '隔空传送分享卡片描述'
      });
      sharableTarget.share(shareData);
      this.shareIndex++;
    };
    
2.  进入页面时在[onPageShow()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-custom-component-lifecycle#onpageshow)中使用[harmonyShare.on('gesturesShare')](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/share-harmony-share#ongesturesshare)方法注册隔空传送监听事件，退出页面或者进入后台时在[onPageHide()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-custom-component-lifecycle#onpagehide)中使用[harmonyShare.off('gesturesShare')](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/share-harmony-share#offgesturesshare)方法取消隔空传送监听事件。
    
    onPageShow(): void {
      this.immersiveListening();
    }
    
    onPageHide(): void {
      this.immersiveDisablingListening();
    }
    
    private immersiveListening() {
      harmonyShare.on('gesturesShare', this.immersiveCallback);
    }
    
    private immersiveDisablingListening() {
      harmonyShare.off('gesturesShare', this.immersiveCallback);
    }
    

_**完整代码:**_

EntryAbility：

import { ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { url } from '@kit.ArkTS';

const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
  onCreate(want: Want): void {
    try {
      this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR\_MODE\_NOT\_SET);
    } catch (err) {
      hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
    }
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
    let uri = want?.uri;
    if (uri) {
      let urlObject = url.URL.parseURL(uri);
      let index = urlObject.params.get('index') as string;
      AppStorage.setOrCreate('GesturesShare\_shareIndex', 'Result:' + index);
      AppStorage.setOrCreate('GesturesShare\_isShareLink', true);
      hilog.info(DOMAIN, 'testTag', 'EntryAbility onCreate invoked. uri: %{public}s', uri);
    }
  }

  onNewWant(want: Want): void {
    let uri = want?.uri;
    if (uri) {
      let urlObject = url.URL.parseURL(uri);
      let index = urlObject.params.get('index') as string;
      AppStorage.setOrCreate('GesturesShare\_shareIndex', 'Result:' + index);
      AppStorage.setOrCreate('GesturesShare\_isShareLink', true);
      hilog.info(DOMAIN, 'testTag', 'EntryAbility onCreate invoked. uri: %{public}s', uri);
    }
  }


  onDestroy(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
  _  // Main window is created, set main page for this ability_
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }

  onWindowStageDestroy(): void {
    _// Main window is destroyed, release UI related resources_
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground(): void {
   _ // Ability has brought to foreground_
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
   _ // Ability has back to background_
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

Index.ets:

import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { systemShare, harmonyShare } from '@kit.ShareKit';
import { fileUri, fileIo as fs } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

@Entry
@Component
struct Index {
  private context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
  private shareIndex: number = 0;

  aboutToAppear(): void {
    try {
      let filePath = this.context.filesDir + '/gestures\_immersive.png'; /_/ 仅为示例 请替换正确的文件路径_
      let file = fs.openSync(filePath, fs.OpenMode.READ\_WRITE | fs.OpenMode.CREATE);
      let writeLen = fs.writeSync(file.fd,
        (this.context.resourceManager.getMediaContentSync($r('app.media.gestures\_immersive').id) as Uint8Array).buffer);
      console.info('write data to file succeed and size is:' + writeLen);
      fs.closeSync(file);
    } catch (error) {
      console.error(\`getMediaContentSync error. Code: ${error?.code}, message: ${error?.message}\`);
    }
  }

  private immersiveCallback = (sharableTarget: harmonyShare.SharableTarget) => {
    let filePath = this.context.filesDir + '/gestures\_immersive.png'; _// 仅为示例 请替换正确的文件路径_
    let shareData: systemShare.SharedData = new systemShare.SharedData({
      utd: utd.UniformDataType.HYPERLINK,
      content: \`App Linking链接?index=${this.shareIndex}\`, _// 须替换为真实的App Linking链接，拼接自定义参数。_
      thumbnailUri: fileUri.getUriFromPath(filePath),
      title: '隔空传送分享卡片标题',
      description: '隔空传送分享卡片描述'
    });
    sharableTarget.share(shareData);
    this.shareIndex++;
  };

  onPageShow(): void {
    this.immersiveListening();
  }

  onPageHide(): void {
    this.immersiveDisablingListening();
  }

  private immersiveListening() {
    harmonyShare.on('gesturesShare', this.immersiveCallback);
  }

  private immersiveDisablingListening() {
    harmonyShare.off('gesturesShare', this.immersiveCallback);
  }

  @StorageLink('GesturesShare\_isShareLink') isShareLink: boolean = false;
  @StorageLink('GesturesShare\_shareIndex') shareResult: string = '隔空投送分享AppLinking';

  build() {
    RelativeContainer() {
      Text(this.shareResult)
        .fontSize('35fp')
        .fontWeight(FontWeight.Bold)
        .alignRules({
          center: { anchor: '\_\_container\_\_', align: VerticalAlign.Center },
          middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
        })
    }
    .height('100%')
    .width('100%')
  }
}