学习笔记——HarmonyOS服务卡片开发知识总结 精华

木棉花Gan_Susan
发布于 2021-8-14 09:54
浏览
4收藏

前言

服务卡片的征文活动也已经接近尾声,在这段时间里,论坛里有许多优秀的服务卡片作品和相关的文章涌现。我拜读了专栏中几乎所有的服务卡片的开发分享文章,从每一篇文章中提取并汲取精华,整合到本文中。一些较为显然的介绍性的内容在本文中将会被省略(如卡片有多少种尺寸之类),但是一些即使在很多文章都已经说明过的,而我又认为比较重要的内容,我仍然会再次记录下来(例如卡片的框架)。另外,作为学习笔记,我会记录下一些我在学习种遇到的新的名词的概念,这些概念可能是大家已经熟知的,这部分大家可以直接忽略;文章的内容整合自论坛种的众多文章,所以大家可能会在本文中看到自己文章的影子。

服务卡片的框架

服务卡片整体框架主要包含三部分:卡片使用方、卡片管理服务和卡片提供方。其概念分别如下:
● 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置,如桌面、服务中心、搜索等。
● 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。
● 卡片提供方:提供卡片显示内容的HarmonyOS应用或原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。
卡片的运行框架有如下示意图:

学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
::: hljs-center

简明版示意图

:::

学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
::: hljs-center

详细版示意图

:::

卡片的常用功能

Java卡片与JS卡片功能的对比图

学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区

MainAbility的自动生成函数解析

在新建服务卡片的时候,在MainAbility类中将会生成一些回调方法,具体方法及其回调条件如下图,在后面的具体的卡片操作中,也会再次声明所调用的回调方法。
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
在onCreatForm(Intent)方法中,卡片提供方被拉起后intent会携带卡片相关信息,具体如下:
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
服务卡片生命周期回调函数时序如下:
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区

配置卡片编辑功能

有些服务卡片需要具备可编辑能力,如天气App需要编辑所在城市。具体实现方法如下:在config.json中,对某一个form的配置增加formConfigAbility的属性配置,可实现编辑功能。formConfigAbility的值是一个url格式的Ability名称。若不配置formConfigAbility,则不显示编辑菜单。示例代码如下:(摘抄自[一文看懂HarmonyOS服务卡片运行原理和开发方法])(https://dl-harmonyos.51cto.com/images/202106/830a107135e5fa06fa1714ebaa9fa523833da2.jpg?x-oss-process=image/resize,w_1080,h_478)
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区

卡片的定点/定时刷新:将回调updateForm()方法

运作机制示意图如下:
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
注意:定时刷新和定点刷新都配置的情况下,定时刷新优先。

JS卡片的定点更新步骤:

1.关闭定时更新:updateDuration”修改为0,以关闭定时刷新(config.json文件中)
2.设定“scheduledUpdateTime”的时刻
3.在具体的xxxxlmpl中重写updateFormData()方法
4.把需要刷新的数据存入一个ZSONObject实例中
5.将这个实例封装在一个FormBindingData的实例bindingData中
6.调用MainAbility的方法updateForm(),并将bindingData作为第二个实参。
JS卡片重写updateFormData()方法的代码如下:
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区

JAVA卡片的定点更新步骤:

1.前三步与JS卡片相同
2.构造一个ComponentProvider的实例,用于表示一个Java卡片实例,传入的第一个实参是根据卡片尺寸得到的布局文件。然后,调用方法setText()修改卡片的标题;最后,调用MainAbility的方法updateForm(),并将componentProvider作为第二个实参。
JAVA卡片重写updateFormData()方法的代码如下:
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区

卡片的定时刷新

卡片的定时刷新的最小单位时间是三十分钟,这就带来了两个问题:
1.创建卡片后的最初的三十分钟是不会进行卡片刷新的,所以需要进行手动的刷新。
2.如果是编写刷新间隔小于三十分的卡片(如时钟卡片每一秒刷新一次),那么需要自己重新创建一个timer实例来设定刷新时间。
添加手动刷新:用onCreateForm()调用onUpdateForm(formId)即可,代码摘抄自亮子力的哔哩哔哩卡片

@Override
protected ProviderFormInfo onCreateForm(Intent intent) {
    ... ...

	//初始化时先在线更新一下卡片
    onUpdateForm(formId);

    return formController.bindFormData();
}

重新编写刷新时间的代码为代码摘抄自Codelabs 之 时钟FA卡片开发样例

private void startTimer() {
        Timer timer = new Timer();
        timer.schedule(
                new TimerTask() {
                    @Override
                    public void run() {
                        updateForms();
                    }
                },
                0,
                SEND_PERIOD);
    }
private void updateForms() {
        // 从数据库中获取卡片信息
        HiLog.info(LABEL, "从数据库中获取卡片");
        OrmPredicates ormPredicates = new OrmPredicates(Form.class);
        List<Form> formList = connect.query(ormPredicates);
        int layoutId=0;
        // 更新时分秒
        if (formList.size() <= 0) {
            return;
        }
        HiLog.info(LABEL, "遍历卡片列表,逐个更新");
        for (Form form : formList) {
            // 遍历卡片列表更新卡片
            ComponentProvider componentProvider = ComponentProviderUtils.getComponentProvider(form, this);
            try {
                Long updateFormId = form.getFormId();
                HiLog.info(LABEL, "updateForm,更新卡片[:"+updateFormId+"] 的数据,并非数据库操作" );
                //更新卡片数据
                boolean isSucc=updateForm(updateFormId, componentProvider);
                HiLog.info(LABEL, "更新卡片数据完成,"+isSucc);
            } catch (FormException e) {
                // 删除不存在的卡片
                DatabaseUtils.deleteFormData(form.getFormId(), connect);
                HiLog.error(LABEL, "更新卡片异常,删除数据库中不存在的卡片");
            }
        }
    }

JAVA与JS卡片定点定时更新的对比总结

由上可见,两者的大体思路是相同的:即先在config.json文件中修改配置,然后对具体的xxxxlmpl中的updateFormData()进行重写。不同的是,JS卡片式通过创建一个ZSONOject实例,然后封装在FormBindingData的实例中的办法更新数据;而JAVA卡片则是通过创建ComponentProvider的实例,通过它来更新数据。因而,在调用MainAbility的方法updateForm()的时候,JS卡片的第二个实参是bingding Data,而JAVA卡片是componentProvider。

卡片的跳转事件:

运作机制示意图如下:
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区

JS卡片的跳转事件步骤

1.在对应卡片的index.json文件中添加跳转配置
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
相关参数意义具体如下:
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
2.在index.hml中,在标签image中添加一个属性onclick,并将值设置为刚刚在index.json中定义的action的名称“startSecondAbility”
3.根据key的值“params”获得一个字符串格式的JSON数据;然后,调用ZSONObject.stringToZSON()将其转换为一个ZSONObject的实例data;最后,从data中分别获得”param1”和”param2”这两个key对应的value。学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区

JAVA卡片的跳转事件步骤

1.打开MainWidget3Impl,在方法bindFormData()中,当卡片尺寸为2*4时,调用componentProvider的方法setIntentAgent(),传入的第一个实参是标题在布局文件中的id,第二个实参是用于页面跳转的IntentAgent实例。代码如下所示:学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
2.定义方法getStartAbilityIntentAgent()的具体实现,代码如下:
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区

总结

Java卡片要通过IntentAgent来设置事件,相比之下JS似乎显得要方便一些。

卡片的消息事件

JS卡片的消息事件步骤

1.在对应卡片的index.json文件中添加信息事件配置
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
相关参数意义具体如下:
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
2.重写方法onTriggerFormEvent(),代码如下:
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
message是一个字符串格式的JSON数据;然后,调用ZSONObject.stringToZSON()将message转换为一个ZSONObject的实例data;最后,从data中分别获得”p1”和”p2”这两个key对应的value。

服务卡片常用到的数据库

数据持久化

概念:数据持久化就是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称
好处:
1、程序代码重用性强,即使更换数据库,只需要更改配置文件,不必重写程序代码。
2、业务逻辑代码可读性强,在代码中不会有大量的SQL语言,提高程序的可读性。
3、持久化技术可以自动优化,以减少对数据库的访问量,提高程序运行效率。
数据持久化对象的基本操作有:保存、更新、删除、查询等。

轻量级偏好数据库

轻量级数据库官方文档链接

轻量级偏好数据库在服务卡片开发中的使用场景

根据轻量级偏好数据库的特点,在服务卡片的开发中,涉及到保存网络上的内容时,可以使用轻量级偏好数据库来存储cookie信息。

轻量级偏好数据库的创建(获取preference实例)

 context = getContext();
 databaseHelper = new DatabaseHelper(context);
 filename = "preference_db";//文件名,不能为空,也不能包含路径
 preferences = databaseHelper.getPreferences(filename);

轻量级偏好数据库的插入,删除,修改,查询

插入与修改:
首先获取指定文件对应的Preferences实例,然后借助Preferences API将数据写入Preferences实例,通过flush或者flushSync将Preferences实例持久化。其中flush为异步地写入,flushSync为同步写入。flush()会立即更改内存中的Preferences对象,但会将更新异步写入磁盘。flushSync()更改内存中的数据的同时会将数据同步写入磁盘。由于flushSync()是同步的,建议不要从主线程调用它,以避免界面卡顿。
异步写入代码:

 preferences.putInt("number",number);
 preferences.putString("fruit","apple");
 preferences.flush();

同步写入的代码:

preferences.putInt("intKey", 3);
preferences.putString("StringKey", "String value");
bool result = preferences.flushSync();

查询:

int value = preferences.getInt("intKey", 0);

删除:
1.删除preferen单实例

DatabaseHelper databaseHelper = new DatabaseHelper(context);
String fileName = "name"; // fileName表示文件名,其取值不能为空,也不能包含路径。
databaseHelper.removePreferencesFromCache(fileName);

2.删除指定文件

DatabaseHelper databaseHelper = new DatabaseHelper(context);
String fileName = "name"; // fileName表示文件名,其取值不能为空,也不能包含路径。
boolean result = databaseHelper.deletePreferences(fileName);

注册观察者

private class PreferencesObserverImpl implements Preferences.PreferencesObserver {
   
    @Override    
    public void onChange(Preferences preferences, String key) {
        if ("intKey".equals(key)) {
           HiLog.info(LABLE, "Change Received:[key=value]");        
        }    
    }
}

// 向preferences实例注册观察者
PreferencesObserverImpl observer = new PreferencesObserverImpl();
preferences.registerObserver(observer);
// 修改数据
preferences.putInt("intKey", 3);
preferences.flush();
// 修改数据后,observer的onChange方法会被回调
// 向preferences实例注销观察者
preferences.unRegisterObserver(observer);

轻量级数据库在服务卡片中的使用例子:(摘抄自亮子力的哔哩哔哩卡片

    /**
     * Map[保存]到偏好型数据库
     * @param preferences  数据库的Preferences实例
     * @param map  要保存的map
     */
    public static void SaveMap(Preferences preferences,Map<String,String> map){
        // 遍历map
        for (Map.Entry<String, String> entry : map.entrySet()) {
            HiLog.info(TAG,entry.getKey() + "=" + entry.getValue());
            preferences.putString(entry.getKey(),entry.getValue());//3.将数据写入Preferences实例,
        }
        preferences.flushSync();//4.通过flush()或者flushSync()将Preferences实例持久化。
    }

    /**
     *  从偏好型数据库[读取]Map
     * @param preferences  数据库的Preferences实例
     * @return  要读取的map
     */
    public static Map<String,?> GetCookieMap(Preferences preferences){
        Map<String, ?> map = new HashMap<>();
        map = preferences.getAll();//3.读取数据
        return map;
    }

对象关系映射数据库

对象关系映射数据库官方文档

对象关系数据库在服务卡片中的应用

对象关系数据库在服务卡片中主要用作存储卡片的信息,如卡片id,卡片名字,卡片大小等。

对象关系数据库的创建

1.配置“build.gradle”文件,这里仅展示使用注解处理器的模块为“com.huawei.ohos.hap”模块,则需要在模块的“build.gradle”文件的ohos节点中添加以下配置:

compileOptions{        
    annotationEnabled true    
} 

2.构造对象关系映射数据库:创建数据库类并配置对应的属性

//数据库有三个表,User,Book, AllDataType三个表,版本号为1
@Database(entities = {User.class, Book.class, AllDataType.class}, version = 1) 
public abstract class BookStore extends OrmDatabase { 
}

3.构造数据表,即创建数据库实体类并配置对应的属性(如对应表的主键,外键等)。数据表必须与其所在的数据库在同一个模块中。

@Entity(tableName = "user", ignoredColumns = {"ignoredColumn1", "ignoredColumn2"},
    indices = {@Index(value = {"firstName", "lastName"}, name = "name_index", unique = true)}) 
public class User extends OrmObject { 
    // 此处将userId设为了自增的主键。注意只有在数据类型为包装类型时,自增主键才能生效。
    @PrimaryKey(autoGenerate = true) 
    private Integer userId;   
    private String firstName;   
    private String lastName;   
    private int age;   
    private double balance;   
    private int ignoredColumn1; 
    private int ignoredColumn2; 
 
    // 需添加各字段的getter和setter方法。 
}

4.使用对象数据操作接口OrmContext创建数据库。

// context入参类型为ohos.app.Context,注意不要使用slice.getContext()来获取context,请直接传入slice,否则会出现找不到类的报错。
DatabaseHelper helper = new DatabaseHelper(this); 

OrmContext context = helper.getOrmContext("BookStore", "BookStore.db", BookStore.class); 

5.对象关系映射数据库的增删改查
查询数据:

OrmPredicates query = context.where(User.class).equalTo("lastName", "San"); 
List<User> users = context.query(query);

更新数据:更新数据有两种方法,这里仅贴出其中一种

// 更新数据
OrmPredicates predicates = context.where(User.class);
predicates.equalTo("age", 29);
List<User> users = context.query(predicates);
User user = users.get(0);
user.setFirstName("Li");
context.update(user);
context.flush();

删除数据:删除数据也有两种方法,这里也仅贴出其中一种,另一种方法可以到官方文档查看

// 删除数据
OrmPredicates predicates = context.where(User.class);
predicates.equalTo("age", 29);
List<User> users = context.query(predicates);
User user = users.get(0);
context.delete(user);
context.flush();

6.注册观察者

// 定义一个观察者类。
private class CustomedOrmObjectObserver implements OrmObjectObserver {
    @Override    
    public void onChange(OrmContext changeContext, AllChangeToTarget subAllChange) {
        // 用户可以在此处定义观察者行为
    }
}

// 调用registerEntityObserver方法注册一个观察者observer。
CustomedOrmObjectObserver observer = new CustomedOrmObjectObserver();
context.registerEntityObserver("user", observer);

// 当以下方法被调用,并flush成功时,观察者observer的onChange方法会被触发。其中,方法的入参必须为User类的对象。
public <T extends OrmObject> boolean insert(T object)
public <T extends OrmObject> boolean update(T object)
public <T extends OrmObject> boolean delete(T object)

对象关系映射数据库在服务卡片中的应用实例

@Entity(tableName = "form")
public class Form extends OrmObject {
    @PrimaryKey()
    // 卡片id
    private Long formId;

    // 卡片名称
    private String formName;

    // 卡片名称
    private int dimension;

    /**
     * 有参构造
     *
     * @param formId 卡片id
     * @param formName 卡片名
     * @param dimension 卡片规格
     */
    public Form(Long formId, String formName, int dimension) {
        this.formId = formId;
        this.formName = formName;
        this.dimension = dimension;
    }
protected ProviderFormInfo onCreateForm(Intent intent) {
        HiLog.info(TAG, "onCreateForm");
        long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
        String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
        int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
        HiLog.info(TAG, "onCreateForm: formId=" + formId + ",formName=" + formName);
        FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
        FormController formController = formControllerManager.getController(formId);
        formController = (formController == null) ? formControllerManager.createFormController(formId,
                formName, dimension) : formController;
        if (formController == null) {
            HiLog.error(TAG, "Get null controller. formId: " + formId + ", formName: " + formName);
            return null;
        }

        //初始化时先在线更新一下卡片
        onUpdateForm(formId);

        // TODO 获取添加到桌面的服务卡片ID

        // 存储卡片信息。对象关系映射数据库2.通过对象数据操作接口OrmContext,创建一个别名为“FormDatabase”,数据库文件名为“FormDatabase.db”的数据库
        ormContext = databaseHelper.getOrmContext("FormDatabase", "FormDatabase.db", MyOrmDatabase.class);
        // 对象关系映射数据库3.实例化数据表
        HiLog.info(TAG, "存储卡片信息: formId=" + formId + ",formName=" + formName + ",dimension=" + dimension);
        Form form = new Form(formId, formName, dimension);
        // 对象关系映射数据库4.添加方法
        ormContext.insert(form);
        ormContext.flush();


        return formController.bindFormData();
    }

网络编程的相关知识

这部分在我平时的学习未曾接触过,在大家眼中很平常的概念,在我这里都是全新的知识,所以会有较多的概念记录。

WebView组件的使用

WebView组件官方文档
WebView提供在应用中集成Web页面的能力。
服务卡片的开发中一般只需要加载Web页面,则常用操作如下:
1.配置应用的网络权限(如未配置网络权限的话)

{
  ...
  "module": {
    ...
    "reqPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ],
    ...
  }
}

2.使用load方法加载Web页面
这里仅贴出使用xml方式布局的代码,使用代码方式布局可参看官方文档

WebView webView = (WebView) findComponentById(ResourceTable.Id_webview);
webView.getWebConfig().setJavaScriptPermit(true);  // 如果网页需要使用JavaScript,增加此行;如何使用JavaScript下文有详细介绍  
final String url = EXAMPLE_URL; // EXAMPLE_URL由开发者自定义
webView.load(url);

什么是JSON文件

JSON的全称为JavaScript Object Notation,是轻量级的文本数据交换格式,JSON实际上是JavaScript的一个子集。重要的是,许多API请求返回值都是josn格式的结果。

JSON生成Java实体类

网上有很多相关的工具,如何自己编写我暂且还未学会,就决定先应用现有的工具了

什么是Cookie

由于HTTP是一种无状态协议,服务器没有办法单单从网络连接上面知道访问者的身份,因此就使用Cookie作为辨识访问者的一种标志。
Cookie是一段不超过4KB的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制Cookie有效期、安全性、使用范围的可选属性组成。
运作机制如下图:
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie,客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,
以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
实际就是颁发一个通行证,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。
cookie 可以让服务端程序跟踪每个客户端的访问,但是每次客户端的访问都必须传回这些Cookie,如果 Cookie 很多,这无形地增加了客户端与服务端的数据传输量。

Cookie相关方法

CookieStore官方文档
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区
学习笔记——HarmonyOS服务卡片开发知识总结-鸿蒙开发者社区

卡片开发的基本步骤总结

1.选择已有的卡片模板,或者自行绘制卡片样式
2.对卡片进行初始化的设置
3.建立数据库(对象关系映射数据库存储卡片信息,涉及网络的可以用轻量级数据库存储cookie
4.给卡片上面的组件添加响应的事件
5.实现卡片的功能

卡片思维导图

摘自:HarmonyOS文档学习 | 服务卡片 - 基础知识 | 思维导图

参考和引用过的部分文章链接

【张荣超老师】鸿蒙卡片开发超细致总结
HarmonyOS文档学习 | 服务卡片 - 基础知识 | 思维导图
龙玄-HarmonyOS服务卡片学习与自发探究|自学笔记
51CTO鸿蒙社区服务卡片应用设计
完整服务卡片项目开发【为Bilibili添加服务卡片】
Codelabs 之 时钟FA卡片开发样例 实践
一文看懂HarmonyOS服务卡片运行原理和开发方法

虽然过程中受益匪浅,但是阅读学习以及整合这么多文章实属是个体力活,希望大家支持!

已于2021-8-14 09:54:33修改
6
收藏 4
回复
举报
2条回复
按时间正序
/
按时间倒序
鸿联
鸿联

用心之作

回复
2021-8-15 15:05:55
vsrrrrrb
vsrrrrrb

那个cookiestore文档截图错误了,正确应该这个才对https://developer.harmonyos.com/cn/docs/documentation/doc-references/cookiestore-0000001091611340

回复
2021-10-19 23:31:14
回复
    相关推荐