harmonyos实战一原子化服务初尝试(clockfacarddemo学习)-yb体育官方
写在前面
- 看到有一个
harmonyos征文大赛
”的活动,所以准备在学习下,拥抱国产操作系统, 之前学harmonyos
照着亚博平台下载官网写了一个holle world
- 博文
主题
是关于服务卡片,原子化服务
的,关于这个,在harmonyos 2.0
发布会上有看到有讲。 - 博文由两部分内容构成:
服务卡片(原子化服务)的一些基本概念,
和亚博平台下载官网demo的代码学习
。 - 依旧,把的地址留在这里,博文好多都是文档里的东西,建议小伙伴看文档学习,对于英语很垃圾的我来讲,这回不用翻译啦,有中文版,没障碍。嗯,博文有理解不对的地方请小伙伴积极留言
时光不能倒流,如果人可以从80岁开始倒过来活的话,人生一定会更加精彩。--------任正非
在最新发布的harmonyos 2版本的新系统中, "服务卡片"服务成为一大亮点:
- 全新的
harmonyos
桌面简洁有序,上滑app生成服务卡片,在桌面即可呈现更丰富的信息。- 卡片内容
实时更新
,只需一管即可获取所需信息,省去了打开app的时间。- 卡片可大可小、可藏可显,还能够
个性化定制
,让每个桌面独一无二。- 同时,卡片也是
原子化服务
的载体,在服务中心可以轻松获取
、随时分享
,无需下载
、安装
,一步到位获取各种服务。
嗯,下面开始我们愉快的harmonyos
----服务卡片
之旅吧!
什么是原子化服务
在学习服务卡片
之前,我们先来了解一下什么是原子化服务
原子化服务
是 harmonyos
提供的一种面向未来
的服务提供方式
,是有独立入口
的(用户可通过点击
、碰一碰
、扫一扫
等方式直接触发
)、免安装
的(无需显式安装
,由系统程序框架后台安装
后即可使用)、可为用户提供一个或多个便捷服务
的用户程序形态。原子化服务
基于 harmonyos api
开发,支持运行在1 8 n
设备上,供用户在合适的场景、合适的设备上便捷使用。
例如:某传统方式的需要安装的“购物应用a
”,在按照原子化服务
理念调整设计后,成为由“商品浏览”“购物车”“支付”等多个便捷服务
组成的、可以免安装
的“购物原子化服务a
”。
文档里说的有些官方,个人理解,原子化服务
本质还是终端应用
(我们先这样理解它,后面在具体描述),一个代替终端应用
提供新服务提供方式
的存在。抛去服务流转/分享
、设备控制
之类的分布式能力
不说,在设计
上类似于微信小程序
和流应用
的优点的结合体,在入口设计
等方面感觉像微信小程序
一样便捷,但是不需要载体(微信,浏览器等),在整体体验方面又像流应用
一样,不需要显示安装。但是(体验应该要好于流应用),个人理解不谈分布式能力,像是用瘦客户
的方式有了胖客户
的体验。
所谓原子化服务,个人理解,即将原来的终端应用
以功能为粒度
细化(原子化
),分离成一个个服务
,多个服务之间通信完成需求,不在依托于具体的终端应用。类似于一种极限的思想,细化功能粒度,逐渐减少对终端应用总体的依赖。
关于原子化服务的更多内容,小伙伴移步
原子化服务
具有如下基本要素 必须设计
和开发
:
- 基础信息
- 服务卡片
简单了解一下基础信息
:即每个原子化服务
有独立的图标
、名称
、描述
、快照
。基础信息
应能够准确反映
服务提供方的特征
及服务的核心体验
,并与其他关联
的应用和服务
保持同步最新
。
其他的没问题,我们重点看一下快照
这里:快照
为与原子化服务关联
的小尺寸服务卡片
的截图
。截图应为理想的服务状态
,让用户一眼可知服务内容
。需提供直角图片,由展示快照的应用进行圆角裁切.,
快照
为与原子化服务关联
的小尺寸服务卡片
的截图
.下面我们看看服务卡片
是什么
什么是服务卡片,存在意义。
关于服务卡片
,我们看看harmonyos developer
中设计文档是怎么讲的。
服务卡片介绍
将原子化服务/应用
的重要信息
以卡片
的形式展示
在桌面,用户可通过快捷手势
使用卡片
,通过轻量交互行为
实现服务直达
、减少层级跳转
的目的。
这是我的手机的时钟应用的类似的服务卡片
的这样一个东西,嗯,这个不算是服务卡片的。和实际的卡片还是有很大差距
的.
这是p40模拟器
上的时钟,带有服务卡片选项的,真正的服务卡片
服务卡片
的核心理念
在于提供用户容易使用且一目了然的信息内容,将智慧化能力融入到服务卡片的体验中供用户选择
使用,同时满足在不同终端设备
上的展示和自适应
。
服务卡片的构成
服务卡片的显示主要由内容主体
、归属的 app
名称构成,在临时态下会出现pin钮
的操作特征,点击按钮用户可快捷将·卡片固定·
在桌面显示。开发者应该借助卡片内容和卡片名称清晰地向用户传递所要提供的服务信息。
嗯,关于服务卡片的其他这里不多介绍,小伙伴了解更多移步到
关于服务卡片的 交互设计,内容设计,视觉风格 等都有详细讲解。
简单实践-原子化服务之服务卡片初尝试
检验真理的唯一标准是实践
,说了这么多,编码试一下。先做出点东西来,然后我们在慢慢研究原理。嘻嘻。
官方给出一个java开发时钟卡片
的例子。
- 官方教程文档见
- 代码见
这里,官方的demo
很简单,文档很完整
小伙伴一看就明白。感兴趣的可以拉下来跑跑,我们看看具体的实现成果
主体结构分析:
harmonyos支持应用以ability为单位进行部署。ability
可以分为fa(feature ability)
和pa(particle ability)
两种类型
- fa:
page ability
:由上次的holle word
我们可以知道,在harmonyos
中,提供了ability
和·abilityslice
·两个基础类, 一个有界面
的ability
可以由一个或多个abilityslice
构成,abilityslice
主要用于承载单个页面
的具体逻辑实现
和界面ui
,是应用显示、运行和跳转的最小单元
。所以clockcardslice
为应用的主界面ui.,即这个demo中只有一个page abilityslice(clockcardslice)
。clockcardslice
是通过setmainroute()
方法来指定,指定当前ability
的默认页面。
- pa:
service ability
: 由于时钟是需要实时更新的,所以需要service ability
来实时运行后台任务,即timerability
为时钟更新的service ability
layout文件夹
:页面布局文件夹,由于时钟卡片codelab涉及两个尺寸:22和24,因此需要新建两个.xml文件用于页面布局。
config.json
:配置文件,用于卡片和service ability的声明。
关于 ability
的更多介绍pageability和serviceability的生命周期
回调等知识,小伙伴移步
时钟fa卡片应用主要设计到创建
、更新
和删除
卡片,对象关系映射型数据库
的使用
以及如何启动计时器服务
,卡片布局
等。下面我们就这几部分代码分析,学习。
对象关系映射型数据库的使用
harmonyos
对象关系映射(object relational mapping,orm)数据库是一款基于sqlite(一款轻型的数据库,是遵守acid的关系型数据库管理系统。)
的数据库框架,屏蔽了底层sqlite数据库的sql操作
,针对实体
和关系
提供了增删改查
等一系列的面向对象接口
。应用开发者不必再去编写复杂的sql语句
, 以操作对象
的形式来操作数据
库,提升效率的同时也能聚焦于业务开发。
个人感觉和使用jpa有相同的地方,如果有使用过jpa的,那这个很容易理解。
对象关系映射
数据库的三个
主要组件:
- 数据库:被开发者用
@database
注解,且继承了ormdatabase
的类,对应关系型数据库
。 - 实体对象:被开发者用
@entity
注解,且继承了ormobject
的类,对应关系型数据库中的表
。 - 对象数据操作接口:包括数据库操作的入口
ormcontext类
和谓词接口(ormpredicate
)等。
package com.huawei.cookbooks.database;
import ohos.data.orm.ormobject;
import ohos.data.orm.annotation.entity;
import ohos.data.orm.annotation.primarykey;
/**
* card table 存放对象关系映射数据库相关对象的目录。
*/
// todo 定义一个对象关系映射的数据表
@entity(tablename = "form")
public class form extends ormobject {
// todo 声明主键
@primarykey()
private long formid;
private string formname;
private integer dimension;
public form(long formid, string formname, integer dimension) {
this.formid = formid;
this.formname = formname;
this.dimension = dimension;
}
public form() { }
public integer getdimension() {
return dimension;
}
public void setdimension(integer dimension) {
this.dimension = dimension;
}
public long getformid() {
return formid;
}
public void setformid(long formid) {
this.formid = formid;
}
public string getformname() {
return formname;
}
public void setformname(string formname) {
this.formname = formname;
}
}
package com.huawei.cookbooks.database;
import ohos.data.orm.ormdatabase;
import ohos.data.orm.annotation.database;
/**
* card database 卡片数据库对象,用于创建卡片数据库。
*/
@database(
entities = {form.class},
version = 1)
public abstract class formdatabase extends ormdatabase { }
创建数据库
:开发者需要定义一个表示数据库的类,继承ormdatabase
,再通过@database
注解内的entities
属性指定哪些数据模型类(表)属于这个数据库。- 属性:
version:数据库版本号
。entities:数据库内包含的表
。
- 属性:
创建数据表
。开发者可通过创建一个继承了ormobject
并用@entity
注解的类,获取数据库实体对象,也就是表的对象。tablename:表名
。primarykeys:主键名,一个表里只能有一个主键,一个主键可以由多个字段组成
。foreignkeys:外键列表
。indices:索引列表
建立数据库连接
提供对数据库相关操作的方法
package com.huawei.cookbooks.utils;
import com.huawei.cookbooks.database.form;
import ohos.data.orm.ormcontext;
import ohos.data.orm.ormpredicates;
import java.util.list;
/**
* card database operations 提供对数据库相关操作的方法
*/
public class databaseutils {
/**
* delete data
*
* @param formid form id
* @param connect data connection
*/
public static void deleteformdata(long formid, ormcontext connect) {
ormpredicates where = connect.where(form.class);
where.equalto("formid", formid);
list<form> query = connect.query(where);
if (!query.isempty()) {
connect.delete(query.get(0));
connect.flush();
}
}
/**
* add card info
*
* @param form card object
* @param connect data connection
*/
public static void insertform(form form, ormcontext connect) {
connect.insert(form);
connect.flush();
}
}
对于对象关系映射型数据库的学习,小伙伴移步harmonyos developer学习,这个不在多介绍:
卡片应用初始化:启动卡片定时器服务
创建、删除卡片
我们想看看官方文档里怎么说。创建一个formabilit
y,覆写卡片相关回调函数
。
- oncreateform(intent intent)
- onupdateform(long formid)
- ondeleteform(long formid)
- oncasttempform(long formid)
- oneventnotify(map
formevents)
在oncreateform(intent intent)
中,当卡片使用方
请求获取卡片
时,卡片提供方
会被拉起
并调用oncreateform(intent intent)
回调,intent
中会带有卡片id
,卡片名称
,临时卡片标记
和卡片外观规格信息
,分别通过
- abilityslice.param_form_identity_key、
- abilityslice.param_form_name_key、
- abilityslice.param_form_temorary_key和
- abilityslice.param_form_dimension_key按需获取。
提供方可以通过abilityslice.param_form_customize_key
获取卡片使用方设置的自定义数据。
卡片使用方
:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置
卡片提供方
:提供卡片显示内容的harmonyos service/harmonyos application,控制卡片的显示内容、控件布局以及控件点击事件
public class formability extends ability {
......
@override
public void onstart(intent intent) {
super.onstart(intent);
......
}
@override
protected providerforminfo oncreateform(intent intent) {
long formid = intent.getlongparam(abilityslice.param_form_identity_key, 0);
string formname = intent.getstringparam(abilityslice.param_form_name_key);
int specificationid = intent.getintparam(abilityslice.param_form_dimension_key, 0);
boolean tempflag = intent.getbooleanparam(abilityslice.param_form_temporary_key, false);
// 获取自定义数据
intentparams intentparams = intent.getparam(abilityslice.param_form_customize_key);
hilog.info(label_log, "oncreateform: " formid " " formname " " specificationid);
// 开发者需要根据卡片的名称以及外观规格获取对应的xml布局并构造卡片对象,此处resourcetable.layout_form_ability_layout_2_2仅为示例
providerforminfo forminfo = new providerforminfo(resourcetable.layout_form_ability_layout_2_2, this);
// 获取卡片信息
string formdata = getinitformdata(formname, specificationid);
componentprovider componentprovider = new componentprovider();
componentprovider.settext(resourcetable.id_title, "formdata-" formdata);
forminfo.mergeactions(componentprovider);
......
hilog.info(label_log, "oncreateform finish.......");
return forminfo;
}
@override
protected void ondeleteform(long formid) {
super.ondeleteform(formid);
// 删除卡片实例数据,需要由开发者实现
deleteforminfo(formid);
......
}
@override
// 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要覆写该方法以支持数据更新
protected void onupdateform(long formid) {
super.onupdateform(formid);
// 更新卡片信息,由开发者实现
......
}
@override
protected void oncasttempform(long formid) {
// 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理,将数据持久化。
super.oncasttempform (formid);
......
}
@override
protected void oneventnotify(map<long, integer> formevents) {
// 使用方发起可见或者不可见通知触发,提供方需要做相应的处理,比如卡片可见时刷新卡片,仅系统应用能收到该回调。
super.oneventnotify(formevents);
......
}
}
了解更多见
我们看看demo里怎么写的:覆盖了
ondeleteform(当卡片被删除时,需要重写ondeleteform方法,根据卡片id删除卡片实例数据:)
和oncreateform·(当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用oncreateform回调函数,完成卡片信息的初始化)
package com.huawei.cookbooks;
import com.huawei.cookbooks.database.form;
import com.huawei.cookbooks.database.formdatabase;
import com.huawei.cookbooks.slice.clockcardslice;
import com.huawei.cookbooks.utils.componentproviderutils;
import com.huawei.cookbooks.utils.databaseutils;
import ohos.aafwk.ability.ability;
import ohos.aafwk.ability.abilityslice;
import ohos.aafwk.ability.providerforminfo;
import ohos.aafwk.content.intent;
import ohos.aafwk.content.operation;
import ohos.agp.components.componentprovider;
import ohos.data.databasehelper;
import ohos.data.orm.ormcontext;
import ohos.hiviewdfx.hilog;
import ohos.hiviewdfx.hiloglabel;
/**
* card main ability 主程序入口,由deveco studio生成,开发者需要重写创建、删除卡片等方法。
*/
public class mainability extends ability {
private static final hiloglabel label_log = new hiloglabel(0, 0, "com.huawei.cookbooks.mainability");
// todo 卡片一
private static final int default_dimension_2x2 = 2;
// todo 卡片二
private static final int default_dimension_2x4 = 3;
private static final string empty_string = "";
private static final int invalid_form_id = -1;
private long formid;
// todo 卡片对象
private providerforminfo forminfo;
// todo 数据库服务对象
private databasehelper helper = new databasehelper(this);
// todo 数据库连接对象
private ormcontext connect;
/**
* todo 项目启动方法,启动定时服务
* @param intent
*/
@override
public void onstart(intent intent) {
super.onstart(intent);
// todo 建立数据库连接
connect = helper.getormcontext("formdatabase", "formdatabase.db", formdatabase.class);
// todo 启动timerability
intent intentservice = new intent();
operation operation =
new intent.operationbuilder()
.withdeviceid("")
.withbundlename(getbundlename())
.withabilityname(timerability.class.getname())
.build();
intentservice.setoperation(operation);
startability(intentservice);
super.setmainroute(clockcardslice.class.getname());
}
//当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用oncreateform回调函数,完成卡片信息的初始化
@override
protected providerforminfo oncreateform(intent intent) {
if (intent == null) {
return new providerforminfo();
}
// 获取卡片id
formid = invalid_form_id;
if (intent.hasparameter(abilityslice.param_form_identity_key)) {
formid = intent.getlongparam(abilityslice.param_form_identity_key, invalid_form_id);
} else {
return new providerforminfo();
}
// 获取卡片名称
string formname = empty_string;
if (intent.hasparameter(abilityslice.param_form_name_key)) {
formname = intent.getstringparam(abilityslice.param_form_name_key);
}
// 获取卡片规格
int dimension = default_dimension_2x2;
if (intent.hasparameter(abilityslice.param_form_dimension_key)) {
dimension = intent.getintparam(abilityslice.param_form_dimension_key, default_dimension_2x2);
}
int layoutid = resourcetable.layout_form_image_with_info_date_card_2_2;
if (dimension == default_dimension_2x4) {
layoutid = resourcetable.layout_form_image_with_info_date_card_2_4;
}
forminfo = new providerforminfo(layoutid, this);
// 存储卡片信息
form form = new form(formid, formname, dimension);
componentprovider componentprovider = componentproviderutils.getcomponentprovider(form, this);
forminfo.mergeactions(componentprovider);
if (connect == null) {
connect =
helper.getormcontext("formdatabase", "formdatabase.db", formdatabase.class);
}
try {
databaseutils.insertform(form, connect);
} catch (exception e) {
databaseutils.deleteformdata(form.getformid(), connect);
}
return forminfo;
}
// todo 当卡片被删除时,需要重写ondeleteform方法,根据卡片id删除卡片实例数据:
@override
protected void ondeleteform(long formid) {
super.ondeleteform(formid);
// 删除数据库中的卡片信息
databaseutils.deleteformdata(formid, connect);
}
}
更新卡片
卡片数据服务:为了方便处理·时钟卡片
刷新的定时任务
,我们创建了一个service ability
,定时
去更新
卡片信息,在timerability.java
中
package com.huawei.cookbooks;
import com.huawei.cookbooks.database.form;
import com.huawei.cookbooks.database.formdatabase;
import com.huawei.cookbooks.utils.componentproviderutils;
import com.huawei.cookbooks.utils.databaseutils;
import ohos.aafwk.ability.ability;
import ohos.aafwk.ability.formexception;
import ohos.aafwk.content.intent;
import ohos.agp.components.componentprovider;
import ohos.data.databasehelper;
import ohos.data.orm.ormcontext;
import ohos.data.orm.ormpredicates;
import ohos.hiviewdfx.hilog;
import ohos.hiviewdfx.hiloglabel;
import ohos.rpc.iremoteobject;
import java.util.list;
import java.util.timer;
import java.util.timertask;
/**
* time pa 时钟更新service ability。
*/
public class timerability extends ability {
private static final hiloglabel label_log = new hiloglabel(3, 0xd001100, "demo");
private static final long send_period = 1000l;
private databasehelper helper = new databasehelper(this);
private ormcontext connect;
@override
public void onstart(intent intent) {
hilog.info(label_log, "timerability::onstart");
connect = helper.getormcontext("formdatabase", "formdatabase.db", formdatabase.class);
starttimer();
super.onstart(intent);
}
// 卡片更新定时器,每秒更新一次
private void starttimer() {
timer timer = new timer();
timer.schedule(
new timertask() {
@override
public void run() {
updateforms();
}
},
0,
send_period);
}
private void updateforms() {
// 从数据库中获取卡片信息
ormpredicates ormpredicates = new ormpredicates(form.class);
list<form> formlist = connect.query(ormpredicates);
// 更新时分秒
if (formlist.size() <= 0) {
return;
}
for (form form : formlist) {
// 遍历卡片列表更新卡片
componentprovider componentprovider = componentproviderutils.getcomponentprovider(form, this);
try {
long updateformid = form.getformid();
updateform(updateformid, componentprovider);
} catch (formexception e) {
// 删除不存在的卡片
databaseutils.deleteformdata(form.getformid(), connect);
hilog.error(label_log, "onupdateform updateform error");
}
}
}
@override
public void onbackground() {
super.onbackground();
hilog.info(label_log, "timerability::onbackground");
}
@override
public void onstop() {
super.onstop();
hilog.info(label_log, "timerability::onstop");
}
}
有关卡片组件的更新
,封装了componentproviderutils
这个类,在卡片更新时候,通过调用updateform
方法,传入参数formid
和componentprovider
.
package com.huawei.cookbooks.utils;
import com.huawei.cookbooks.resourcetable;
import com.huawei.cookbooks.database.form;
import ohos.agp.components.componentprovider;
import ohos.agp.utils.color;
import ohos.app.context;
import java.util.calendar;
/**
* component providerutils 提供获取componentprovider对象的方法,用于卡片组件的更新。
*/
public class componentproviderutils {
// 当前星期颜色
private static color nowweekcolor = new color(color.rgb(255, 245, 238));
// 原色星期
private static color primaryweekcolor = new color(color.rgb(192, 192, 192));
private static final int week_days = 7;
private static final int string_length = 2;
private static final int dim_version = 3;
private static final int sunday = 1;
private static final int monday = 2;
private static final int tuesday = 3;
private static final int wednesday = 4;
private static final int thursday = 5;
private static final int friday = 6;
private static final int saturday = 7;
/**
* obtain the day of the week
*
* @return week
*/
public static int getweekdayid() {
calendar calendar = calendar.getinstance();
int week = calendar.get(calendar.day_of_week);
int result = getweekidresult(week);
return result;
}
/**
* get week component id
*
* @param week week
* @return component id
*/
private static int getweekidresult(int week) {
int result = resourcetable.id_mon;
switch (week) {
case sunday:
result = resourcetable.id_sun;
break;
case monday:
result = resourcetable.id_mon;
break;
case tuesday:
result = resourcetable.id_tue;
break;
case wednesday:
result = resourcetable.id_wed;
break;
case thursday:
result = resourcetable.id_thu;
break;
case friday:
result = resourcetable.id_fri;
break;
case saturday:
result = resourcetable.id_sat;
break;
default:
result = resourcetable.id_sun;
break;
}
return result;
}
/**
* obtains the componentprovider object
*
* @param form form info
* @param context context
* @return component provider
*/
public static componentprovider getcomponentprovider(form form, context context) {
int layoutid = resourcetable.layout_form_image_with_info_date_card_2_2;
if (form.getdimension() == dim_version) {
layoutid = resourcetable.layout_form_image_with_info_date_card_2_4;
}
componentprovider componentprovider = new componentprovider(layoutid, context);
setcomponentprovidervalue(componentprovider);
return componentprovider;
}
/**
* time converted to string
*
* @param time time
* @return time string
*/
private static string int2string(int time) {
string timestring;
if (string.valueof(time).length() < string_length) {
timestring = "0" time;
} else {
timestring = time "";
}
return timestring;
}
/**
* set the value of componentprovider
*
* @param componentprovider component provider
*/
private static void setcomponentprovidervalue(componentprovider componentprovider) {
calendar now = calendar.getinstance();
int hour = now.get(calendar.hour_of_day);
int min = now.get(calendar.minute);
int second = now.get(calendar.second);
string hourstring = int2string(hour);
string minstring = int2string(min);
string secondstring = int2string(second);
componentprovider.settext(resourcetable.id_date, dateutils.getcurrentdate("yyyy-mm-dd"));
componentprovider.settext(resourcetable.id_hour, hourstring);
componentprovider.settext(resourcetable.id_min, minstring);
componentprovider.settext(resourcetable.id_sec, secondstring);
// 获取当前星期
int weekdayid = getweekdayid();
componentprovider.settextcolor(weekdayid, nowweekcolor);
// 将前一天的星期改回原色
int lastweekid = getlastweekdayid();
componentprovider.settextcolor(lastweekid, primaryweekcolor);
}
/**
* obtain previous day of the week
*
* @return previous day of the week
*/
public static int getlastweekdayid() {
calendar calendar = calendar.getinstance();
int week = calendar.get(calendar.day_of_week);
int lastweek;
if (week == 1) {
lastweek = week_days;
} else {
lastweek = week - 1;
}
return getweekidresult(lastweek);
}
}
对于服务卡片我们就先学的这里,学完了demo
,小伙伴是不是蠢蠢欲动呢?自己写一个类似的demo应该是在可能完成的范围之内
的,感兴趣的小伙伴赶快试试吧。之后我准备基于官方的一个demo(一个音频播放器),搞一个服务卡片。嗯,时间关系,今天就先搞到这里。
- 点赞
- 收藏
- 关注作者
评论(0)