2

HarmonyOS-原子化服务完整开发流程

 2 years ago
source link: https://os.51cto.com/article/703371.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
HarmonyOS-原子化服务完整开发流程-51CTO.COM
HarmonyOS-原子化服务完整开发流程
作者:OS中的塔秋莎 2022-03-07 16:46:03
区别于传统的HarmonyOS应用,在通过DevEco Studio工程向导创建原子化服务时,Project Type应选择为Service,同时勾选Show in Service Center。

d947743014dadc9482f2821062554ff4c6bbd7.png

​想了解更多内容,请访问:​

​51CTO和华为官方合作共建的鸿蒙技术社区​

​https://ost.51cto.com​

初始化原子化服务

区别于传统的HarmonyOS应用,在通过DevEco Studio工程向导创建原子化服务时,Project Type应选择为Service,同时勾选Show in Service Center,如图:

56369430606ab422d60367a47a9aa2b39c3620.png

原子化服务的项目结构

060fa7e183dbf92c0f07446a23ce531a3977c9.png

上述目录中,widget包下的代码即为服务卡片的相关的代码。其中:

  • controller包下的FormController是服务卡片控制器的接口。
  • controller包下的FormControllerManager是服务卡片控制器的管理器。
  • widget包下的WidgetImpl是FormController的默认实现类。

此时,会同步创建一个2x2的默认服务卡片模板form_image_with_information_widget_2_2.xml,同时还会创建该卡片对应的快照图form_image_with_information_widget_default_image_2.png。

1、FormController

FormController定义了卡片的控制器接口,代码如下:

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

上述接口由代码生成器直接生成,基本上在开发过程中无须修改,直接使用即可。

2、FormControllerManager

FormControllerManager是服务卡片控制器的管理器,代码如下:

/**
 * 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.waylau.hmos.hellodog.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";
    }
}

上述类由代码生成器直接生成,基本上在开发过程中无须修改,直接使用即可。

3、WidgetImpl

WidgetImpl是FormController的默认实现类,代码如下:

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

上述WidgetImpl类可以根据实际开发需要进行修改,比如涉及数据的更新或者事件的监听,只需要重写上述的updateFormData或者onTriggerFormEvent方法即可。

4、MainAbility

MainAbility是主页面,代码如下:

public class MainAbility extends Ability {
    public static final int DEFAULT_DIMENSION_2X2 = 2;
    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();
    }
}

卡片服务也是在该MainAbility类中进行管理和路由的。

5、修改form_image_with_information_widget_2_2.xml

form_image_with_information_widget_2_2.xml是原子化服务卡片的布局,对其进行个性化的修改,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<DependentLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:background_element="#FFFFFFFF"
    ohos:remote="true">
    <Image
        ohos:height="match_parent"
        ohos:width="126vp"
        ohos:horizontal_center="true"
        ohos:image_src="$media:weather"
        ohos:scale_mode="zoom_start"
        ohos:top_margin="17vp"/>
    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="126vp"
        ohos:align_parent_bottom="true"
        ohos:bottom_margin="12vp"
        ohos:horizontal_center="true"
        ohos:orientation="vertical">
        <Text
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:text="天气预报"
            ohos:text_color="#E5000000"
            ohos:text_size="16fp"
            ohos:text_weight="500"
            ohos:truncation_mode="ellipsis_at_end"/>
        <Text
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:text="随时掌握天气情况"
            ohos:text_color="#99000000"
            ohos:text_size="12fp"
            ohos:text_weight="400"
            ohos:top_margin="2vp"
            ohos:truncation_mode="ellipsis_at_end"/>
    </DirectionalLayout>
</DependentLayout>

预览原子化服务卡片,如下图所示:

59798b868716779d0c97474c95e4daca613da3.png

6、修改ability_main.xml

ability_main是整个项目的布局,修改ability_main.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:alignment="center"
    ohos:orientation="vertical">
    <Image
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:image_src="$media:weather"
        />
    <Text
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:text="天气预报"
        ohos:text_size="30fp"
        />
    <Text
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:text="随时掌握天气情况"
        ohos:text_size="20fp"
        />
</DirectionalLayout>

预览效果,如图所示:

b25f12f5243a9764d52838fef41363f1ba1b09.png

7、config.json

config.json是整个项目的配置文件,代码如下:

{
  "app": {
    "bundleName": "com.waylau.hmos.hellodog",
    "vendor": "waylau",
    "version": {
      "code": 1000000,
      "name": "1.0.0"
    }
  },
  "deviceConfig": {},
  "module": {
    "package": "com.waylau.hmos.hellodog",
    "name": ".MyApplication",
    "mainAbility": "com.waylau.hmos.hellodog.MainAbility",
    "deviceType": [
      "phone"
    ],
    "distro": {
      "deliveryWithInstall": true,
      "moduleName": "entry",
      "moduleType": "entry",
      "installationFree": true
    },
    "abilities": [
      {
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ],
        "orientation": "unspecified",
        "name": "com.waylau.hmos.hellodog.MainAbility",
        "icon": "$media:icon",
        "description": "$string:mainability_description",
        "formsEnabled": true,
        "label": "$string:entry_MainAbility",
        "type": "page",
        "forms": [
          {
            "landscapeLayouts": [
              "$layout:form_image_with_information_widget_2_2"
            ],
            "isDefault": true,
            "scheduledUpdateTime": "10:30",
            "defaultDimension": "2*2",
            "name": "widget",
            "description": "This is a service widget",
            "colorMode": "auto",
            "type": "Java",
            "supportDimensions": [
              "2*2"
            ],
            "portraitLayouts": [
              "$layout:form_image_with_information_widget_2_2"
            ],
            "updateEnabled": true,
            "updateDuration": 1
          }
        ],
        "launchType": "standard"
      }
    ]
  }
}

注意:这里的label标签是便捷服务队用户显示的名称,必须配置,且应以资源索引的方式配置,以支持多语言,不同的HAP包的label要唯一,以免造成用户看到多个同名服务而无法区分。此外label的命名应知名见义。

{
  "string": [
    {
      "name": "entry_MainAbility",
      "value": "WeatherServiceCard"
    },
   ...
  ]
}

搜索原子化服务

安装完原子化服务后,就可以在服务中心通过的名称搜索到该原子服务,如下图所示,可以通过长按卡片将服务添加到桌面和我的收藏,或者直接点击卡片来打开原子化服务。

27d97770844df5e939590534091cf90233f131.png

55234a562a18dca803f59931815ecf7ed7f66d.png

运行原子化服务

点击卡片来打开原子化服务,就能够运行该原子服务,效果如下图所示:

d41f2ab69c364956548281b2c9b6ef47f8cbb4.png

​想了解更多内容,请访问:​

​51CTO和华为官方合作共建的鸿蒙技术社区​

​https://ost.51cto.com​

d639b1a976e4e7d651a09828e506c314bc2a49.jpg


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK