初始化原子化服务
区别于传统的HarmonyOS应用,在通过DevEco Studio工程向导创建原子化服务时,Project Type应选择为Service,同时勾选Show in Service Center,如图:
原子化服务的项目结构
如下图:
上述目录中,widget包下的代码即为服务卡片的相关的代码。其中:
- controller包下的FormController是服务卡片控制器的接口。
- controller包下的FormControllerManager是服务卡片控制器的管理器。
- widget包下的WidgetImpl是FormController的默认实现类。
此时,会同步创建一个2×2的默认服务卡片模板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>
预览原子化服务卡片,如下图所示:
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>
预览效果,如图所示:
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"
},
...
]
}
搜索原子化服务
安装完原子化服务后,就可以在服务中心通过的名称搜索到该原子服务,如下图所示,可以通过长按卡片将服务添加到桌面和我的收藏,或者直接点击卡片来打开原子化服务。
运行原子化服务
点击卡片来打开原子化服务,就能够运行该原子服务,效果如下图所示: