#HarmonyOS征文#HarmonyOS实战—亮眼的原子化服务体验 - HarmonyOS技术社区 - 电子技术论坛 - 广受欢迎的专业电子论坛
分享 收藏 返回

[文章]

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

`他来了他来了,他脚踏祥云走来了。

就像是一声初春的惊雷,响彻在华夏大地,预示着一个崭新的国产化的时代的到来。

我的民族自豪感也油然而生。谁说国产的不香呢,相信以后国产化也一样可以做到No.1,做到引领潮流,引领世界。

随着国际环境的变化,今后的国产化是是个大的趋势。咱们的芯片、半导体、软件系统等领域也好消息不断。

2021年6月2日晚20:00,华为HarmonyOS 2及华为全场景新品发布会如期召开。发布会上,重磅发布了万众瞩目的HarmonyOS 2正式版,HarmonyOS正式到来!

在最新的HarmonyOS 2.0中,"服务卡片"成为一个新的亮点。

其实不止是服务卡片,更是这种原子化服务的理念,简洁,高效,开放,带来的想象力和创新,开启了信息化时代的新的一页,相信以后基于鸿蒙的创新场景,会如雨后春笋般的不断涌现。

什么是服务卡片?
服务卡片(以下简称“卡片”)是一种界面展示形式,将重要信息或操作直接放置到卡片中,用户通过直接操作卡片就可以达到应用的使用体验,这样做大大减少了应用的使用层级性。

卡片常用于嵌入到其他应用中作为其界面的一部分显示(也可以使用原子化服务将应用保存到服务中心中,这种方式不需要安装应用),并支持拉起页面,发送消息等基础的交互功能。

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

什么是原子化服务?
原子是构成应用的不可再分解的基本服务单元,鸿蒙原子化服务强调的是轻量化服务、免安装应用,也便于为其他应用共享,轻量化的服务可以随意组合,且可有意突出特色化的服务,可为用户提供一个或多个便捷的用户程序形态。试想一个庞大的app有很多服务,都揉合在一个应用中不但操作繁琐,且没有重点,不能有效的吸引用户,里面庞大的百分之七八十的功能可能用户都不关注。且庞大的体系也不利于传播,复用和快速迭代。在平台微服务化,移动端小程序化大行其道火热的今天,华为把类似的概念引入鸿蒙系统,生成一个新的连接场景,提供了更新的想象空间。关于原子化服务的更多内容,参见:HarmonyOS Developer:什么是原子化服务

来看下几个火热的例子:
距离HarmonyOS 2.0发布不到一个月时间,6月25日,国泰君安上线了HarmonyOS原子化服务,一次推出“君弘Lite”、“开户”两个原子化服务卡片。同在6月25日,兴业证券官宣也发布鸿蒙生态服务卡片“优理宝”。

招商银行、中国银行等金融机构此前也官宣上线鸿蒙原子化服务,积极布局开放银行场景新生态。

鸿蒙原子化服务具备免安装、轻量化的特性,其载体为卡片,在服务中心可以轻松获取、随时分享,无需下载、安装。基于 HarmonyOS 2,南航向用户推出“机票预订”和“航班动态”两个原子化服务。其最大特点是用户无需下载“南方航空”App,即可获得与 App 相同的使用体验。


招行银行试着将财富管理的服务从App外延至鸿蒙系统,以App热门栏位“财富直击”为抓手,形成一个原子化服务向客户推荐,当用户看见心仪产品时,即可完成购买。

还有更多的企业不断的在跟进中,大有争先恐后之势。第一次感受到有证券部门比技术部还着急的,因为大凡沾点鸿蒙概念的边,那啥都长飞了。在数字化转型的关键时期,单是在银行业就将迎来一轮新的机遇,借力鸿蒙系统完成应用创新。

服务入口不止是APP,用户在不下载APP的情况下,同样能畅享多种轻应用服务,建立起让用户感觉更亲密的认知和体验。 在全面到来的数字化时代,用户对服务的需求在不同场景及终端,逐步向智能化、便捷化、轻量化转变。

鸿蒙的出现,提供了更新的想象空间,尤其是超级终端理念、轻量化原子服务。
未来,鸿蒙系统带来的全新技术与服务方式,将会进一步加快产品与鸿蒙系统间的融合,从产品、技术、场景联合发力,打造出更多基于鸿蒙生态的特色服务场景。

酝酿了这么多,下面开始进入技术部分的正题,来尝尝鲜。

官方将服务卡片分为三方面:

卡片使用方
显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。

卡片管理服务
用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。

卡片提供方
提供卡片显示内容的HarmonyOS应用或原子化服务,控制卡片的显示内容、控件布局以及控件点击事件

卡片使用方和提供方不要求常驻运行,在需要添加/删除/请求更新卡片时,卡片管理服务会拉起卡片提供方获取卡片信息。

卡片管理服务
  • 周期性刷新:在卡片添加后,根据卡片的刷新策略启动定时任务周期性触发卡片的刷新。
  • 卡片缓存管理:在卡片添加到卡片管理服务后,对卡片的视图信息进行缓存,以便下次获取卡片时可以直接返回缓存数据,降低时延。
  • 卡片生命周期管理:对于卡片切换到后台或者被遮挡时,暂停卡片的刷新;以及卡片的升级/卸载场景下对卡片数据的更新和清理。
  • 卡片使用方对象管理:对卡片使用方的RPC对象进行管理,用于使用方请求进行校验以及对卡片更新后的回调处理。

卡片提供方
创建卡片实例并实现onCreateForm、onUpdateForm和onDeleteForm处理创建卡片、更新卡片以及删除卡片等请求,提供相应的卡片服务。

服务卡片的实现:
官方文档,官方教程文档见Java卡片开发指导

运行DevEco Studio创建一个新项目,因为要使用java语言,选择支持Java语言。测试起见,选择个空的Ability页面。

接下来创建模板:右击“Entry”→“New”→“Server Widget”

创建完成之后,在代码的根目录下会自动创建一个widget的文件夹。且自动生成了一些相关代码。

在文件夹中出现了三个文件FormControllerManager.java、FormController.java、CardWidgetimpl.java。

三个文件对应了卡片服务的运行机制,通过关系的对应可以清楚的了解到代码运行原理。
FormControllerManager.java(卡片控制器管理--卡片服务管理)

  1. public class FormControllerManager {
  2.     private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, FormControllerManager.class.getName());
  3.     private static final String PACKAGE_PATH = "com.example.carddemo.widget";
  4.     private static final String SHARED_SP_NAME = "form_info_sp.xml";
  5.     private static final String FORM_NAME = "formName";
  6.     private static final String DIMENSION = "dimension";
  7.     private static FormControllerManager managerInstance = null;
  8.     private final HashMap controllerHashMap = new HashMap<>();
  9.     private final Context context;
  10.     private final Preferences preferences;

  11.     /**
  12.      * 初始化构造器
  13.      *
  14.      * @param context instance of Context.
  15.      */
  16.     private FormControllerManager(Context context) {
  17.         this.context = context;
  18.         DatabaseHelper databaseHelper = new DatabaseHelper(this.context.getApplicationContext());
  19.         preferences = databaseHelper.getPreferences(SHARED_SP_NAME);
  20.     }

  21.     /**
  22.      * FormControllerManager 实例化
  23.      *
  24.      * @param context instance of Context.
  25.      * @return FormControllerManager instance.
  26.      */
  27.     public static FormControllerManager getInstance(Context context) {
  28.         if (managerInstance == null) {
  29.             synchronized (FormControllerManager.class) {
  30.                 if (managerInstance == null) {
  31.                     managerInstance = new FormControllerManager(context);
  32.                 }
  33.             }
  34.         }
  35.         return managerInstance;
  36.     }

  37.     /**
  38.      * 通过构造器将传入的卡片信息进行封装,返回卡片服务
  39.      *
  40.      * @param formId    form id.
  41.      * @param formName  form name.
  42.      * @param dimension form dimension
  43.      * @return FormController form controller
  44.      */
  45.     public FormController createFormController(long formId, String formName, int dimension) {
  46.         synchronized (controllerHashMap) {
  47.             if (formId < 0 || formName.isEmpty()) {
  48.                 return null;
  49.             }
  50.             HiLog.info(TAG,
  51.                     "saveFormId() formId: " + formId + ", formName: " + formName + ", preferences: " + preferences);
  52.             if (preferences != null) {
  53.                 ZSONObject formObj = new ZSONObject();
  54.                 formObj.put(FORM_NAME, formName);
  55.                 formObj.put(DIMENSION, dimension);
  56.                 preferences.putString(Long.toString(formId), ZSONObject.toZSONString(formObj));
  57.                 preferences.flushSync();
  58.             }
  59.             // Create controller instance.
  60.             FormController controller = newInstance(formName, dimension, context);
  61.             // Cache the controller.
  62.             if (controller != null) {
  63.                 if (!controllerHashMap.containsKey(formId)) {
  64.                     controllerHashMap.put(formId, controller);
  65.                 }
  66.             }
  67.             return controller;
  68.         }
  69.     }

  70.     /**
  71.      * 获取卡片控制器实例
  72.      *
  73.      * @param formId form id.
  74.      * @return the instance of form controller.
  75.      */
  76.     public FormController getController(long formId) {
  77.         synchronized (controllerHashMap) {
  78.             if (controllerHashMap.containsKey(formId)) {
  79.                 return controllerHashMap.get(formId);
  80.             }
  81.             Map forms = preferences.getAll();
  82.             String formIdString = Long.toString(formId);
  83.             if (forms.containsKey(formIdString)) {
  84.                 ZSONObject formObj = ZSONObject.stringToZSON((String) forms.get(formIdString));
  85.                 String formName = formObj.getString(FORM_NAME);
  86.                 int dimension = formObj.getIntValue(DIMENSION);
  87.                 FormController controller = newInstance(formName, dimension, context);
  88.                 controllerHashMap.put(formId, controller);
  89.             }
  90.             return controllerHashMap.get(formId);
  91.         }
  92.     }

  93.     private FormController newInstance(String formName, int dimension, Context context) {
  94.         FormController ctrInstance = null;
  95.         if (formName == null || formName.isEmpty()) {
  96.             HiLog.error(TAG, "newInstance() get empty form name");
  97.             return ctrInstance;
  98.         }
  99.         try {
  100.             String className = PACKAGE_PATH + "." + formName.toLowerCase(Locale.ROOT) + "."
  101.                     + getClassNameByFormName(formName);
  102.             Class clazz = Class.forName(className);
  103.             if (clazz != null) {
  104.                 Object controllerInstance = clazz.getConstructor(Context.class, String.class, Integer.class)
  105.                         .newInstance(context, formName, dimension);
  106.                 if (controllerInstance instanceof FormController) {
  107.                     ctrInstance = (FormController) controllerInstance;
  108.                 }
  109.             }
  110.         } catch (NoSuchMethodException | InstantiationException | IllegalArgumentException | InvocationTargetException
  111.                 | IllegalAccessException | ClassNotFoundException | SecurityException exception) {
  112.             HiLog.error(TAG, "newInstance() get exception: " + exception.getMessage());
  113.         }
  114.         return ctrInstance;
  115.     }

  116.     /**
  117.      * 从数组中获取所有卡片id
  118.      *
  119.      * @return form id list
  120.      */
  121.     public List getAllFormIdFromSharePreference() {
  122.         List result = new ArrayList<>();
  123.         Map forms = preferences.getAll();
  124.         for (String formId : forms.keySet()) {
  125.             result.add(Long.parseLong(formId));
  126.         }
  127.         return result;
  128.     }

  129.     /**
  130.      * 删除卡片服务
  131.      *
  132.      * @param formId form id
  133.      */
  134.     public void deleteFormController(long formId) {
  135.         synchronized (controllerHashMap) {
  136.             preferences.delete(Long.toString(formId));
  137.             preferences.flushSync();
  138.             controllerHashMap.remove(formId);
  139.         }
  140.     }

  141.     private String getClassNameByFormName(String formName) {
  142.         String[] strings = formName.split("_");
  143.         StringBuilder result = new StringBuilder();
  144.         for (String string : strings) {
  145.             result.append(string);
  146.         }
  147.         char[] charResult = result.toString().toCharArray();
  148.         charResult[0] = (charResult[0] >= 'a' && charResult[0] <= 'z') ? (char) (charResult[0] - 32) : charResult[0];
  149.         return String.copyValueOf(charResult) + "Impl";
  150.     }
  151. }
FormController.java(卡片控制器--卡片提供方)
  1. public abstract class FormController {
  2.     protected final Context context;
  3.     protected final String formName;
  4.     protected final int dimension;
  5.     public FormController(Context context, String formName, Integer dimension) {
  6.         this.context = context;
  7.         this.formName = formName;
  8.         this.dimension = dimension;
  9.     }
  10.     /**
  11.      * 创建卡片信息提供者
  12.      */
  13.     public abstract ProviderFormInfo bindFormData();
  14.     /**
  15.      * 更新卡片信息
  16.      */
  17.     public abstract void updateFormData(long formId, Object... vars);
  18.     /**
  19.      * 在接收服务小部件消息事件时调用
  20.      */
  21.     public abstract void onTriggerFormEvent(long formId, String message);
  22.     /**
  23.      * 获取路由的目标界面
  24.      */
  25.     public abstract Class getRoutePageSlice(Intent intent);
  26. }
CardWidgetImpl.java(卡片应用--卡片使用方)
  1. public class CardWidgetImpl extends FormController {
  2.     public static final int DIMENSION_1X2 = 1;
  3.     public static final int DIMENSION_2X4 = 3;
  4.     private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, CardWidgetImpl.class.getName());
  5.     private static final int DEFAULT_DIMENSION_2X2 = 2;
  6.     private static final Map RESOURCE_ID_MAP = new HashMap<>();
  7.     static {
  8.         RESOURCE_ID_MAP.put(DIMENSION_1X2, ResourceTable.Layout_form_grid_pattern_cardwidget_1_2);
  9.         RESOURCE_ID_MAP.put(DEFAULT_DIMENSION_2X2, ResourceTable.Layout_form_grid_pattern_cardwidget_2_2);
  10.         RESOURCE_ID_MAP.put(DIMENSION_2X4, ResourceTable.Layout_form_grid_pattern_cardwidget_2_4);
  11.     }

  12.     public CardWidgetImpl(Context context, String formName, Integer dimension) {
  13.         super(context, formName, dimension);
  14.     }

  15.     //创建好卡片服务后,在界面展示卡片
  16.     @Override
  17.     public ProviderFormInfo bindFormData() {
  18.         HiLog.info(TAG, "bind form data when create form");
  19.         return new ProviderFormInfo(RESOURCE_ID_MAP.get(dimension), context);
  20.     }

  21.     //更新卡片信息
  22.     @Override
  23.     public void updateFormData(long formId, Object... vars) {
  24.         HiLog.info(TAG, "update form data timing, default 30 minutes");
  25.     }

  26.     //卡片中内容手势触发方法
  27.     @Override
  28.     public void onTriggerFormEvent(long formId, String message) {
  29.         HiLog.info(TAG, "handle card click event.");
  30.     }

  31.     @Override
  32.     public Class getRoutePageSlice(Intent intent) {
  33.         HiLog.info(TAG, "get the default page to route when you click card.");
  34.         return null;
  35.     }
  36. }
MainAbility里自动新增了一些方法:
  1. public class MainAbility extends Ability {
  2.     public static final int DEFAULT_DIMENSION_2X2 = 2;
  3.     public static final int DIMENSION_1X2 = 1;
  4.     public static final int DIMENSION_2X4 = 3;
  5.     public static final int DIMENSION_4X4 = 4;
  6.     private static final int INVALID_FORM_ID = -1;
  7.     private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, MainAbility.class.getName());
  8.     private String topWidgetSlice;

  9.     @Override
  10.     public void onStart(Intent intent) {
  11.         super.onStart(intent);
  12.         super.setMainRoute(MainAbilitySlice.class.getName());
  13.         if (intentFromWidget(intent)) {
  14.             topWidgetSlice = getRoutePageSlice(intent);
  15.             if (topWidgetSlice != null) {
  16.                 setMainRoute(topWidgetSlice);
  17.             }
  18.         }
  19.         stopAbility(intent);
  20.     }

  21.     //创建卡片信息并返回卡片内容
  22.     @Override
  23.     protected ProviderFormInfo onCreateForm(Intent intent) {
  24.         HiLog.info(TAG, "onCreateForm");
  25.         long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
  26.         String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
  27.         int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
  28.         HiLog.info(TAG, "onCreateForm: formId=" + formId + ",formName=" + formName);
  29.         FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
  30.         FormController formController = formControllerManager.getController(formId);
  31.         //通过调用布局Id将卡片布局与卡片信息进行绑定
  32.         formController = (formController == null) ? formControllerManager.createFormController(formId,
  33.                 formName, dimension) : formController;
  34.         if (formController == null) {
  35.             HiLog.error(TAG, "Get null controller. formId: " + formId + ", formName: " + formName);
  36.             return null;
  37.         }
  38.         return formController.bindFormData();
  39.     }

  40.     //更新卡片信息
  41.     @Override
  42.     protected void onUpdateForm(long formId) {
  43.         HiLog.info(TAG, "onUpdateForm");
  44.         super.onUpdateForm(formId);
  45.         FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
  46.         FormController formController = formControllerManager.getController(formId);
  47.         formController.updateFormData(formId);
  48.     }

  49.     //删除卡片
  50.     @Override
  51.     protected void onDeleteForm(long formId) {
  52.         HiLog.info(TAG, "onDeleteForm: formId=" + formId);
  53.         super.onDeleteForm(formId);
  54.         FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
  55.         formControllerManager.deleteFormController(formId);
  56.     }

  57.     //卡片手势触发
  58.     @Override
  59.     protected void onTriggerFormEvent(long formId, String message) {
  60.         HiLog.info(TAG, "onTriggerFormEvent: " + message);
  61.         super.onTriggerFormEvent(formId, message);
  62.         FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
  63.         FormController formController = formControllerManager.getController(formId);
  64.         formController.onTriggerFormEvent(formId, message);
  65.     }

  66.     @Override
  67.     public void onNewIntent(Intent intent) {
  68.         if (intentFromWidget(intent)) { // Only response to it when starting from a service widget.
  69.             String newWidgetSlice = getRoutePageSlice(intent);
  70.             if (topWidgetSlice == null || !topWidgetSlice.equals(newWidgetSlice)) {
  71.                 topWidgetSlice = newWidgetSlice;
  72.                 restart();
  73.             }
  74.         }
  75.     }

  76.     private boolean intentFromWidget(Intent intent) {
  77.         long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
  78.         return formId != INVALID_FORM_ID;
  79.     }

  80.     //跳转至对应的卡片界面
  81.     private String getRoutePageSlice(Intent intent) {
  82.         long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
  83.         if (formId == INVALID_FORM_ID) {
  84.             return null;
  85.         }
  86.         FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
  87.         FormController formController = formControllerManager.getController(formId);
  88.         if (formController == null) {
  89.             return null;
  90.         }
  91.         Class clazz = formController.getRoutePageSlice(intent);
  92.         if (clazz == null) {
  93.             return null;
  94.         }
  95.         return clazz.getName();
  96.     }
  97. }
onCreateForm(Intent intent)
onUpdateForm(long formId)
onDeleteForm(long formId)
onCastTempForm(long formId)
onEventNotify(MapformEvents)

在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获取卡片使用方设置的自定义数据。当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用

onCreateForm回调函数,完成卡片信息的初始化。

onDeleteForm,当卡片被删除时,需要重写onDeleteForm方法,根据卡片id删除卡片实例数据。

卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。

卡片提供方:提供卡片显示内容的HarmonyOS Service/HarmonyOS Application,控制卡片的显示内容、控件布局以及控件点击事件。

配置文件config.json里也发生了一些变化。

可以对卡片的界面根据需要更改。

修改form_grid_pattern_cardwidget_1_2、form_grid_pattern_cardwidget_2_2.xml和form_grid_pattern_cardwidget_2_4.xml文件。

最后运行程序,可查看效果。点击图标上划,出现卡片。后续可以进一步完善。使其进行动态的刷新及动态的获取数据。比其传统的一个小应用图标,这种卡片的方式成了一种新的媒介窗口,更有活力和生命力,方便了用户的同时也连通了企业。

更多详细的介绍,参加官方文档:https://developer.harmonyos.com/cn/docs/design/des-guides/service-overview-0000001139795693

其中官方还有个ClockFACardDemo,值得学习。代码参见:https://gitee.com/openharmony/codelabs/tree/master/ClockFACardDemo

` 84.png

回帖(1)

洒下墨色

2021-8-17 14:01:14
果然是大佬,太牛了

更多回帖

×
发帖