JS卡片开发指导(FA模型)

FA模型从API version 7开始支持,已经不再主推。该应用模型通过导出匿名对象、固定入口文件的方式指定应用组件,开发者无法进行派生,不利于扩展能力。建议使用新的Stage模型进行开发。

接口说明

FormAbility生命周期接口如下:

接口名 描述
onCreate(want: Want): formBindingData.FormBindingData 卡片提供方接收创建卡片的通知接口。
onCastToNormal(formId: string): void 卡片提供方接收临时卡片转常态卡片的通知接口。
onUpdate(formId: string): void 卡片提供方接收更新卡片的通知接口。
onVisibilityChange(newStatus: Record<string, number>): void 卡片提供方接收修改可见性的通知接口。
onEvent(formId: string, message: string): void 卡片提供方接收处理卡片事件的通知接口。
onDestroy(formId: string): void 卡片提供方接收销毁卡片的通知接口。
onAcquireFormState?(want: Want): formInfo.FormState 卡片提供方接收查询卡片状态的通知接口。
onShare?(formId: string): {[key: string]: any} 卡片提供方接收卡片分享的通知接口。
onShareForm?(formId: string): Record<string, Object> 卡片提供方接收卡片分享的通知接口。推荐使用该接口替代onShare接口。如果实现了该接口,onShare将不再被回调。

FormProvider类有如下API接口,具体的API介绍详见@ohos.app.form.formProvider (formProvider)。

接口名 描述
setFormNextRefreshTime(formId: string, minute: number, callback: AsyncCallback<void>): void; 设置指定卡片的下一次刷新时间,使用callback异步回调。
setFormNextRefreshTime(formId: string, minute: number): Promise<void>; 设置指定卡片的下一次刷新时间,以promise方式返回。
updateForm(formId: string, formBindingData: formBindingData.FormBindingData,callback: AsyncCallback<void>): void; 更新指定的卡片,使用callback异步回调。
updateForm(formId: string, formBindingData: FormBindingData): Promise<void>; 更新指定的卡片,以promise方式返回。

FormBindingData类有如下API接口,具体的API介绍详见@ohos.app.form.formBindingData (卡片数据绑定类)。

接口名 描述
createFormBindingData(obj?: Object | string): FormBindingData 创建一个FormBindingData对象。

开发步骤

FA卡片开发,即基于FA模型的卡片提供方开发,主要涉及如下关键步骤:

[h2]实现卡片生命周期接口

创建FA模型的卡片,需实现卡片的生命周期接口。先参考DevEco Studio开发服务卡片指南生成服务卡片模板。

  1. 在form.ts中,导入相关模块

    import type featureAbility from '@ohos.ability.featureAbility';
    import type Want from '@ohos.app.ability.Want';
    import formBindingData from '@ohos.app.form.formBindingData';
    import formInfo from '@ohos.app.form.formInfo';
    import formProvider from '@ohos.app.form.formProvider';
    import dataPreferences from '@ohos.data.preferences';
    import hilog from '@ohos.hilog';
  2. 在form.ts中,实现卡片生命周期接口

    const TAG: string = '[Sample_FAModelAbilityDevelop]';
    const domain: number = 0xFF00;
    
    const DATA_STORAGE_PATH: string = 'form_store';
    let storeFormInfo = async (formId: string, formName: string, tempFlag: boolean, context: featureAbility.Context): Promise<void> => {
      // 此处仅对卡片名:formName, 是否为临时卡片:tempFlag进行了持久化
      let formInfo: Record<string, string | number | boolean> = {
        'formName': formName,
        'tempFlag': tempFlag,
        'updateCount': 0
      };
      try {
        const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
        // put form info
        await storage.put(formId, JSON.stringify(formInfo));
        hilog.info(domain, TAG, `storeFormInfo, put form info successfully, formId: ${formId}`);
        await storage.flush();
      } catch (err) {
        hilog.error(domain, TAG, `failed to storeFormInfo, err: ${JSON.stringify(err as Error)}`);
      }
    };
    
    let deleteFormInfo = async (formId: string, context: featureAbility.Context) => {
      try {
        const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
        // del form info
        await storage.delete(formId);
        hilog.info(domain, TAG, `deleteFormInfo, del form info successfully, formId: ${formId}`);
        await storage.flush();
      } catch (err) {
        hilog.error(domain, TAG, `failed to deleteFormInfo, err: ${JSON.stringify(err)}`);
      }
    }
    
    class LifeCycle {
      onCreate: (want: Want) => formBindingData.FormBindingData = (want) => ({ data: '' });
      onCastToNormal: (formId: string) => void = (formId) => {
      };
      onUpdate: (formId: string) => void = (formId) => {
      };
      onVisibilityChange: (newStatus: Record<string, number>) => void = (newStatus) => {
      };
      onEvent: (formId: string, message: string) => void = (formId, message) => {
      };
      onDestroy: (formId: string) => void = (formId) => {
      };
      onAcquireFormState?: (want: Want) => formInfo.FormState = (want) => (0);
      onShareForm?: (formId: string) => Record<string, Object> = (formId) => {
        let obj: Record<string, number> = {
          'test': 1
        };
        return obj;
      };
    }
    
    let obj: LifeCycle = {
      onCreate(want: Want) {
        hilog.info(domain, TAG, 'FormAbility onCreate');
        if (want.parameters) {
          let formId = String(want.parameters['ohos.extra.param.key.form_identity']);
          let formName = String(want.parameters['ohos.extra.param.key.form_name']);
          let tempFlag = Boolean(want.parameters['ohos.extra.param.key.form_temporary']);
          // 将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用
          // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
          hilog.info(domain, TAG, 'FormAbility onCreate' + formId);
          storeFormInfo(formId, formName, tempFlag, this.context);
        }
    
        // 使用方创建卡片时触发,提供方需要返回卡片数据绑定类
        let obj: Record<string, string> = {
          'title': 'titleOnCreate',
          'detail': 'detailOnCreate'
        };
        let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
        return formData;
      },
      onCastToNormal(formId: string) {
        // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理,当前卡片使用方不存在临时卡片场景
        hilog.info(domain, TAG, 'FormAbility onCastToNormal');
      },
      onUpdate(formId: string) {
        // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
        hilog.info(domain, TAG, 'FormAbility onUpdate');
        let obj: Record<string, string> = {
          'title': 'titleOnUpdate',
          'detail': 'detailOnUpdate'
        };
        let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
        // 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变
        formProvider.updateForm(formId, formData).catch((error: Error) => {
          hilog.error(domain, TAG, 'FormAbility updateForm, error:' + JSON.stringify(error));
        });
      },
      onVisibilityChange(newStatus: Record<string, number>) {
        // 使用方发起可见或者不可见通知触发,提供方需要做相应的处理,仅系统应用生效
        hilog.info(domain, TAG, 'FormAbility onVisibilityChange');
      },
      onEvent(formId: string, message: string) {
        // 若卡片支持触发事件,则需要重写该方法并实现对事件的触发
        let obj: Record<string, string> = {
          'title': 'titleOnEvent',
          'detail': 'detailOnEvent'
        };
        let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
        // 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变
        formProvider.updateForm(formId, formData).catch((error: Error) => {
          hilog.error(domain, TAG, 'FormAbility updateForm, error:' + JSON.stringify(error));
        });
        hilog.info(domain, TAG, 'FormAbility onEvent');
      },
      onDestroy(formId: string) {
        // 删除卡片实例数据
        hilog.info(domain, TAG, 'FormAbility onDestroy');
        // 删除之前持久化的卡片实例数据
        // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
        deleteFormInfo(formId, this.context);
      },
      onAcquireFormState(want: Want) {
        hilog.info(domain, TAG, 'FormAbility onAcquireFormState');
        return formInfo.FormState.READY;
      }
    };
    
    export default obj;

FormAbility不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务。

[h2]配置卡片配置文件

卡片需要在应用配置文件config.json中进行配置。

[h2]卡片信息的持久化

因大部分卡片提供方都不是常驻服务,只有在需要使用时才会被拉起获取卡片信息,且卡片管理服务支持对卡片进行多实例管理,卡片ID对应实例ID,因此若卡片提供方支持对卡片数据进行配置,则需要对卡片的业务数据按照卡片ID进行持久化管理,以便在后续获取、更新以及拉起时能获取到正确的卡片业务数据。且需要适配onDestroy卡片删除通知接口,在其中实现卡片实例数据的删除。

const TAG: string = '[Sample_FAModelAbilityDevelop]';
const domain: number = 0xFF00;

const DATA_STORAGE_PATH: string = 'form_store';
let storeFormInfo = async (formId: string, formName: string, tempFlag: boolean, context: featureAbility.Context): Promise<void> => {
  // 此处仅对卡片名:formName, 是否为临时卡片:tempFlag进行了持久化
  let formInfo: Record<string, string | number | boolean> = {
    'formName': formName,
    'tempFlag': tempFlag,
    'updateCount': 0
  };
  try {
    const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
    // put form info
    await storage.put(formId, JSON.stringify(formInfo));
    hilog.info(domain, TAG, `storeFormInfo, put form info successfully, formId: ${formId}`);
    await storage.flush();
  } catch (err) {
    hilog.error(domain, TAG, `failed to storeFormInfo, err: ${JSON.stringify(err as Error)}`);
  }
};

let deleteFormInfo = async (formId: string, context: featureAbility.Context) => {
  try {
    const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
    // del form info
    await storage.delete(formId);
    hilog.info(domain, TAG, `deleteFormInfo, del form info successfully, formId: ${formId}`);
    await storage.flush();
  } catch (err) {
    hilog.error(domain, TAG, `failed to deleteFormInfo, err: ${JSON.stringify(err)}`);
  }
}

