HarmonyOS实战一原子化服务初尝试(ClockFACardDemo学习)

Posted 山河已无恙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HarmonyOS实战一原子化服务初尝试(ClockFACardDemo学习)相关的知识,希望对你有一定的参考价值。

【本文正在参与“有奖征文 | HarmonyOS征文大赛”活动】


写在前面

  • 看到有一个HarmonyOS征文大赛”的活动,所以准备在学习下,拥抱国产操作系统, 之前学HarmonyOS 照着官网写了一个Holle World(关于HarmonyOS的环境搭建,基本目录结构,简单Holle World实现)
  • 博文主题是关于服务卡片,原子化服务的,关于这个,在HarmonyOS 2.0发布会上有看到有讲。
  • 博文由两部分内容构成: 服务卡片(原子化服务)的一些基本概念,官网Demo的代码学习
  • 依旧,把HarmonyOS Developer的地址留在这里,博文好多都是文档里的东西,建议小伙伴看文档学习,对于英语很垃圾的我来讲,这回不用翻译啦,有中文版,没障碍。嗯,博文有理解不对的地方请小伙伴积极留言

时光不能倒流,如果人可以从80岁开始倒过来活的话,人生一定会更加精彩。--------任正非


在最新发布的Harmonyos 2版本的新系统中, "服务卡片"服务成为一大亮点:1

  • 全新的Harmonyos桌面简洁有序,上滑App生成服务卡片,在桌面即可呈现更丰富的信息。
  • 卡片内容实时更新,只需一管即可获取所需信息,省去了打开App的时间。
  • 卡片可大可小、可藏可显,还能够个性化定制,让每个桌面独一无二。
  • 同时,卡片也是原子化服务的载体,在服务中心可以轻松获取随时分享,无需下载安装,一步到位获取各种服务。

嗯,下面开始我们愉快的HarmonyOS----服务卡片之旅吧!

什么是原子化服务

在学习服务卡片之前,我们先来了解一下什么是原子化服务

原子化服务HarmonyOS 提供的一种面向未来服务提供方式,是有独立入口的(用户可通过点击碰一碰扫一扫等方式直接触发)、免安装的(无需显式安装,由系统程序框架后台安装后即可使用)、可为用户提供一个或多个便捷服务的用户程序形态。原子化服务基于 HarmonyOS API 开发,支持运行在1+8+N设备上,供用户在合适的场景、合适的设备上便捷使用。2

例如:某传统方式的需要安装的“购物应用A”,在按照原子化服务理念调整设计后,成为由“商品浏览”“购物车”“支付”等多个便捷服务组成的、可以免安装“购物原子化服务A”。

文档里说的有些官方,个人理解,原子化服务本质还是终端应用(我们先这样理解它,后面在具体描述),一个代替终端应用提供新服务提供方式的存在。抛去服务流转/分享设备控制之类的分布式能力不说,在设计上类似于微信小程序流应用的优点的结合体,在入口设计等方面感觉像微信小程序一样便捷,但是不需要载体(微信,浏览器等),在整体体验方面又像流应用一样,不需要显示安装。但是(体验应该要好于流应用),个人理解不谈分布式能力,像是用瘦客户的方式有了胖客户的体验。

所谓原子化服务,个人理解,即将原来的终端应用功能为粒度细化(原子化),分离成一个个服务,多个服务之间通信完成需求,不在依托于具体的终端应用。类似于一种极限的思想,细化功能粒度,逐渐减少对终端应用总体的依赖。

关于原子化服务的更多内容,小伙伴移步HarmonyOS Developer:什么是原子化服务

原子化服务具有如下基本要素 必须设计开发

  • 基础信息
  • 服务卡片

简单了解一下基础信息:即每个原子化服务有独立的图标名称描述快照基础信息应能够准确反映服务提供方的特征服务的核心体验,并与其他关联应用和服务保持同步最新

其他的没问题,我们重点看一下快照这里快照为与原子化服务关联小尺寸服务卡片截图。截图应为理想的服务状态,让用户一眼可知服务内容。需提供直角图片,由展示快照的应用进行圆角裁切.,
在这里插入图片描述

快照为与原子化服务关联小尺寸服务卡片截图.下面我们看看服务卡片是什么

什么是服务卡片,存在意义。

关于服务卡片,我们看看HarmonyOS Developer中设计文档是怎么讲的。https://developer.harmonyos.com/cn/docs/design/des-guides/service-widget-about-0000001144696239

服务卡片介绍

原子化服务/应用的重要信息卡片的形式展示在桌面,用户可通过快捷手势使用卡片,通过轻量交互行为实现服务直达减少层级跳转的目的。
在这里插入图片描述
这是我的手机的时钟应用的类似的服务卡片的这样一个东西,嗯,这个不算是服务卡片的。和实际的卡片还是有很大差距的.
在这里插入图片描述
这是P40模拟器上的时钟,带有服务卡片选项的,真正的服务卡片

服务卡片核心理念在于提供用户容易使用且一目了然的信息内容,将智慧化能力融入到服务卡片的体验中供用户选择使用,同时满足在不同终端设备上的展示和自适应

服务卡片的构成

服务卡片的显示主要由内容主体归属的 App 名称构成,在临时态下会出现Pin钮的操作特征,点击按钮用户可快捷将·卡片固定·在桌面显示。开发者应该借助卡片内容和卡片名称清晰地向用户传递所要提供的服务信息。

嗯,关于服务卡片的其他这里不多介绍,小伙伴了解更多移步到
官方文档:https://developer.harmonyos.com/cn/docs/design/des-guides/service-widget-about-0000001144696239
关于服务卡片的 交互设计,内容设计,视觉风格 等都有详细讲解。

简单实践-原子化服务之服务卡片初尝试

检验真理的唯一标准是实践 ,说了这么多,编码试一下。先做出点东西来,然后我们在慢慢研究原理。嘻嘻。

官方给出一个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的生命周期回调等知识,小伙伴移步HarmonyOS Developer:https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ability-service-lifecycle-0000000000044472

时钟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学习,这个不在多介绍:https://developer.harmonyos.com/cn/docs/documentation/doc-guides/database-orm-guidelines-0000000000030063

卡片应用初始化:启动卡片定时器服务

在这里插入图片描述

创建、删除卡片

我们想看看官方文档里怎么说。创建一个FormAbility,覆写卡片相关回调函数

  • onCreateForm(Intent intent)
  • onUpdateForm(long formId)
  • onDeleteForm(long formId)
  • onCastTempForm(long formId)
  • onEventNotify(Map<Long, Integer> 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);
        ......
    }
}

了解更多见Java卡片开发指导:https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ability-service-widget-provider-java-0000001104082220

我们看看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_D

以上是关于HarmonyOS实战一原子化服务初尝试(ClockFACardDemo学习)的主要内容,如果未能解决你的问题,请参考以下文章

HarmonyOS实战一原子化服务初尝试(ClockFACardDemo学习)

HarmonyOS实战[二]—超级详细的原子化服务体验[可编辑的卡片交互]快来尝试吧

HarmonyOS实战[三]—可编辑的卡片交互

《HarmonyOS实战—入门到开发,浅析原子化服务》

HarmonyOS实战—亮眼的原子化服务体验

HarmonyOS实战[一]——原理概念介绍安装:基础篇