开天辟地 HarmonyOS(鸿蒙) - 卡片: FormLink(为静态卡片提供与应用交互的能力)
开天辟地 HarmonyOS(鸿蒙) - 卡片: FormLink(为静态卡片提供与应用交互的能力)
示例如下:
pages\widget\FormLinkDemo.ets
/*
* FormLink - 为静态卡片提供与应用交互的功能
*/
import { TitleBar } from '../TitleBar';
@Entry
@Component
struct FormLinkDemo {
@State message: string = "FormLink 的演示请参见:\n" +
"/entryformability/EntryFormAbility.ets\n" +
"/widget/pages/FormLinkCard.ets" +
"/entryability/EntryAbility.ets"
build() {
Column({space:10}) {
TitleBar()
Text(this.message)
}
}
}
\entry\src\main\ets\entryformability\EntryFormAbility.ets
/*
* 卡片对应的 extension ability
* 用于管理卡片的生命周期,以及和卡片做数据交互
*/
import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
import { Helper } from '../utils/Helper';
import { MyLog } from '../utils/MyLog';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';
export default class EntryFormAbility extends FormExtensionAbility {
// 将指定的资源文件复制到指定的沙箱目录
copyFile(src: Resource, dst: string) {
let resourceManager = this.context.getApplicationContext().resourceManager
let buffer = resourceManager.getMediaContentSync(src.id).buffer
let outputStream = fs.createStreamSync(dst, 'w+');
let writeLength = outputStream.writeSync(buffer, {
offset: 0,
length: buffer.byteLength
})
outputStream.closeSync();
}
// 卡片创建时的回调
onAddForm(want: Want) {
// 通过 want.parameters 可以获取卡片的规格,以及宽和高等
MyLog.d(`onAddForm ${JSON.stringify(want.parameters)}`)
// FormExtensionContext - FormExtensionAbility 的上下文(在 FormExtensionAbility 内,可以通过 this.context 获取 FormExtensionContext 对象)
let context = this.context
let abilityName = context.extensionAbilityInfo.name
MyLog.d(`extension ability name: ${abilityName}`)
// 将指定的资源文件复制到指定的沙箱目录
// 如果需要卡片显示一个网络图片,则可以先将图片下载到沙箱目录(注:卡片每次活过来后,最多在后台存在 5 秒),然后再参照本例后续的方法
let imagePath = this.context.getApplicationContext().filesDir + '/icon.png'
this.copyFile($r('app.media.app_icon'), imagePath)
// 创建一个保存多张图片的字典表,用于在卡片中显示图片
// key 代表图片的标识
// value 代表图片的文件描述符,打开文件到内存后,把文件描述符传给卡片,然后卡片再根据文件描述符显示内存中的图片
let formImages: Record<string, number> = {};
let file = fs.openSync(imagePath);
formImages['myImage_0'] = file.fd; // 注意:如果图片更新了,则这里需要指定一个和之前不同的 key 以便卡片可以显示更新后的图片
/*
* formBindingData.createFormBindingData() - 创建一个 FormBindingData 对象
* formBindingData.FormBindingData - 需要传递给卡片的数据
* 此对象中的字段的值,可以在卡片中通过 @LocalStorageProp 引用
*/
let formData: Record<string, string | Record<string, number>> = {
'formId': `${want.parameters!['ohos.extra.param.key.form_identity']}`, // 卡片 id
'dimension': `${want.parameters!['ohos.extra.param.key.form_dimension']}`, // 卡片的规格(1代表1*2, 2代表2*2, 3代表2*4, 4代表4*4, 7代表6*4)
'width': `${want.parameters!['ohos.extra.param.key.form_width']}`, // 卡片的宽
'height': `${want.parameters!['ohos.extra.param.key.form_height']}`, // 卡片的高
'content': Helper.getTimestampString(), // 卡片中可以通过 @LocalStorageProp('content') 引用此值
'myImage': 'myImage_0', // 指定在 formImages 中的指定 key 的图片,然后在卡片中可以通过 @LocalStorageProp('myImage') 引用此图片
'formImages': formImages // 保存多张图片的字典表
};
return formBindingData.createFormBindingData(formData);
}
// 刷新时会执行 onUpdateForm() 回调
// 当时间符合 scheduledUpdateTime 或 updateDuration 的条件时会触发刷新
// 当系统语言或深色浅色模式发生变化时也会触发刷新
// 卡片每次活过来后,最多在后台存在 5 秒
onUpdateForm(formId: string) {
MyLog.d(`onUpdateForm: ${formId}`)
/*
* formBindingData.createFormBindingData() - 创建一个 FormBindingData 对象
* formBindingData.FormBindingData - 需要传递给卡片的数据
* 此对象中的字段的值,可以在卡片中通过 @LocalStorageProp 引用
* formProvider.updateForm() - 更新指定的卡片
* formId - 卡片 id
* formBindingData - 为卡片绑定的 FormBindingData 对象
* formProvider.setFormNextRefreshTime() - 指定下一次刷新的时间(注:卡片每天最多刷新 50 次)
* formId - 卡片 id
* minute - 此时间后刷新(单位:分钟),最短为 5 分钟
*/
let formData: Record<string, string> = {
'content': Helper.getTimestampString() // 卡片中可以通过 @LocalStorageProp('content') 引用此值
};
let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo)
try {
// 设置过 5 分钟后刷新卡片
formProvider.setFormNextRefreshTime(formId, 5, (err: BusinessError) => {
})
} catch (err) {
}
}
// 卡片通过 message 的方式传递数据时,会触发此回调
onFormEvent(formId: string, message: string) {
MyLog.d(`onFormEvent: ${formId}, ${message}`) // 此处的 message 参数就是 message 方式传递过来的数据
// 更新指定的卡片
let formData: Record<string, string> = {
'content': Helper.getTimestampString() // 卡片中可以通过 @LocalStorageProp('content') 引用此值
};
let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo)
}
// 卡片销毁时的回调
onRemoveForm(formId: string) {
MyLog.d(`onRemoveForm: ${formId}`)
}
};
\entry\src\main\ets\widget\pages\FormLinkCard.ets
/*
* FormLink - 为静态卡片提供与应用交互的能力
*/
import { Helper } from '../../utils/Helper'
@Entry
@Component
struct FormLinkCard {
@LocalStorageProp('content') content: string = ''
/*
* FormLink - 为静态卡片提供与应用交互的能力
* bundleName - bundle 的名称
* moduleName - module 的名称
* abilityName - ability 的名称
* uri - deep linking 或 app linking 地址
* params - 需要传递的数据
* action - 交互方式
* router - 拉起目标应用
* 在目标应用的 onCreate() 或 onNewWant() 中通过 Want 获取卡片传递给应用的数据
* 在目标应用中通过 formProvider.updateForm() 将数据传递给卡片
* message - 触发 FormExtensionAbility 的 onFormEvent 回调
* 在 FormExtensionAbility 的 onFormEvent 回调中通过 message 参数获取卡片传递过来的数据
* 在 FormExtensionAbility 的 onFormEvent 回调中通过 formProvider.updateForm() 将数据传递给卡片
* call - 拉起目标应用,只能是冷启动的方式,且不会出现在最近任务列表中
* 在目标应用的 onCreate() 中通过 Want 获取卡片传递给应用的数据
* 如果目标应用已经启动了,则可以通过 Caller/Callee 的方式将数据从卡片传递给应用
* 在 params 中必须要传递名为 method 的参数,其用于作为 Caller/Callee 之间约定的消息通知字符串
* 在目标应用中通过 this.callee.on() 监听指定的消息通知字符串,如果监听到,则可以获取卡片传递给目标应用的数据
* 在目标应用中通过 formProvider.updateForm() 将数据传递给卡片
* 目标应用必须具有 ohos.permission.KEEP_BACKGROUND_RUNNING 权限(参见 module.json5 中的相关说明)
*/
build() {
Column() {
// 通过 router 和 deep linking 的方式拉起指定的应用(目标应用为 /harmonydemo2 项目)
// 关于 deep linking 和 app linking 请参见 /ipc/DeepLinkingDemo.ets 和 /ipc/AppLinkingDemo.ets 中的说明
FormLink({
action: 'router',
uri: "webabcd://a.b.c/api?p1=xyz",
params: {
'k1': 'v1',
'k2': `${Helper.getTimestampString()}`,
}
}) {
Column().backgroundColor(Color.Red).width('100%').height('100%')
}
.width('100%').layoutWeight(1)
// 通过 router 和 abilityName 的方式拉起指定的应用
// params 中的数据可以在目标应用的 onCreate() 或 onNewWant() 中通过 Want 获取到,详见 /entryability/EntryAbility.ets 中的说明
// 目标应用中通过 formProvider.updateForm() 将数据传递给卡片,详见 /entryability/EntryAbility.ets 中的说明
FormLink({
action: 'router',
bundleName: 'com.webabcd.harmonydemo',
moduleName: 'entry',
abilityName: 'com.webabcd.harmonydemo.EntryAbility',
params: {
'k1': 'v1',
'k2': `${Helper.getTimestampString()}`,
}
}) {
Column().backgroundColor(Color.Green).width('100%').height('100%')
}
.width('100%').layoutWeight(1)
// 通过 message 的方式触发 FormExtensionAbility 的 onFormEvent 回调
// params 中的数据可以在 onFormEvent 回调中获取到,详见 /entryformability/EntryFormAbility.ets 中的说明
// 在 onFormEvent 回调中通过 formProvider.updateForm() 将数据传递给卡片,详见 /entryformability/EntryFormAbility.ets 中的说明
FormLink({
action: 'message',
params: {
'k1': 'v1',
'k2': `${Helper.getTimestampString()}`,
}
}) {
Column() {
Text(this.content).fontColor(Color.White)
}.backgroundColor(Color.Blue).width('100%').height('100%').justifyContent(FlexAlign.Center)
}
.width('100%').layoutWeight(1)
// 通过 call 和 abilityName 的方式拉起指定的应用(params 中必须要包含名为 method 的参数)
// 目标应用未启动时,则会拉起目标应用,但是目标应用不会出现在最近任务列表中,此时 params 中的数据可以在目标应用的 onCreate() 中通过 Want 获取到,详见 /entryability/EntryAbility.ets 中的说明
// 目标应用已启动时,则通过 Caller/Callee 的方式将数据从卡片传递给应用(前提是目标应用注册了监听,详见 /entryability/EntryAbility.ets 中的说明)
// 目标应用中通过 formProvider.updateForm() 将数据传递给卡片,详见 /entryability/EntryAbility.ets 中的说明
// 目标应用必须具有 ohos.permission.KEEP_BACKGROUND_RUNNING 权限
FormLink({
action: 'call',
abilityName: 'com.webabcd.harmonydemo.EntryAbility',
params: {
'method': 'myMethod', // 如需 call 方式生效,则必须要有 method 参数,其会作为 Caller/Callee 之间约定的消息通知字符串
'k1': 'v1',
'k2': `${Helper.getTimestampString()}`,
}
}) {
Column().backgroundColor(Color.Orange).width('100%').height('100%')
}
.width('100%').layoutWeight(1)
}
.width('100%')
}
}
\entry\src\main\ets\entryability\EntryAbility.ets
/*
* UIAbility - 用于为应用提供绘制界面的窗口
* UIAbility 的相关配置,请参见 module.json5 配置文件中的 abilities 标签
*/
import { AbilityConstant, Configuration, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { MyLog } from '../utils/MyLog';
import { formBindingData, formInfo, formProvider } from '@kit.FormKit';
import { Helper } from '../utils/Helper';
import { rpc } from '@kit.IPCKit';
export default class EntryAbility extends UIAbility {
// 用于保存通过卡片的 call 的方式拉起了 ability 时,传递过来的名为 method 的参数
private cardCallMethod = ""
// UIAbility 实例创建完成时(冷启动)
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
MyLog.d(`ability onCreate, parameters:${JSON.stringify((want.parameters))}`);
this.handleCard(want)
}
// 当 UIAbility 已经启动了,之后再次启动时(热启动)
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
MyLog.d(`ability onNewWant, parameters:${JSON.stringify((want.parameters))}`);
this.handleCard(want)
}
// 当通过卡片的 router 或 call 方式拉起了此 ability 时
handleCard(want: Want) {
// 有 method 参数,所以是 call 方式拉起的
// 当卡片通过 call 的方式调用此应用时:
// 1、此应用未启动时,则会拉起此应用,可以在 onCreate() 中通过 Want 获取到卡片传递过来的数据,但是此应用不会出现在最近任务列表中
// 2、此应用已启动时,则在 callee.on() 中通过 data.readString() 获取卡片传递过来的数据
// 3、此应用必须具有 ohos.permission.KEEP_BACKGROUND_RUNNING 权限
if (want.parameters && want.parameters["method"] !== undefined) {
this.cardCallMethod = want.parameters["method"].toString(); // 通过卡片的 call 的方式拉起了 ability 时,肯定有名为 method 的参数
// 监听 Callee 与 Caller 约定的消息通知字符串(即通过卡片的 call 的方式拉起了 ability 时,其中的名为 method 的参数)
// 注:本例是在 call 方式拉起应用时监听的,如果不是 call 方式拉起的则监听不到,要避免此问题,则别管谁拉起的都按约定值监听即可
this.callee.on(this.cardCallMethod, (data: rpc.MessageSequence) => {
// 通过 data.readString() 可以获取卡片通过 call 方式传递过来的数据
MyLog.d(`this.callee.on, ${this.cardCallMethod}, ${data.readString()}}`)
// 1 秒后更新卡片的信息
setTimeout(() => {
let formId = want.parameters!['ohos.extra.param.key.form_identity'].toString();
let formData: Record<string, string> = {
'content': Helper.getTimestampString()
};
let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo)
}, 1000)
// 这里必须要返回一个 rpc.Parcelable 对象(空的 rpc.Parcelable 对象即可)
return new MyParcelable();
});
}
// 有 ohos.extra.param.key.form_identity 参数,所以是 router 或 call 方式拉起的
if (want.parameters && want.parameters['ohos.extra.param.key.form_identity'] !== undefined) {
// 获取卡片 id 并更新卡片的信息(关于这部分的说明,请参见 /entryformability/EntryFormAbility.ets)
let formId = want.parameters['ohos.extra.param.key.form_identity'].toString();
let formData: Record<string, string> = {
'content': Helper.getTimestampString()
};
let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo)
}
}
// UIAbility 实例销毁时
onDestroy(): void {
MyLog.d('ability onDestroy');
if (this.cardCallMethod != '') {
// 取消监听
this.callee.off(this.cardCallMethod)
}
}
// UIAbility 的窗口创建完成时
// 在这里需要通过 windowStage.loadContent() 加载当前 UIAbility 的首页
onWindowStageCreate(windowStage: window.WindowStage): void {
MyLog.d('ability onWindowStageCreate');
let record: Record<string, number> = {
'myNumber': 5000
};
let storage: LocalStorage = new LocalStorage(record);
// 加载当前 UIAbility 的首页
// 必须是 @Entry 组件
// 必须在 main_pages.json 文件(在 module.json5 中通过 "pages":"$profile:main_pages" 指定的)中声明
// 这里指定的 LocalStorage 对象的数据,会自动合并到当前 UIAbility 的 LocalStorage.getShared() 中
windowStage.loadContent('pages/Index', storage, (err) => {
if (err.code) {
MyLog.d(`windowStage.loadContent() failed, ${JSON.stringify(err)}`)
return;
}
MyLog.d(`windowStage.loadContent() succeeded`)
});
}
// UIAbility 的窗口销毁之前
onWindowStageWillDestroy(windowStage: window.WindowStage) {
MyLog.d('ability onWindowStageWillDestroy');
}
// UIAbility 的窗口销毁时
onWindowStageDestroy(): void {
MyLog.d('ability onWindowStageDestroy');
}
// 切换到前台时
onForeground(): void {
MyLog.d('ability onForeground');
}
// 切换到后台时
onBackground(): void {
MyLog.d('ability onBackground');
}
// 内存占用级别发生变化时的回调
// 注:在 AbilityStage 中也有此回调
onMemoryLevel(level: AbilityConstant.MemoryLevel): void {
// MEMORY_LEVEL_MODERATE - 内存占用适中
// MEMORY_LEVEL_LOW - 内存占用低
// MEMORY_LEVEL_CRITICAL - 内存占用高
MyLog.d(`ability onMemoryLevel ${level}`);
}
// 全局的环境配置发生变化时的回调(比如系统语言,深色浅色模式等)
// 注:在 AbilityStage 中也有此回调
onConfigurationUpdate(newConfig: Configuration): void {
MyLog.d(`ability onConfigurationUpdate ${JSON.stringify(newConfig)}`);
}
}
class MyParcelable implements rpc.Parcelable {
num: number;
str: string;
constructor()
constructor(num: number, str: string)
constructor(num?: number, str?: string) {
this.num = num ?? 0;
this.str = str ?? "";
}
marshalling(messageSequence: rpc.MessageSequence): boolean {
messageSequence.writeInt(this.num);
messageSequence.writeString(this.str);
return true;
}
unmarshalling(messageSequence: rpc.MessageSequence): boolean {
this.num = messageSequence.readInt();
this.str = messageSequence.readString();
return true;
}
}