前言
在万物互联时代,人均持有设备量不断攀升,设备和场景的多样性,使应用开发变得更加复杂、应用入口更加丰富。在此背景下,应用提供方和用户迫切需要一种新的服务提供方式,使应用开发更简单、服务(如听音乐、打车等)的获取和使用更便捷。为此,HarmonyOS提供了特定功能的免安装的应用(即原子化服务)。
原子化服务是HarmonyOS提供的一种面向未来的服务提供方式,是有独立入口的(用户可通过点击方式直接触发)、免安装的(无需显式安装,由系统程序框架后台安装后即可使用)、可为用户提供一个或多个便捷服务的用户应用程序形态。例如:某传统方式的需要安装的购物应用A,在按照原子化服务理念调整设计后,成为由“商品浏览”“购物车”“支付”等多个便捷服务组成的、可以免安装的购物原子化服务A*。
原子化服务特征
1、随处可及
原子化服务通过以下方式被发现并使用:
- 服务发现:原子化服务可在服务中心发现并使用。
- 智能推荐:原子化服务可以基于合适场景被主动推荐给用户使用;用户可在服务中心和小艺建议中发现系统推荐的服务。
2、服务直达
服务直达体现在以下两个方面:
- 原子化服务支持免安装使用。
- 服务卡片:支持用户无需打开原子化服务便可获取服务内重要信息的展示和动态变化,如天气、关键事务备忘、热点新闻列表。
3、跨设备
跨设备体现在以下几个方面:
- 原子化服务支持运行在1+8+N设备上,如手机、平板等设备。
- 支持跨设备分享:例如接入华为分享后,用户可分享原子化服务给好友,好友确认后打开分享的服务。
- 支持跨端迁移:例如手机上未完成的邮件,迁移到平板继续编辑。
- 支持多端协同:例如手机用作文档翻页和批注,配合智慧屏显示完成分布式办公;手机作为手柄,与智慧屏配合玩游戏。
初始化原子化服务
区别于传统的HarmonyOS应用,在通过DevEco Studio工程向导创建原子化服务时,Project Type应选择为Service,同时勾选Show in Service Center,如图:
原子化服务的项目结构
如下图:
上述目录中,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。
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;
...
/**
* 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
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.example.weatherservicecard.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;
}
...
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
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 MapInteger, 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 Class extends AbilitySlice getRoutePageSlice(Intent intent) {
HiLog.info(TAG, get the default page to route when you click card.);
return null;
}
}
上述WidgetImpl类可以根据实际开发需要进行修改,比如涉及数据的更新或者事件的监听,只需要重写上述的updateFormData或者onTriggerFormEvent方法即可。
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类中进行管理和路由的。
修改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>
预览原子化服务卡片,如下图所示:
修改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>
预览效果,如图所示:
config.json
config.json是整个项目的配置文件,代码如下:
{
"app": {
...
}
},
"deviceConfig": {},
"module": {
....
"abilities": [
{
...
"orientation": "unspecified",
"name": "com.example.weatherservicecard.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"
},
...
]
}
搜索原子化服务
安装完原子化服务后,就可以在服务中心通过的名称搜索到该原子服务,如下图所示,可以通过长按卡片将服务添加到桌面和我的收藏,或者直接点击卡片来打开原子化服务。
运行原子化服务
点击卡片来打开原子化服务,就能够运行该原子服务,效果如下图所示: