学习笔记——HarmonyOS服务卡片开发知识总结 精华
前言
服务卡片的征文活动也已经接近尾声,在这段时间里,论坛里有许多优秀的服务卡片作品和相关的文章涌现。我拜读了专栏中几乎所有的服务卡片的开发分享文章,从每一篇文章中提取并汲取精华,整合到本文中。一些较为显然的介绍性的内容在本文中将会被省略(如卡片有多少种尺寸之类),但是一些即使在很多文章都已经说明过的,而我又认为比较重要的内容,我仍然会再次记录下来(例如卡片的框架)。另外,作为学习笔记,我会记录下一些我在学习种遇到的新的名词的概念,这些概念可能是大家已经熟知的,这部分大家可以直接忽略;文章的内容整合自论坛种的众多文章,所以大家可能会在本文中看到自己文章的影子。
服务卡片的框架
服务卡片整体框架主要包含三部分:卡片使用方、卡片管理服务和卡片提供方。其概念分别如下:
● 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置,如桌面、服务中心、搜索等。
● 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。
● 卡片提供方:提供卡片显示内容的HarmonyOS应用或原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。
卡片的运行框架有如下示意图:
::: hljs-center
简明版示意图
:::
::: hljs-center
详细版示意图
:::
卡片的常用功能
Java卡片与JS卡片功能的对比图
MainAbility的自动生成函数解析
在新建服务卡片的时候,在MainAbility类中将会生成一些回调方法,具体方法及其回调条件如下图,在后面的具体的卡片操作中,也会再次声明所调用的回调方法。
在onCreatForm(Intent)方法中,卡片提供方被拉起后intent会携带卡片相关信息,具体如下:
服务卡片生命周期回调函数时序如下:
配置卡片编辑功能
有些服务卡片需要具备可编辑能力,如天气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)
卡片的定点/定时刷新:将回调updateForm()方法
运作机制示意图如下:
注意:定时刷新和定点刷新都配置的情况下,定时刷新优先。
JS卡片的定点更新步骤:
1.关闭定时更新:updateDuration”修改为0,以关闭定时刷新(config.json文件中)
2.设定“scheduledUpdateTime”的时刻
3.在具体的xxxxlmpl中重写updateFormData()方法
4.把需要刷新的数据存入一个ZSONObject实例中
5.将这个实例封装在一个FormBindingData的实例bindingData中
6.调用MainAbility的方法updateForm(),并将bindingData作为第二个实参。
JS卡片重写updateFormData()方法的代码如下:
JAVA卡片的定点更新步骤:
1.前三步与JS卡片相同
2.构造一个ComponentProvider的实例,用于表示一个Java卡片实例,传入的第一个实参是根据卡片尺寸得到的布局文件。然后,调用方法setText()修改卡片的标题;最后,调用MainAbility的方法updateForm(),并将componentProvider作为第二个实参。
JAVA卡片重写updateFormData()方法的代码如下:
卡片的定时刷新
卡片的定时刷新的最小单位时间是三十分钟,这就带来了两个问题:
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。
卡片的跳转事件:
运作机制示意图如下:
JS卡片的跳转事件步骤
1.在对应卡片的index.json文件中添加跳转配置
相关参数意义具体如下:
2.在index.hml中,在标签image中添加一个属性onclick,并将值设置为刚刚在index.json中定义的action的名称“startSecondAbility”
3.根据key的值“params”获得一个字符串格式的JSON数据;然后,调用ZSONObject.stringToZSON()将其转换为一个ZSONObject的实例data;最后,从data中分别获得”param1”和”param2”这两个key对应的value。
JAVA卡片的跳转事件步骤
1.打开MainWidget3Impl,在方法bindFormData()中,当卡片尺寸为2*4时,调用componentProvider的方法setIntentAgent(),传入的第一个实参是标题在布局文件中的id,第二个实参是用于页面跳转的IntentAgent实例。代码如下所示:
2.定义方法getStartAbilityIntentAgent()的具体实现,代码如下:
总结
Java卡片要通过IntentAgent来设置事件,相比之下JS似乎显得要方便一些。
卡片的消息事件
JS卡片的消息事件步骤
1.在对应卡片的index.json文件中添加信息事件配置
相关参数意义具体如下:
2.重写方法onTriggerFormEvent(),代码如下:
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有效期、安全性、使用范围的可选属性组成。
运作机制如下图:
客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie,客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,
以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
实际就是颁发一个通行证,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。
cookie 可以让服务端程序跟踪每个客户端的访问,但是每次客户端的访问都必须传回这些Cookie,如果 Cookie 很多,这无形地增加了客户端与服务端的数据传输量。
Cookie相关方法
卡片开发的基本步骤总结
1.选择已有的卡片模板,或者自行绘制卡片样式
2.对卡片进行初始化的设置
3.建立数据库(对象关系映射数据库存储卡片信息,涉及网络的可以用轻量级数据库存储cookie
4.给卡片上面的组件添加响应的事件
5.实现卡片的功能
卡片思维导图
摘自:HarmonyOS文档学习 | 服务卡片 - 基础知识 | 思维导图
参考和引用过的部分文章链接
【张荣超老师】鸿蒙卡片开发超细致总结
HarmonyOS文档学习 | 服务卡片 - 基础知识 | 思维导图
龙玄-HarmonyOS服务卡片学习与自发探究|自学笔记
51CTO鸿蒙社区服务卡片应用设计
完整服务卡片项目开发【为Bilibili添加服务卡片】
Codelabs 之 时钟FA卡片开发样例 实践
一文看懂HarmonyOS服务卡片运行原理和开发方法
虽然过程中受益匪浅,但是阅读学习以及整合这么多文章实属是个体力活,希望大家支持!
用心之作
那个cookiestore文档截图错误了,正确应该这个才对https://developer.harmonyos.com/cn/docs/documentation/doc-references/cookiestore-0000001091611340