1. 介绍
分布式运动健康Codelab包含两个HarmonyOS应用(手机端和智能穿戴端),本篇要介绍的是基于智能穿戴的HarmonyOS分布式运动健康应用,在这个应用中,我们通过调用CategroyBodyAgent提供的相应的接口,实现获取心率、步数等健康数据,利用分布式数据库和分布式软总线等能力,实现智能穿戴侧数据同步到与其绑定的手机上,以及数据在不同手机或者其他设备上进行共享,另外当识别到心率数据异常时,会拉起远端PA(Particle Ability),达到通知的效果。
最终效果预览
我们最终会构建一个简易的HarmonyOS分布式运动健康的智能穿戴客户端。应用只包含一个FA页面,我们在页面中提供了一些按钮和显示区域,以便进行数据的订阅和显示,成功订阅数据后应用上会显示具体数据,数据也会同步到手机和穿戴的分布式数据库中。当心率出现异常的时候,应用会远程启动手机侧PA服务,进行状态栏的心率异常通知,下面是应用的最终显示效果。在这个部分,我们将一起完成这个智能穿戴侧应用,其中包括:
- 智能穿戴侧如何获取健康数据
- 智能穿戴侧健康数据如何同步到手机
- 心率异常拉起手机PA并在手机通知栏显示通知
- 线程间通信开发 EventHandler
2. 搭建HarmonyOS环境 我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
- 安装DevEco Studio,详情请参考下载和安装软件。
- 设置DevEco Studio开发环境,DevEco Studio开发环境依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
- 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
- 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
- 开发者可以参考以下链接,完成设备调试的相关配置:
说明
智能穿戴侧应用需要通过运动健康APP完成和手机侧绑定,才可以实现分布式相关调测。
3. 代码结构解读
本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在参考中提供下载方式,接下来我们会用一小节来讲解整个工程的代码结构。
annotation:Bind是一个自定义注解,用来初始化页面中的组件。
enums:KeyEnum是一个枚举类,用来表示运动健康数据(K-V键值对)的键值。
remoteAbility:ISelectResultImplStartPa是ISelectResult接口的一个类,重写了ISelectResult接口,用来实现远程启动PA。
slice:HealthWatchSlice是手表应用的FA页面,其中也包含分布式数据库操作,获取健康数据等逻辑实现。
task:ScheduleRemoteAbilityTask是一个远程启动PA任务,当心率数据发生异常时调用。
util:LogUtils是封装好的日志打印类。
resources目录:存放工程使用到的资源文件,其中resourcesbaselayout下存放xml布局文件。resourcesbasemedia下存放图片资源。
config.json:工程相关配置文件。
4. 页面布局 智能穿戴侧应用仅需要一个页面,由DirectionalLayout布局和Button、Text组件共同来构成,在resourceslayoutability_main.xml下有如下代码:
- <?xml version="1.0" encoding="utf-8"?>
- <DirectionalLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:background_element="black"
- ohos:width="match_parent"
- ohos:orientation="vertical">
- <Button
- ohos:id="$+id:button_btnSubscribe"
- ohos:height="match_content"
- ohos:width="100vp"
- ohos:background_element="$graphic:background_ability_main"
- ohos:layout_alignment="horizontal_center"
- ohos:text="订阅心率"
- ohos:text_color="#FFEEE6E6"
- ohos:text_size="30"
- ohos:top_margin="20px"
- ohos:top_padding="10px"
- />
- <Button
- ohos:id="$+id:button_btnUnsubscribe"
- ohos:height="match_content"
- ohos:width="100vp"
- ohos:background_element="$graphic:background_ability_main"
- ohos:layout_alignment="horizontal_center"
- ohos:text="取消订阅心率"
- ohos:text_color="#FFEEE6E6"
- ohos:text_size="30"
- ohos:top_margin="20px"
- ohos:top_padding="10px"
- />
- <Text
- ohos:id="$+id:text_rate"
- ohos:height="match_content"
- ohos:width="120vp"
- ohos:background_element="$graphic:background_rate_step"
- ohos:layout_alignment="horizontal_center"
- ohos:text="实时心率"
- ohos:text_color="#FFEEE6E6"
- ohos:text_alignment="horizontal_center"
- ohos:text_size="30"
- ohos:top_margin="20px"
- ohos:top_padding="10px"
- />
- <Button
- ohos:id="$+id:button_btnSubscribeStep"
- ohos:height="match_content"
- ohos:width="100vp"
- ohos:background_element="$graphic:background_ability_main"
- ohos:layout_alignment="horizontal_center"
- ohos:text="订阅步数"
- ohos:text_color="#FFEEE6E6"
- ohos:text_size="30"
- ohos:top_margin="20px"
- ohos:top_padding="10px"
- />
- <Button
- ohos:id="$+id:button_btnUnsubscribeStep"
- ohos:height="match_content"
- ohos:width="100vp"
- ohos:background_element="$graphic:background_ability_main"
- ohos:layout_alignment="horizontal_center"
- ohos:text="取消订阅步数"
- ohos:text_color="#FFEEE6E6"
- ohos:text_size="30"
- ohos:top_margin="20px"
- ohos:top_padding="10px"
- />
- <Text
- ohos:id="$+id:text_step"
- ohos:height="match_content"
- ohos:width="120vp"
- ohos:background_element="$graphic:background_rate_step"
- ohos:layout_alignment="horizontal_center"
- ohos:text="实时总步数"
- ohos:text_alignment="horizontal_center"
- ohos:text_size="30"
- ohos:text_color="#FFEEE6E6"
- ohos:top_margin="20px"
- ohos:top_padding="10px"
- />
- </DirectionalLayout>
复制代码说明
布局文件中使用到的background_element样式,在entrysrcmainresourcesbasegraphic下有做定义,详情请参考完整代码。
5. 应用初始化
添加权限及设置页面路由为了保证应用的成功运行,需要在config.json中申请如下权限:
- "reqPermissions": [
- {
- "name": "ohos.permission.DISTRIBUTED_DATASYNC",
- },
- {
- "name": "ohos.permission.READ_HEALTH_DATA",
- },
- {
- "name": "ohos.permission.ACTIVITY_MOTION",
- },
- {
- "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE",
- },
- {
- "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO",
- },
- {
- "name": "ohos.permission.GET_BUNDLE_INFO",
- }
- ]
复制代码
此外,还需要在HealthWatchSlice.java的onStart()中动态添加权限,代码如下:
- if (verifySelfPermission("ohos.permission.ACTIVITY_MOTION") != 0) {
- if (canRequestPermission("ohos.permission.ACTIVITY_MOTION")) {
- requestPermissionsFromUser(new String[]{"ohos.permission.ACTIVITY_MOTION",
- "ohos.permission.READ_HEALTH_DATA", "ohos.permission.DISTRIBUTED_DATASYNC"}, 1);
- }
- }
复制代码
应用首次安装并启动后,会进入MainAbility的onStart()方法中,这里设置了页面路由,代码如下:
- super.setMainRoute(HealthWatchSlice.class.getName());
复制代码
手表FA页面加载在HealthWatchSlice.java的onStart()方法中,首先调用了initViewAnnotation()方法,用来初始化页面上的组件,在相应的组件属性上添加@Bind注解即可完成注入,代码如下:
- private void initViewAnnotation() {
- Field[] fields = getClass().getDeclaredFields();
- for (Field field : fields) {
- Bind bind = field.getAnnotation(Bind.class);
- if (bind != null) {
-
- if (bind.value() == -1) {
-
- LogUtils.error("TAG", "bind.value is must set!");
-
- return;
-
- }
-
- try {
-
- field.setAccessible(true);
-
- field.set(this, findComponentById(bind.value()));
-
- } catch (IllegalAccessException e) {
-
- LogUtils.error("TAG", "IllegalAccessException happen");
-
- }
- }
- }
- }
复制代码
随后是初始化监听initListener(),这里主要是监听几个按钮的点击事件,也是应用内获取健康数据的途径,我们会在第六小节详细说明如何实现。
接下来是初始化心率和步数数据订阅的回调initCallback(),当检测到有步数和心率数据变化时,会进入回调函数,使用对应api接口获得相应的健康数据,并写入到分布式数据库中,我们会在第七小节详细说明如何实现。
最后是初始化分布式数据服务initDbManager(),有关分布式数据库的更多知识,可以参考如何使用分布式数据库。
除此之外,在onStart中,我们还初始化了一个事件处理器,代码如下:
- myHandler = new MyEventHandler(EventRunner.current());
复制代码 6. 获取健康数据在应用里,我们添加了订阅心率和订阅步数的Button,以及取消订阅心率和步数的Button,通过监听不同Button的点击事件,来实现不同的业务逻辑。以获取心率数据为例,当点击订阅心率Button时,根据指定的传感器类型,我们会收到对应的传感器数据,并进入相应回调函数去获取数据和处理数据,示例代码如下:
- private void initListener() {
- subscribe.setClickedListener(va -> {
- bodySensor = categoryBodyAgent.getSingleSensor(CategoryBody.SENSOR_TYPE_HEART_RATE);
- if (bodySensor != null) {
- categoryBodyAgent.setSensorDataCallback(bodyDataCallback, bodySensor, INTERVAL);
- showTip("订阅心率成功");
- }
- });
-
- unsubscribe.setClickedListener(va -> {
- if (bodySensor != null) {
- categoryBodyAgent.releaseSensorDataCallback(bodyDataCallback, bodySensor);
- showTip("取消订阅心率成功");
- rate.setText("实时心率");
- }
- });
- }
复制代码 7. 数据写入分布式数据库
上面我们提到在获取到健康数据后,会进入到相应的回调函数去处理数据,这里就来具体说下数据是如何进行处理的
我们将存在categoryBodyData中的数据取出,并使用myHandler投递任务,将心率和步数数据分别通过writeData()方法写入事先建立好的分布式数据库中,参考代码如下:
- private void initCallback() {
- bodyDataCallback = new ICategoryBodyDataCallback() {
- [url=home.php?mod=space&uid=2735960]@Override[/url]
- public void onSensorDataModified(CategoryBodyData categoryBodyData) {
- float[] values = categoryBodyData.getValues();
- myHandler.postTask(new Runnable() {
- @Override
- public void run() {
- rate.setText(getNowTime() + regex + values[0]);
- // 过滤无效数据
- if (values[0] != TOP_HEART_RATE) {
- writeData(KeyEnum.RATE.getValue() + (UUID.randomUUID()),
- getNowTime() + regex + values[0]);
- // 数据异常时拉起其他设备PA
- if (values[0] < MIN_HEART_RATE || values[0] > MAX_HEART_RATE) {
- getUITaskDispatcher().asyncDispatch(new ScheduleRemoteAbilityTask(context));
- }
- }
- }
- });
- }
- };
-
- motionDataCallback = new ICategoryMotionDataCallback() {
- @Override
- public void onSensorDataModified(CategoryMotionData categoryMotionData) {
- float[] values = categoryMotionData.getValues();
- myHandler.postTask(new Runnable() {
- @Override
- public void run() {
- // 数据写入到分布式数据库中
- step.setText(KeyEnum.STEP.getDesc() + values[0]);
- writeData(KeyEnum.STEP.getValue() + (UUID.randomUUID()),
- getNowTime() + regex + values[0]);
- }
- });
- }
- };
- }
复制代码
8. 心率异常拉起PA 将心率数据写入到分布式数据库中时,我们实现了一个异常判断的逻辑:当数据大于或者小于我们规定的临界值时,便会开启一个异步任务去执行启动远端设备PA的动作,在initCallback方法中有如下代码:
- if (values[0] < MIN_HEART_RATE || values[0] > MAX_HEART_RATE) {
- getUITaskDispatcher().asyncDispatch(new ScheduleRemoteAbilityTask(context));
- }
- 在ScheduleRemoteAbilityTask.java中有如下代码:
- @Override
- public void run() {
- List<DeviceInfo> onlineDevices = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
- // 判断组网设备是否为空
- if (onlineDevices.isEmpty()) {
- iSelectResult.onSelectResult(null, context);
- return ;
- }
- // 获取设备信息
- ArrayList<String> deviceIds = new ArrayList<>();
- onlineDevices.forEach((device) -> {
- //此处设备名称需要开发者根据实际情况进行替换
- if ("Test Phone".equals(device.getDeviceName())) {
- deviceIds.add(device.getDeviceId());
- }
- });
- if(deviceIds.size() == 0){
- return;
- }
- String selectDeviceId = deviceIds.get(0);
- // 根据设备id拉起指定PA
- iSelectResult.onSelectResult(selectDeviceId, context);
- }
- }
复制代码说明
以上代码仅demo演示参考使用
9. 最终效果
最终智能穿戴侧的应用完成了获取心率、步数等健康数据,并将这些数据定时推送到手机上,而且在心率数据异常时,会拉起远端设备的PA,实现如下通知手机的效果:
智能穿戴端订阅数据:
手机端收到异常通知拉起FA:
10. 恭喜你恭喜你已经完成了分布式运动健康应用的智能穿戴侧Codelab的开发,并且学到了:
- 手表侧获取健康数据
- 手表数据同步到手机
- 心率异常拉起手机PA并在手机侧通知栏显示通知
- 线程间通信开发 EventHandler
11. 参考