Recently, I read about the HarmonyOS essay contest on a number of blog sites, and I was excited to see the beautiful prizes. Of course, the lure of the prizes is not enough to resist my passion for technology. Technology has no peak and no end for developers. So today to do a card service development experience to share, if there is something wrong, please correct the developers, if there is any problem can also send a private letter or directly in the post message, I will respond to you in time.

What is a service card

Service card (hereinafter referred to as “card”) is an interface display form of FA. Important information or operations of FA are directly placed in the card, so that users can directly operate the card to achieve application experience, which greatly reduces the application hierarchy. Cards are often embedded in other applications to display as part of their interfaces (you can also use atomized services to save applications to a service center, which does not require application installation) and support basic interactive functions such as pulling up pages and sending messages.

Atomization services will be covered in the next post.

An example is shown in the figure below.

In order to make it easier for developers to understand, the official service card is divided into three aspects:

  • Card user

Host application that displays card content and controls where the card is displayed in the host.

  • Card Management Services

It is used to manage the resident agent service of added cards in the system, including the management and use of card objects, and the periodical refresh of cards, etc.

  • Card provider

HarmonyOS application or atomization service that provides control over card display, control layout, and control click events

The card user and provider do not require permanent operation and the card management service will pull the card provider to obtain the card information when adding/deleting/requesting update cards.

Ii. Operation mechanism of service card

Text description lag, above first. (Picture provided by the official website, borrowed here)

It can be clearly seen from the figure that the overall communication layer of card service is responsible for “RPC”, which constitutes the channel of data sending and receiving through the communication adaptation layer.

Card Management Services

  • Periodically refresh: After a card is added, a scheduled task is started based on the card refresh policy to periodically refresh the card.
  • Card cache management: After a card is added to the card management service, the system caches the view information of the card so that the cache data can be directly returned when the card is obtained next time, reducing the delay.
  • Card life cycle management: When the card is switched to the background or blocked, the refresh of the card is suspended; And update and clean the card data in the upgrade/uninstall scenario of the card.
  • Card user object management: Manages the RPC object of the card user, which is used to verify the request of the user and callback the card after update.

Card provider

  • Create a card instance and implement onCreateForm, onUpdateForm, and onDeleteForm to handle requests for creating, updating, and deleting cards and provide corresponding card services.

Implementation of Service Card (Java)

Step 1: Download, install, and configure DevEco Studio

Click to view

Step 2: Run DevEco Studio to create a new project

Because you will be writing in the Java language, select an empty page that supports the Java language.The new project configuration page is displayed.Click Finish to enter the project.

Step 3: Add the card template

Create template: Right click ‘Entry’ → ‘New’ → ‘Server Widget’

Set the template

Step 4. View the card service configuration

Project directoryBy the graph is: add card template after add a widget in the original project folder, and appeared three files in the folder FormControllerManager. Java, FormController. Java, CardWidgetImpl. Java. The three files correspond to the operation mechanism of the card service, and the operation principle of the code can be clearly understood through the corresponding relationship

FormControllerManager. Java (card controller management “card service management”)

public class FormControllerManager {
    private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, FormControllerManager.class.getName());
    private static final String PACKAGE_PATH = "com.example.carddemo.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;

    /** * Initialize the constructor **@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);
    }

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

    /** * Returns the card service ** by wrapping the incoming card information through the constructor@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);
                }
            }
            returncontroller; }}/** * Get card controller instance **@param formId form id.
     * @return the instance of form controller.
     */
    public FormController getController(long formId) {
        synchronized (controllerHashMap) {
            if (controllerHashMap.containsKey(formId)) {
                returncontrollerHashMap.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);
            }
            returncontrollerHashMap.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 instanceofFormController) { ctrInstance = (FormController) controllerInstance; }}}catch (NoSuchMethodException | InstantiationException | IllegalArgumentException | InvocationTargetException
                | IllegalAccessException | ClassNotFoundException | SecurityException exception) {
            HiLog.error(TAG, "newInstance() get exception: " + exception.getMessage());
        }
        return ctrInstance;
    }

    /** * get all card ids ** from array@return form id list
     */
    public List<Long> getAllFormIdFromSharePreference(a) {
        List<Long> result = newArrayList<>(); Map<String, ? > forms = preferences.getAll();for (String formId : forms.keySet()) {
            result.add(Long.parseLong(formId));
        }
        return result;
    }

    /** * Delete card service **@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"; }}Copy the code

Formcontroller.java (card Controller [card provider])

public abstract class FormController {
    protected final Context context;
    protected final String formName;
    protected final int dimension;
    public FormController(Context context, String formName, Integer dimension) {
        this.context = context;
        this.formName = formName;
        this.dimension = dimension;
    }
    /** * Create the card information provider */
    public abstract ProviderFormInfo bindFormData(a);
    /** * Update the card information */
    public abstract void updateFormData(long formId, Object... vars);
    /** * is called when a service widget message event is received
    public abstract void onTriggerFormEvent(long formId, String message);
    /** * Get the target interface of the route */
    public abstract Class<? extends AbilitySlice> getRoutePageSlice(Intent intent);
}
Copy the code

