• 1、概述
  • 1.1、其他基本概念
  • 1.2、运作机制
  • 2、来体验一把HarmoneyOS当下火热的服务卡片(Services Widget)吧。
  • 3、项目代码
  • 4、运行


1、概述

        服务卡片是FA(Feature Ability)的一种界面展示形式,将FA的重要信息或操作前置到卡片,以达到服务直达,减少体验层级目的。

可能有些小伙伴还不太理解服务卡片是个什么东西,其实我们在手机上已经接触过很多了,不管你用的什么手机,只要你滑到负1屏就可以看到我们的服务卡片

服务卡片目前仅支持系统应用,用于展示部分信息,并支持拉起页面,发送消息等功能,卡片使用方负责显示卡片。就拿菜鸟裹裹来说,我们取快递都需要用到身份码,而打开身份码往往需要多次操作才能完成,这无疑增加了不必要的逻辑操作,浪费了用户的时间。为了提高用户的体验,我们完全可以将用户经常需要使用的功能单独增加一个入口,而这个入口就是我们的服务卡片

1.1、其他基本概念

  • 卡片使用方
    显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。
  • 卡片管理服务
    用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。
  • 卡片提供方
  • 提供卡片显示内容的HarmoneyOS应用或原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。

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

1.2、运作机制


原子层微服务 原子服务器怎么样_提供方


简单分析一下这个图,以添加为例,卡片使用方发起添加请求,由卡片管理服务接收,然后拉起卡片提供方获取卡片服务,并将该卡片服务对象返回给卡片使用方。

卡片管理服务包含以下模块:

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

卡片提供方包含以下模块:

  • 卡片服务
    由卡片提供方开发者实现,开发者实现onCreateForm、onUpdateForm和onDeleteForm处理创建卡片、删除卡片以及更新卡片等请求,提供相应的卡片服务。
  • 卡片提供方实例管理模块
    由卡片提供方开发者实现,负责对负责对卡片管理服务分配的卡片实例进行持久化管理
  • 通信适配层
    由HarmoneyOS SDK提供,负责与卡片管理服务通信,用于将卡片的更新数据主动推送到卡片管理服务。

2、来体验一把HarmoneyOS当下火热的服务卡片(Services Widget)吧。

首先,我们需要先创建一个项目,还没有安装软件以及不会创建项目的小伙伴请参考超详细的DevEcoStudio安装教程以及我的第一个HarmoneyOS程序

当我们创建好项目之后是这样的。

原子层微服务 原子服务器怎么样_HarmoneyOS_02

项目创建好之后,我们需要为其添加卡片服务,可参考官方文档:java卡片开发指导

具体操作如下:

找到我们java代码的根目录,右键新建一个服务卡片(Services Widget)

原子层微服务 原子服务器怎么样_原子层微服务_03


完成之后点击Finish即可。

原子层微服务 原子服务器怎么样_HarmoneyOS_04


卡片创建完成之后,在代码的根目录下会自动创建一个widget的文件夹

原子层微服务 原子服务器怎么样_华为_05

3、项目代码

卡片提供方:为卡片使用方的请求提供不同的接口,当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用onCreateForm(Intent intent)回调,intent中会带有卡片ID,卡片名称,临时卡片标记和卡片外观规格信息,分别通过以下方式获取:

  • AbilitySlice.PARAM_FORM_IDENTITY_KEY 获取卡片ID
  • AbilitySlice.PARAM_FORM_NAME_KEY 获取卡片名称
  • AbilitySlice.PARAM_FORM_TEMORARY_KEY 获取临时卡片标记
  • AbilitySlice.PARAM_FORM_DIMENSION_KEY 获取卡片外观规格信息

提供方还可以通过AbilitySlice.PARAM_FORM_CUSTOMIZE_KEY获取卡片使用方设置的自定义数据

package com.example.myapplication;

import com.example.myapplication.slice.MainAbilitySlice;
import com.example.myapplication.widget.controller.*;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.ability.ProviderFormInfo;
import ohos.aafwk.content.Intent;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

public class MainAbility extends Ability {
    public static final int DEFAULT_DIMENSION_2X2 = 2;
    public static final int DIMENSION_1X2 = 1;
    public static final int DIMENSION_2X4 = 3;
    public static final int DIMENSION_4X4 = 4;
    private static final int INVALID_FORM_ID = -1;
    private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, MainAbility.class.getName());
    private String topWidgetSlice;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setMainRoute(MainAbilitySlice.class.getName());
        if (intentFromWidget(intent)) {
            topWidgetSlice = getRoutePageSlice(intent);
            if (topWidgetSlice != null) {
                setMainRoute(topWidgetSlice);
            }
        }
        stopAbility(intent);
    }

	//卡片提供方接收创建卡片通知接口。
    @Override
    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;
        }
        return formController.bindFormData();
    }

	//卡片提供方接收更新卡片通知接口。
    @Override
    protected void onUpdateForm(long formId) {
        HiLog.info(TAG, "onUpdateForm");
        super.onUpdateForm(formId);
        FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
        FormController formController = formControllerManager.getController(formId);
        formController.updateFormData(formId);
    }

	//卡片提供方接收删除卡片通知接口。
    @Override
    protected void onDeleteForm(long formId) {
        HiLog.info(TAG, "onDeleteForm: formId=" + formId);
        super.onDeleteForm(formId);
        FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
        formControllerManager.deleteFormController(formId);
    }

    @Override
    protected void onTriggerFormEvent(long formId, String message) {
        HiLog.info(TAG, "onTriggerFormEvent: " + message);
        super.onTriggerFormEvent(formId, message);
        FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
        FormController formController = formControllerManager.getController(formId);
        formController.onTriggerFormEvent(formId, message);
    }

    @Override
    public void onNewIntent(Intent intent) {
        if (intentFromWidget(intent)) { // Only response to it when starting from a service widget.
            String newWidgetSlice = getRoutePageSlice(intent);
            if (topWidgetSlice == null || !topWidgetSlice.equals(newWidgetSlice)) {
                topWidgetSlice = newWidgetSlice;
                restart();
            }
        }
    }

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

    private String getRoutePageSlice(Intent intent) {
        long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
        if (formId == INVALID_FORM_ID) {
            return null;
        }
        FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
        FormController formController = formControllerManager.getController(formId);
        if (formController == null) {
            return null;
        }
        Class<? extends AbilitySlice> clazz = formController.getRoutePageSlice(intent);
        if (clazz == null) {
            return null;
        }
        return clazz.getName();
    }
}

FormController抽象类

package com.example.myapplication.widget.controller;

import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.ability.ProviderFormInfo;
import ohos.aafwk.content.Intent;
import ohos.app.Context;

/**
 * The api set for form controller.
 */
public abstract class FormController {
    /**
     * Context of ability
     */
    protected final Context context;

    /**
     * The name of current form service widget
     */
    protected final String formName;

    /**
     * The dimension of current form service widget
     */
    protected final int dimension;

    public FormController(Context context, String formName, Integer dimension) {
        this.context = context;
        this.formName = formName;
        this.dimension = dimension;
    }

    /**
     * Bind data for a form
     *
     * @return ProviderFormInfo
     */
    public abstract ProviderFormInfo bindFormData();

    /**
     * Update form data
     *
     * @param formId the id of service widget to be updated
     * @param vars   the data to update for service widget, this parameter is optional
     */
    public abstract void updateFormData(long formId, Object... vars);

    /**
     * Called when receive service widget message event
     *
     * @param formId  form id
     * @param message the message context sent by service widget message event
     */
    public abstract void onTriggerFormEvent(long formId, String message);

    /**
     * Get the destination ability slice to route
     *
     * @param intent intent of current page slice
     * @return the destination ability slice name to route
     */
    public abstract Class<? extends AbilitySlice> getRoutePageSlice(Intent intent);
}

**FormControllerManager类 **

package com.example.myapplication.widget.controller;

import ohos.app.Context;
import ohos.data.DatabaseHelper;
import ohos.data.preferences.Preferences;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.zson.ZSONObject;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;

/**
 * Form controller manager.
 */
public class FormControllerManager {
    private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, FormControllerManager.class.getName());
    private static final String PACKAGE_PATH = "com.example.myapplication.widget";
    private static final String SHARED_SP_NAME = "form_info_sp.xml";
    private static final String FORM_NAME = "formName";
    private static final String DIMENSION = "dimension";
    private static FormControllerManager managerInstance = null;
    private final HashMap<Long, FormController> controllerHashMap = new HashMap<>();

    private final Context context;

    private final Preferences preferences;

    /**
     * Constructor with context.
     *
     * @param context instance of Context.
     */
    private FormControllerManager(Context context) {
        this.context = context;
        DatabaseHelper databaseHelper = new DatabaseHelper(this.context.getApplicationContext());
        preferences = databaseHelper.getPreferences(SHARED_SP_NAME);
    }

    /**
     * Singleton mode.
     *
     * @param context instance of Context.
     * @return FormControllerManager instance.
     */
    public static FormControllerManager getInstance(Context context) {
        if (managerInstance == null) {
            synchronized (FormControllerManager.class) {
                if (managerInstance == null) {
                    managerInstance = new FormControllerManager(context);
                }
            }
        }
        return managerInstance;
    }

    /**
     * Save the form id and form name.
     *
     * @param formId    form id.
     * @param formName  form name.
     * @param dimension form dimension
     * @return FormController form controller
     */
    public FormController createFormController(long formId, String formName, int dimension) {
        synchronized (controllerHashMap) {
            if (formId < 0 || formName.isEmpty()) {
                return null;
            }
            HiLog.info(TAG,
                    "saveFormId() formId: " + formId + ", formName: " + formName + ", preferences: " + preferences);
            if (preferences != null) {
                ZSONObject formObj = new ZSONObject();
                formObj.put(FORM_NAME, formName);
                formObj.put(DIMENSION, dimension);
                preferences.putString(Long.toString(formId), ZSONObject.toZSONString(formObj));
                preferences.flushSync();
            }

            // Create controller instance.
            FormController controller = newInstance(formName, dimension, context);

            // Cache the controller.
            if (controller != null) {
                if (!controllerHashMap.containsKey(formId)) {
                    controllerHashMap.put(formId, controller);
                }
            }

            return controller;
        }
    }

    /**
     * Get the form controller instance.
     *
     * @param formId form id.
     * @return the instance of form controller.
     */
    public FormController getController(long formId) {
        synchronized (controllerHashMap) {
            if (controllerHashMap.containsKey(formId)) {
                return controllerHashMap.get(formId);
            }
            Map<String, ?> forms = preferences.getAll();
            String formIdString = Long.toString(formId);
            if (forms.containsKey(formIdString)) {
                ZSONObject formObj = ZSONObject.stringToZSON((String) forms.get(formIdString));
                String formName = formObj.getString(FORM_NAME);
                int dimension = formObj.getIntValue(DIMENSION);
                FormController controller = newInstance(formName, dimension, context);
                controllerHashMap.put(formId, controller);
            }
            return controllerHashMap.get(formId);
        }
    }

    private FormController newInstance(String formName, int dimension, Context context) {
        FormController ctrInstance = null;
        if (formName == null || formName.isEmpty()) {
            HiLog.error(TAG, "newInstance() get empty form name");
            return ctrInstance;
        }
        try {
            String className = PACKAGE_PATH + "." + formName.toLowerCase(Locale.ROOT) + "."
                    + getClassNameByFormName(formName);
            Class<?> clazz = Class.forName(className);
            if (clazz != null) {
                Object controllerInstance = clazz.getConstructor(Context.class, String.class, Integer.class)
                        .newInstance(context, formName, dimension);
                if (controllerInstance instanceof FormController) {
                    ctrInstance = (FormController) controllerInstance;
                }
            }
        } catch (NoSuchMethodException | InstantiationException | IllegalArgumentException | InvocationTargetException
                | IllegalAccessException | ClassNotFoundException | SecurityException exception) {
            HiLog.error(TAG, "newInstance() get exception: " + exception.getMessage());
        }
        return ctrInstance;
    }

    /**
     * Get all form id from the share preference
     *
     * @return form id list
     */
    public List<Long> getAllFormIdFromSharePreference() {
        List<Long> result = new ArrayList<>();
        Map<String, ?> forms = preferences.getAll();
        for (String formId : forms.keySet()) {
            result.add(Long.parseLong(formId));
        }
        return result;
    }

    /**
     * Delete a form controller
     *
     * @param formId form id
     */
    public void deleteFormController(long formId) {
        synchronized (controllerHashMap) {
            preferences.delete(Long.toString(formId));
            preferences.flushSync();
            controllerHashMap.remove(formId);
        }
    }

    private String getClassNameByFormName(String formName) {
        String[] strings = formName.split("_");
        StringBuilder result = new StringBuilder();
        for (String string : strings) {
            result.append(string);
        }
        char[] charResult = result.toString().toCharArray();
        charResult[0] = (charResult[0] >= 'a' && charResult[0] <= 'z') ? (char) (charResult[0] - 32) : charResult[0];
        return String.copyValueOf(charResult) + "Impl";
    }
}

**WidgetImpl类,实现了FormController抽象类 **

package com.example.myapplication.widget.widget;

import com.example.myapplication.ResourceTable;
import com.example.myapplication.widget.controller.FormController;

import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.ability.ProviderFormInfo;
import ohos.aafwk.content.Intent;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import java.util.HashMap;
import java.util.Map;

public class WidgetImpl extends FormController {
    private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, WidgetImpl.class.getName());
    private static final int DEFAULT_DIMENSION_2X2 = 2;
    private static final Map<Integer, Integer> RESOURCE_ID_MAP = new HashMap<>();

    static {
        RESOURCE_ID_MAP.put(DEFAULT_DIMENSION_2X2, ResourceTable.Layout_form_image_with_information_widget_2_2);
    }

    public WidgetImpl(Context context, String formName, Integer dimension) {
        super(context, formName, dimension);
    }

    @Override
    public ProviderFormInfo bindFormData() {
        HiLog.info(TAG, "bind form data when create form");
        return new ProviderFormInfo(RESOURCE_ID_MAP.get(dimension), context);
    }

    @Override
    public void updateFormData(long formId, Object... vars) {
        HiLog.info(TAG, "update form data timing, default 30 minutes");
    }

    @Override
    public void onTriggerFormEvent(long formId, String message) {
        HiLog.info(TAG, "handle card click event.");
    }

    @Override
    public Class<? extends AbilitySlice> getRoutePageSlice(Intent intent) {
        HiLog.info(TAG, "get the default page to route when you click card.");
        return null;
    }
}

4、运行

在应用图片处上滑,属于你的个人卡片就出现了,是不是很有趣呢😄

原子层微服务 原子服务器怎么样_鸿蒙_06