开天辟地 HarmonyOS(鸿蒙) - 跨进程通信: App Linking

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

开天辟地 HarmonyOS(鸿蒙) - 跨进程通信: App Linking

示例如下:

pages\ipc\AppLinkingDemo.ets

/*
 * App Linking 跳转
 * App Linking 在 Deep Linking 的基础上,增加了 https 校验,可以验证 App Linking 地址中域名的合法性
 * App Linking 要求同一地址必须支持网页和应用两种呈现方式,打开 App Linking 地址时有应用则启动应用,无应用则启动浏览器显示网页
 *
 * 注:本例演示的 app linking 的目标应用请参见 harmonydemo2 项目
 *
 * 1、在 app linking 的目标应用的 module.json5 中做类似如下的配置
 * {
 *   "module": {
 *     "abilities": [
 *       {
 *         "skills": [
 *           // 作为 app linking 的目标应用,需要做类似如下的配置
 *           {
 *             // entities 必须配置为 entity.system.browsable
 *             "entities": [
 *               "entity.system.browsable"
 *             ],
 *             // actions 必须配置为 ohos.want.action.viewData
 *             "actions": [
 *               "ohos.want.action.viewData"
 *             ],
 *             // 配置 app linking 的地址,本例为 https://x.y.z
 *             "uris": [
 *               {
 *                 // app linking 的协议名,必须配置为 https
 *                 "scheme": "https",
 *                 // app linking 的域名
 *                 "host": "x.y.z",
 *                 // app linking 的 path,这是可选的,当需要一套域名匹配不同的应用时,则可以通过 path 区分
 *                 "path": ""
 *               }
 *             ],
 *             // domainVerify 必须配置为 true
 *             "domainVerify": true
 *           }
 *         ]
 *       }
 *     ]
 *   }
 * }
 *
 * 2、在 https 域名的固定目录下放置配置文件
 * 地址类似 https://x.y.z/.well-known/applinking.json
 * 配置文件 applinking.json 类似如下(其中的 appIdentifier 是云端为 app 分配的唯一 id)
 * {
 *   "applinking": {
 *     "apps": [
 *       {
 *         "appIdentifier": "1234"
 *       }
 *     ]
 *   }
 * }
 */

import { TitleBar } from '../TitleBar';
import { common, OpenLinkOptions, Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { Helper } from '../../utils/Helper';
import { webview } from '@kit.ArkWeb';

@Entry
@Component
struct AppLinkingDemo {

  build() {
    Column({space:10}) {
      TitleBar()
      Tabs() {
        TabContent() { MySample1() }.tabBar('通过 openLink 跳转').align(Alignment.Top)
        TabContent() { MySample2() }.tabBar('通过 startAbility 跳转').align(Alignment.Top)
        TabContent() { MySample3() }.tabBar('通过 webview 跳转').align(Alignment.Top)
      }
      .scrollable(true)
      .barMode(BarMode.Scrollable)
      .layoutWeight(1)
    }
  }
}

@Component
struct MySample1 {

  @State message: string = ""

  build() {
    Column({space:10}) {

      Text(this.message)

      /*
       * UIAbilityContext
       *   openLink() - 拉起指定 app linking 地址的 app
       *     link - app linking 地址
       *     options - 选项(一个 OpenLinkOptions 对象)
       *       appLinkingOnly - 是否仅以 app linking 的方式打开
       *         true - 仅以 app linking 的方式打开
       *         false - 优先尝试以 app linking 的方式打,然后再以 deep linking 的方式打开
       *       parameters - 传参,目标 app 可以从 want 中获取
       */
      Button('通过 openLink 拉起 app').onClick(() => {
        let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;

        let link: string = "https://x.y.z/api?p1=xyz";
        let openLinkOptions: OpenLinkOptions = {
          appLinkingOnly: false,
          parameters: { // 传参
            'k1': 'v1',
            'k2': `${Helper.getTimestampString()}`,
          }
        };

        context.openLink(link, openLinkOptions).then(() => {
          this.message = 'openLink 成功';
        }).catch((err: BusinessError) => {
          this.message = `openLink 失败 errCode:${err.code}, errMessage:${err.message}`;
        });
      })
    }
  }
}

@Component
struct MySample2 {

  @State message: string = ""

  build() {
    Column({space:10}) {

      Text(this.message)

      /*
       * UIAbilityContext
       *   startAbility() - 拉起指定 want 的 ability
       *
       * Want - 需要拉起的 ability 的相关信息
       *   uri - app linking 地址
       *   parameters - 传参,目标 app 可以从 want 中获取
       */
      Button('通过 startAbility 拉起 app').onClick(() => {
        let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
        let want: Want = {
          uri: "https://x.y.z/api?p1=xyz",
          parameters: { // 传参
            'k1': 'v1',
            'k2': `${Helper.getTimestampString()}`,
          }
        };

        context.startAbility(want).then(() => {
          this.message = 'startAbility 成功';
        }).catch((err: BusinessError) => {
          this.message = `startAbility 失败 errCode:${err.code}, errMessage:${err.message}`;
        });
      })
    }
  }
}

@Component
struct MySample3 {

  @State message: string = ""
  controller: webview.WebviewController = new webview.WebviewController();

  build() {
    Column({space:10}) {

      Text(this.message)

      /*
       * 通过 webview 显示 html,然后拦截跳转请求,如果发现是 app linking 连接,则通过 openLink() 做 app linking 跳转
       */
      Web({ src: $rawfile('AppLinking.html'), controller: this.controller })
        .onLoadIntercept((event) => {
          let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
          const url: string = event.data.getRequestUrl();
          if (url.startsWith("https://x.y.z")) {
            context.openLink(url).then(() => {
              this.message = 'startAbility 成功';
            }).catch((err: BusinessError) => {
              this.message = `startAbility 失败 errCode:${err.code}, errMessage:${err.message}`;
            });
            // 阻止此次跳转
            return true;
          }
          // 允许此次跳转
          return false;
        })
    }
  }
}

\entry\src\main\resources\rawfile\AppLinking.html

<!DOCTYPE HTML>
<html>
    <head>
        <title>app linking</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    </head>
    <body>

        <p>
            <button onclick="openLink()">通过 app linking 跳转至指定的应用</button>
        </p>

        <p>
            <a href="https://x.y.z?p1=xyz">通过 app linking 跳转至指定的应用</a>
        </p>

        <script type="text/javascript">
            function openLink() {
                window.open("https://x.y.z/api?p1=xyz")
            }
        </script>

    </body>
</html>

\harmonydemo2\entry\src\main\module.json5

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "com.webabcd.harmonydemo2.EntryAbility",
    "deviceTypes": [
      "phone",
      "tablet",
      "2in1"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "com.webabcd.harmonydemo2.EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:layered_image",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          },

          // 作为 deep linking 的目标应用,需要做类似如下的配置
          {
            // actions 不能为空,随便配置一个值就好
            "actions": [
              "action.app.scheme.webabcd"
            ],
            // 配置 deep linking 的地址,本例为 webabcd://a.b.c
            "uris": [
              {
                // deep linking 的协议名
                "scheme": "webabcd",
                // deep linking 的域名
                "host": "a.b.c",
              }
            ]
          },

          // 作为 app linking 的目标应用,需要做类似如下的配置
          {
            // entities 必须配置为 entity.system.browsable
            "entities": [
              "entity.system.browsable"
            ],
            // actions 必须配置为 ohos.want.action.viewData
            "actions": [
              "ohos.want.action.viewData"
            ],
            // 配置 app linking 的地址,本例为 https://x.y.z
            "uris": [
              {
                // app linking 的协议名,必须配置为 https
                "scheme": "https",
                // app linking 的域名
                "host": "x.y.z",
                // app linking 的 path,这是可选的,当需要一套域名匹配不同的应用时,则可以通过 path 区分
                "path": ""
              }
            ],
            // domainVerify 必须配置为 true
            "domainVerify": true
          },

          // 作为系统分享的目标应用,需要做类似如下的配置
          {
            "actions": [
              "ohos.want.action.sendData"
            ],
            "uris": [
              {
                // 系统分享的目标应用的协议名,必须配置为 file
                "scheme": "file",
                // 可接收数据的标准化数据类型
                // 分享源分享此 utd 的数据时,当前应用会显示在分享面板中
                "utd": "general.text",
                // 可接收的分享数据的最大数量
                "maxFileSupported": 1
              },
              {
                "scheme": "file",
                "utd": "general.jpeg",
                "maxFileSupported": 1
              }
            ]
          }
        ]
      }
    ]
  }
}

\harmonydemo2\entry\src\main\ets\entryability\EntryAbility.ets

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { systemShare } from '@kit.ShareKit';
import { BusinessError } from '@kit.BasicServicesKit';

export default class Feature1Ability extends UIAbility {

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.parseWantParameter(want)
    this.parseWantShare(want)
  }

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.parseWantParameter(want)
    this.parseWantShare(want)
  }

  // 解析 Want 中的参数,并保存在 AppStorage 中
  parseWantParameter(want: Want) {
    let k1 = ""
    let k2 = ""
    if (want.parameters?.k1) {
      k1 = want.parameters.k1.toString()
    }
    if (want.parameters?.k2) {
      k2 = want.parameters.k2.toString()
    }
    AppStorage.setOrCreate("k1", k1)
    AppStorage.setOrCreate("k2", k2)
    AppStorage.setOrCreate("uri", want.uri ?? "")
  }

  // 解析 Want 中的分享数据,并保存在 AppStorage 中
  parseWantShare(want: Want) {
    /*
     * systemShare.getSharedData() - 获取分享过来的数据(需要在 module.json5 做相关的配置)
     * SharedData - 分享数据(请参见 /HarmonyDemo/entry/src/main/ets/pages/ipc/ShareDemo 中的相关说明)
     *   getRecords() - 获取分享数据中的所有记录
     * SharedRecord - 分享数据中的某条记录
     */
    systemShare.getSharedData(want).then((data: systemShare.SharedData) => {
      data.getRecords().forEach((record: systemShare.SharedRecord) => {
        let title = record.title
        let description = record.description
        let content = record.content
        let uri = record.uri
        AppStorage.setOrCreate("share_data", `title:${title}, description:${description}, content:${content}, uri:${uri}`)
        AppStorage.setOrCreate("share_uri", `${uri}`)
      });
    }).catch((error: BusinessError) => {

    });
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index', (err) => {

    });
  }
}

\harmonydemo2\entry\src\main\ets\pages\Index.ets

/*
 * 本例用于演示 Want 的目标应用
 * 本例用于演示系统分享的目标应用
 */

@Entry
@Component
struct Index {

  @State message: string = '';

  // 分享过来的数据
  @StorageProp('share_data') share_data: string = ''
  // 分享过来的图片地址
  @StorageProp('share_uri') share_uri: string = ''

  onPageShow(): void {
    // 获取 AppStorage 中的数据(本例会在 UIAbility 中解析 Want 中的参数,并保存在 AppStorage 中)
    this.message = `k1: ${AppStorage.get("k1")}\n`
    this.message += `k2: ${AppStorage.get("k2")}\n`
    this.message += `uri: ${AppStorage.get("uri")}\n`
  }

  build() {
    Column({space:10}) {
      Text(this.message)

      Text(`share_data: ${this.share_data}`)

      Image(`${this.share_uri}`).width(50).height(50)
    }
    .justifyContent(FlexAlign.Start)
    .alignItems(HorizontalAlign.Start)
  }
}

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

posted @ 2025-04-15 09:46  webabcd  阅读(117)  评论(0)    收藏  举报