Cardwidgetimpl.java (CardWidgeTimpl.java)

public class CardWidgetImpl extends FormController {
    public static final int DIMENSION_1X2 = 1;
    public static final int DIMENSION_2X4 = 3;
    private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, CardWidgetImpl.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(DIMENSION_1X2, ResourceTable.Layout_form_grid_pattern_cardwidget_1_2);
        RESOURCE_ID_MAP.put(DEFAULT_DIMENSION_2X2, ResourceTable.Layout_form_grid_pattern_cardwidget_2_2);
        RESOURCE_ID_MAP.put(DIMENSION_2X4, ResourceTable.Layout_form_grid_pattern_cardwidget_2_4);
    }

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

    // After creating the card service, display the card in the interface
    @Override
    public ProviderFormInfo bindFormData(a) {
        HiLog.info(TAG, "bind form data when create form");
        return new ProviderFormInfo(RESOURCE_ID_MAP.get(dimension), context);
    }

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

    // Card content gesture trigger method
    @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; }}Copy the code

Changes to MainAbility have added some new methods.

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);
    }

    // Create the card information and return the card contents
    @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);
        // Bind the card layout to card information by calling the layout Id
        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();
    }

    // Update the card information
    @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);
    }

    // Delete the card
    @Override
    protected void onDeleteForm(long formId) {
        HiLog.info(TAG, "onDeleteForm: formId=" + formId);
        super.onDeleteForm(formId);
        FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
        formControllerManager.deleteFormController(formId);
    }

    // Card gesture trigger
    @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);
        returnformId ! = INVALID_FORM_ID; }// Jump to the corresponding card interface
    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;
        }
        returnclazz.getName(); }}Copy the code

The configuration file

Step 5: Write the card interface

Modify the form_grid_pattern_cardwidget_1_2, form_grid_pattern_cardwidget_2_2. XML and form_grid_pattern_cardwidget_2_4.xml files, respectively.

form_grid_pattern_cardwidget_1_2.xml


      <DependentLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:remote="true">
    <Image
        ohos:height="match_parent"
        ohos:width="match_parent"
        ohos:background_element="#1281f0"
        ohos:scale\_mode="clip\_center"/>
    <DirectionalLayout
        ohos:height="match_parent"
        ohos:width="match_parent"
        ohos:alignment="center"
        ohos:orientation="horizontal">
        <Text
            ohos:id="$+id:tv_name"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="$string:cardwidget_title"
            ohos:text_color="#E5FFFFFF"
            ohos:text_size="16fp"
            ohos:text_weight="1200"/>
        <Text
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="3350"
            ohos:text_color="#E5FFFFFF"
            ohos:text_size="16fp"
            ohos:text_weight="1200"/>
    </DirectionalLayout></DependentLayout>
Copy the code

form_grid_pattern_cardwidget_2_2.xml


      <DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:background_element="#FFFFFFFF"
    ohos:remote="true"
    ohos:orientation="vertical">
    <DirectionalLayout
        ohos:id="$+id:tv_top"
        ohos:height="match_parent"
        ohos:width="match_parent"
        ohos:weight="1"
        ohos:orientation="horizontal">
        <DirectionalLayout
            ohos:height="match_parent"
            ohos:width="match_parent"
            ohos:weight="1"
            ohos:orientation="vertical"
            ohos:alignment="center">
            <Text
                ohos:height="match_content"
                ohos:width="match_content"
                ohos:text="Burn calories"
                ohos:top_margin="5vp"
                ohos:text_size="10fp"
                ohos:text_weight="500">
            </Text>
            <Text
                ohos:height="60vp"
                ohos:width="60vp"
                ohos:text="2.1 Kcal"
                ohos:text_alignment="center"
                ohos:text_color="#1281f0"
                ohos:background\_element="$media:aa\_1"
                ohos:text_size="10fp"
                ohos:text_weight="500"/>
        </DirectionalLayout>
        <DirectionalLayout
            ohos:height="match_parent"
            ohos:width="match_parent"
            ohos:weight="1"
            ohos:orientation="vertical"
            ohos:alignment="center">
            <Text
                ohos:height="match_content"
                ohos:width="match_content"
                ohos:text="Real-time heart rate."
                ohos:top_margin="5vp"
                ohos:text_size="10fp"
                ohos:text_weight="500">
            </Text>
            <Text
                ohos:height="60vp"
                ohos:width="60vp"
                ohos:text="99/min"
                ohos:text_alignment="center"
                ohos:text_color="#1281f0"
                ohos:background\_element="$media:aa\_1"
                ohos:text_size="10fp"
                ohos:text_weight="500"/>
        </DirectionalLayout>
    </DirectionalLayout>
    <DirectionalLayout
        ohos:id="$+id:tv_blow"
        ohos:height="60vp"
        ohos:width="match_parent"
        ohos:align\_parent\_bottom="true"
        ohos:background_element="#1281f0"
        ohos:alignment="center"
        ohos:orientation="vertical">
        <DirectionalLayout
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:alignment="center"
            ohos:orientation="horizontal">
            <Text
                ohos:id="$+id:tv_name"
                ohos:height="match_content"
                ohos:width="match_content"
                ohos:text="$string:cardwidget_title"
                ohos:text_color="#E5FFFFFF"
                ohos:text_size="16fp"
                ohos:text_weight="600"/>
            <Text
                ohos:height="match_content"
                ohos:width="match_content"
                ohos:text="3350"
                ohos:text_color="#E5FFFFFF"
                ohos:text_size="16fp"
                ohos:text_weight="1000"/>
        </DirectionalLayout>
        <Text
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="$string:cardwidget_introduction"
            ohos:text_color="#ffffff"
            ohos:text_size="14fp"
            ohos:text_weight="800"
            ohos:top_margin="7vp"/>
    </DirectionalLayout></DirectionalLayout>
Copy the code

form_grid_pattern_cardwidget_2_4.xml


      <DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:background_element="#FFFFFFFF"
    ohos:orientation="horizontal"
    ohos:remote="true">
    <DirectionalLayout
        ohos:height="match_parent"
        ohos:width="match_parent"
        ohos:weight="1"
        ohos:orientation="vertical">
        <DirectionalLayout
            ohos:height="match_parent"
            ohos:width="match_parent"
            ohos:weight="1"
            ohos:orientation="horizontal">
            <DirectionalLayout
                ohos:height="match_parent"
                ohos:width="match_parent"
                ohos:weight="1"
                ohos:orientation="vertical"
                ohos:alignment="center">
                <Text
                    ohos:height="match_content"
                    ohos:width="match_content"
                    ohos:text="Burn calories"
                    ohos:top_margin="5vp"
                    ohos:text_size="10fp"
                    ohos:text_weight="500">
                </Text>
                <Text
                    ohos:height="60vp"
                    ohos:width="60vp"
                    ohos:text="2.1 Kcal"
                    ohos:text_alignment="center"
                    ohos:text_color="#1281f0"
                    ohos:background\_element="$media:aa\_1"
                    ohos:text_size="10fp"
                    ohos:text_weight="500"/>
            </DirectionalLayout>
            <DirectionalLayout
                ohos:height="match_parent"
                ohos:width="match_parent"
                ohos:weight="1"
                ohos:orientation="vertical"
                ohos:alignment="center">
                <Text
                    ohos:height="match_content"
                    ohos:width="match_content"
                    ohos:text="Real-time heart rate."
                    ohos:top_margin="5vp"
                    ohos:text_size="10fp"
                    ohos:text_weight="500">
                </Text>
                <Text
                    ohos:height="60vp"
                    ohos:width="60vp"
                    ohos:text="99/min"
                    ohos:text_alignment="center"
                    ohos:text_color="#1281f0"
                    ohos:background\_element="$media:aa\_1"
                    ohos:text_size="10fp"
                    ohos:text_weight="500"/>
            </DirectionalLayout>
        </DirectionalLayout>
        <DirectionalLayout
            ohos:height="60vp"
            ohos:width="match_parent"
            ohos:align\_parent\_bottom="true"
            ohos:background_element="#1281f0"
            ohos:alignment="center"
            ohos:orientation="vertical">
            <DirectionalLayout
                ohos:height="match_content"
                ohos:width="match_parent"
                ohos:alignment="center"
                ohos:orientation="horizontal">
                <Text
                    ohos:height="match_content"
                    ohos:width="match_content"
                    ohos:text="$string:cardwidget_title"
                    ohos:text_color="#E5FFFFFF"
                    ohos:text_size="16fp"
                    ohos:text_weight="600"/>
                <Text
                    ohos:height="match_content"
                    ohos:width="match_content"
                    ohos:text="3350"
                    ohos:text_color="#E5FFFFFF"
                    ohos:text_size="16fp"
                    ohos:text_weight="1000"/>
            </DirectionalLayout>
            <Text
                ohos:height="match_content"
                ohos:width="match_content"
                ohos:text="$string:cardwidget_introduction"
                ohos:text_color="#ffffff"
                ohos:text_size="14fp"
                ohos:text_weight="800"
                ohos:top_margin="7vp"/>
        </DirectionalLayout>
    </DirectionalLayout>
    <DirectionalLayout
        ohos:height="match_parent"
        ohos:width="match_parent"
        ohos:weight="1">
    </DirectionalLayout></DirectionalLayout>
Copy the code

The sixth step, run the program, view the effect

Click on the icon to dash the card.Click the thumbtack button in the upper right corner to place the card on the screen.Long press apply, the service card appears.Click the service card selection interface, slide up and down to select the card content.Click Add to desktop to add the card to the desktop.At this point, the card service application is fully developed, and the subsequent internal preparation of the card will be related. Make it dynamic refresh and dynamic data acquisition.

Jump item address

If you like it, please give a like or compliment to the author