开天辟地 HarmonyOS(鸿蒙) - 跨进程通信: App Linking
开天辟地 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)
}
}