// ...
  onCreate(want: Want) {
    hilog.info(domain, TAG, 'FormAbility onCreate');
    if (want.parameters) {
      let formId = String(want.parameters['ohos.extra.param.key.form_identity']);
      let formName = String(want.parameters['ohos.extra.param.key.form_name']);
      let tempFlag = Boolean(want.parameters['ohos.extra.param.key.form_temporary']);
      // 将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用
      // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
      hilog.info(domain, TAG, 'FormAbility onCreate' + formId);
      storeFormInfo(formId, formName, tempFlag, this.context);
    }

    // 使用方创建卡片时触发,提供方需要返回卡片数据绑定类
    let obj: Record<string, string> = {
      'title': 'titleOnCreate',
      'detail': 'detailOnCreate'
    };
    let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
    return formData;
  },
// ...

let deleteFormInfo = async (formId: string, context: featureAbility.Context): Promise<void> => {
  try {
    const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
    // del form info
    await storage.delete(formId);
    hilog.info(domain, TAG, `deleteFormInfo, del form info successfully, formId: ${formId}`);
    await storage.flush();
  } catch (err) {
    hilog.error(domain, TAG, `failed to deleteFormInfo, err: ${JSON.stringify(err)}`);
  }
};

// ...
    // 适配onDestroy卡片删除通知接口,在其中实现卡片实例数据的删除。
  onDestroy(formId: string) {
    // 删除卡片实例数据
    hilog.info(domain, TAG, 'FormAbility onDestroy');
    // 删除之前持久化的卡片实例数据
    // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
    deleteFormInfo(formId, this.context);
  }
// ...

具体的持久化方法可以参考应用数据持久化概述。

需要注意的是,卡片使用方在请求卡片时传递给提供方应用的Want数据中存在临时标记字段,表示此次请求的卡片是否为临时卡片:

临时卡片转常态卡片:上划卡片应用后,此时会显示的卡片为临时卡片;点击卡片上的“图钉”按钮后添加到桌面,此时卡片转为常态卡片。

由于临时卡片的数据具有非持久化的特殊性,某些场景例如卡片服务框架死亡重启,此时临时卡片数据在卡片管理服务中已经删除,且对应的卡片ID不会通知到提供方,所以卡片提供方需要自己负责清理长时间未删除的临时卡片数据。同时对应的卡片使用方可能会将之前请求的临时卡片转换为常态卡片。如果转换成功,卡片提供方也需要对对应的临时卡片ID进行处理,把卡片提供方记录的临时卡片数据转换为常态卡片数据,防止提供方在清理长时间未删除的临时卡片时,把已经转换为常态卡片的临时卡片信息删除,导致卡片信息丢失。

[h2]卡片数据交互

当卡片应用需要更新数据时(如触发了定时更新或定点更新),卡片应用获取最新数据,并调用updateForm()接口更新主动触发卡片的更新。

const TAG: string = '[Sample_FAModelAbilityDevelop]';
const domain: number = 0xFF00;

onUpdate(formId: string) {
  // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
  hilog.info(domain, TAG, 'FormAbility onUpdate');
  let obj: Record<string, string> = {
    'title': 'titleOnUpdate',
    'detail': 'detailOnUpdate'
  };
  let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
  // 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变
  formProvider.updateForm(formId, formData).catch((error: Error) => {
    hilog.error(domain, TAG, 'FormAbility updateForm, error:' + JSON.stringify(error));
  });
}

[h2]开发卡片页面

开发者可以使用类Web范式(HML+CSS+JSON)开发JS卡片页面。生成如下卡片页面,可以这样配置卡片页面文件:

FA模型当前仅支持JS扩展的类Web开发范式来实现卡片的UI。

[h2]开发卡片事件

卡片支持为组件设置交互事件(action),包括router事件和message事件,其中router事件用于Ability跳转,message事件用于卡片开发人员自定义点击事件。关键步骤说明如下:

  1. 在hml中为组件设置onclick属性,其值对应到json文件的actions字段中。

  2. 如何设置router事件:

    • action属性值为"router";
    • abilityName为跳转目标的Ability名(支持跳转FA模型的PageAbility组件和Stage模型的UIAbility组件),如目前DevEco创建的Stage模型的UIAbility默认名为com.example.entry.EntryAbility;
    • params为传递给跳转目标Ability的自定义参数,可以按需填写。其值可以在目标Ability启动时的want中的parameters里获取。如FA模型PageAbility的onCreate生命周期里可以通过featureAbility.getWant()获取到want,然后在其parameters字段下获取到配置的参数;
  3. 如何设置message事件:

    • action属性值为"message";
    • params为message事件的用户自定义参数,可以按需填写。其值可以在卡片生命周期函数onEvent中的message里获取;

示例如下: