[文章]HarmonyOS教程—分布式运动健康应用(智能穿戴端)

阅读量0
0
1
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下有如下代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <DirectionalLayout
  3.     xmlns:ohos="http://schemas.huawei.com/res/ohos"
  4.     ohos:height="match_parent"
  5.     ohos:background_element="black"
  6.     ohos:width="match_parent"
  7.     ohos:orientation="vertical">
  8.     <Button
  9.         ohos:id="$+id:button_btnSubscribe"
  10.         ohos:height="match_content"
  11.         ohos:width="100vp"
  12.         ohos:background_element="$graphic:background_ability_main"
  13.         ohos:layout_alignment="horizontal_center"
  14.         ohos:text="订阅心率"
  15.         ohos:text_color="#FFEEE6E6"
  16.         ohos:text_size="30"
  17.         ohos:top_margin="20px"
  18.         ohos:top_padding="10px"
  19.         />
  20.     <Button
  21.         ohos:id="$+id:button_btnUnsubscribe"
  22.         ohos:height="match_content"
  23.         ohos:width="100vp"
  24.         ohos:background_element="$graphic:background_ability_main"
  25.         ohos:layout_alignment="horizontal_center"
  26.         ohos:text="取消订阅心率"
  27.         ohos:text_color="#FFEEE6E6"
  28.         ohos:text_size="30"
  29.         ohos:top_margin="20px"
  30.         ohos:top_padding="10px"
  31.         />
  32.     <Text
  33.         ohos:id="$+id:text_rate"
  34.         ohos:height="match_content"
  35.         ohos:width="120vp"
  36.         ohos:background_element="$graphic:background_rate_step"
  37.         ohos:layout_alignment="horizontal_center"
  38.         ohos:text="实时心率"
  39.         ohos:text_color="#FFEEE6E6"
  40.         ohos:text_alignment="horizontal_center"
  41.         ohos:text_size="30"
  42.         ohos:top_margin="20px"
  43.         ohos:top_padding="10px"
  44.         />
  45.     <Button
  46.         ohos:id="$+id:button_btnSubscribeStep"
  47.         ohos:height="match_content"
  48.         ohos:width="100vp"
  49.         ohos:background_element="$graphic:background_ability_main"
  50.         ohos:layout_alignment="horizontal_center"
  51.         ohos:text="订阅步数"
  52.         ohos:text_color="#FFEEE6E6"
  53.         ohos:text_size="30"
  54.         ohos:top_margin="20px"
  55.         ohos:top_padding="10px"
  56.         />
  57.     <Button
  58.         ohos:id="$+id:button_btnUnsubscribeStep"
  59.         ohos:height="match_content"
  60.         ohos:width="100vp"
  61.         ohos:background_element="$graphic:background_ability_main"
  62.         ohos:layout_alignment="horizontal_center"
  63.         ohos:text="取消订阅步数"
  64.         ohos:text_color="#FFEEE6E6"
  65.         ohos:text_size="30"
  66.         ohos:top_margin="20px"
  67.         ohos:top_padding="10px"
  68.         />
  69.     <Text
  70.         ohos:id="$+id:text_step"
  71.         ohos:height="match_content"
  72.         ohos:width="120vp"
  73.         ohos:background_element="$graphic:background_rate_step"
  74.         ohos:layout_alignment="horizontal_center"
  75.         ohos:text="实时总步数"
  76.         ohos:text_alignment="horizontal_center"
  77.         ohos:text_size="30"
  78.         ohos:text_color="#FFEEE6E6"
  79.         ohos:top_margin="20px"
  80.         ohos:top_padding="10px"
  81.         />
  82. </DirectionalLayout>
复制代码
说明
布局文件中使用到的background_element样式,在entrysrcmainresourcesbasegraphic下有做定义,详情请参考完整代码。
   

5. 应用初始化      
添加权限及设置页面路由
为了保证应用的成功运行,需要在config.json中申请如下权限:
  1. "reqPermissions": [
  2.   {
  3.         "name": "ohos.permission.DISTRIBUTED_DATASYNC",
  4.   },
  5.   {
  6.         "name": "ohos.permission.READ_HEALTH_DATA",
  7.   },
  8.   {
  9.         "name": "ohos.permission.ACTIVITY_MOTION",
  10.   },
  11.   {
  12.         "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE",
  13.   },
  14.   {
  15.         "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO",
  16.   },
  17.   {
  18.         "name": "ohos.permission.GET_BUNDLE_INFO",
  19.   }
  20. ]
复制代码

此外,还需要在HealthWatchSlice.java的onStart()中动态添加权限,代码如下:
  1. if (verifySelfPermission("ohos.permission.ACTIVITY_MOTION") != 0) {
  2.     if (canRequestPermission("ohos.permission.ACTIVITY_MOTION")) {
  3.         requestPermissionsFromUser(new String[]{"ohos.permission.ACTIVITY_MOTION",
  4.             "ohos.permission.READ_HEALTH_DATA", "ohos.permission.DISTRIBUTED_DATASYNC"}, 1);
  5.     }
  6. }
复制代码

应用首次安装并启动后,会进入MainAbility的onStart()方法中,这里设置了页面路由,代码如下:
  1. super.setMainRoute(HealthWatchSlice.class.getName());
复制代码

手表FA页面加载
在HealthWatchSlice.java的onStart()方法中,首先调用了initViewAnnotation()方法,用来初始化页面上的组件,在相应的组件属性上添加@Bind注解即可完成注入,代码如下:
  1. private void initViewAnnotation() {
  2.     Field[] fields = getClass().getDeclaredFields();
  3.     for (Field field : fields) {
  4.         Bind bind = field.getAnnotation(Bind.class);
  5.             if (bind != null) {
  6.         
  7. if (bind.value() == -1) {
  8.         
  9.         LogUtils.error("TAG", "bind.value is must set!");
  10.         
  11.             return;
  12.         
  13. }
  14.         
  15.     try {
  16.         
  17.         field.setAccessible(true);
  18.         
  19.         field.set(this, findComponentById(bind.value()));
  20.         
  21.     } catch (IllegalAccessException e) {
  22.         
  23.         LogUtils.error("TAG", "IllegalAccessException happen");
  24.         
  25.     }
  26.             }
  27.      }
  28. }
复制代码

随后是初始化监听initListener(),这里主要是监听几个按钮的点击事件,也是应用内获取健康数据的途径,我们会在第六小节详细说明如何实现。

接下来是初始化心率和步数数据订阅的回调initCallback(),当检测到有步数和心率数据变化时,会进入回调函数,使用对应api接口获得相应的健康数据,并写入到分布式数据库中,我们会在第七小节详细说明如何实现。

最后是初始化分布式数据服务initDbManager(),有关分布式数据库的更多知识,可以参考如何使用分布式数据库
除此之外,在onStart中,我们还初始化了一个事件处理器,代码如下:
  1. myHandler = new MyEventHandler(EventRunner.current());
复制代码
6. 获取健康数据
在应用里,我们添加了订阅心率和订阅步数的Button,以及取消订阅心率和步数的Button,通过监听不同Button的点击事件,来实现不同的业务逻辑。以获取心率数据为例,当点击订阅心率Button时,根据指定的传感器类型,我们会收到对应的传感器数据,并进入相应回调函数去获取数据和处理数据,示例代码如下:

  1. private void initListener() {
  2.     subscribe.setClickedListener(va -> {
  3.         bodySensor = categoryBodyAgent.getSingleSensor(CategoryBody.SENSOR_TYPE_HEART_RATE);
  4.         if (bodySensor != null) {
  5.             categoryBodyAgent.setSensorDataCallback(bodyDataCallback, bodySensor, INTERVAL);
  6.             showTip("订阅心率成功");
  7.         }
  8.     });

  9.     unsubscribe.setClickedListener(va -> {
  10.         if (bodySensor != null) {
  11.             categoryBodyAgent.releaseSensorDataCallback(bodyDataCallback, bodySensor);
  12.             showTip("取消订阅心率成功");
  13.             rate.setText("实时心率");
  14.         }
  15.     });
  16. }
复制代码
7. 数据写入分布式数据库
上面我们提到在获取到健康数据后,会进入到相应的回调函数去处理数据,这里就来具体说下数据是如何进行处理的
我们将存在categoryBodyData中的数据取出,并使用myHandler投递任务,将心率和步数数据分别通过writeData()方法写入事先建立好的分布式数据库中,参考代码如下:
  1. private void initCallback() {
  2.     bodyDataCallback = new ICategoryBodyDataCallback() {
  3.         [url=home.php?mod=space&uid=2735960]@Override[/url]
  4.         public void onSensorDataModified(CategoryBodyData categoryBodyData) {
  5.             float[] values = categoryBodyData.getValues();
  6.             myHandler.postTask(new Runnable() {
  7.                 @Override
  8.                 public void run() {
  9.                     rate.setText(getNowTime() + regex + values[0]);
  10.                      // 过滤无效数据
  11.                      if (values[0] != TOP_HEART_RATE) {
  12.                          writeData(KeyEnum.RATE.getValue() + (UUID.randomUUID()),
  13.                          getNowTime() + regex + values[0]);
  14.                               // 数据异常时拉起其他设备PA
  15.                               if (values[0] < MIN_HEART_RATE || values[0] > MAX_HEART_RATE) {
  16.                               getUITaskDispatcher().asyncDispatch(new ScheduleRemoteAbilityTask(context));
  17.                             }
  18.                         }
  19.                     }
  20.                 });
  21.             }
  22.         };

  23.         motionDataCallback = new ICategoryMotionDataCallback() {
  24.             @Override
  25.             public void onSensorDataModified(CategoryMotionData categoryMotionData) {
  26.                 float[] values = categoryMotionData.getValues();
  27.                 myHandler.postTask(new Runnable() {
  28.                     @Override
  29.                     public void run() {
  30.                         // 数据写入到分布式数据库中
  31.                         step.setText(KeyEnum.STEP.getDesc() + values[0]);
  32.                         writeData(KeyEnum.STEP.getValue() + (UUID.randomUUID()),
  33.                                 getNowTime() + regex + values[0]);
  34.                 }
  35.             });
  36.         }
  37.     };
  38. }
复制代码

8. 心率异常拉起PA   
将心率数据写入到分布式数据库中时,我们实现了一个异常判断的逻辑:当数据大于或者小于我们规定的临界值时,便会开启一个异步任务去执行启动远端设备PA的动作,在initCallback方法中有如下代码:
  1. if (values[0] < MIN_HEART_RATE || values[0] > MAX_HEART_RATE) {
  2.     getUITaskDispatcher().asyncDispatch(new ScheduleRemoteAbilityTask(context));
  3. }

  4. 在ScheduleRemoteAbilityTask.java中有如下代码:


  5. @Override
  6. public void run() {
  7.     List<DeviceInfo> onlineDevices = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
  8.     // 判断组网设备是否为空
  9.     if (onlineDevices.isEmpty()) {
  10.         iSelectResult.onSelectResult(null, context);
  11.             return ;
  12.     }
  13.     // 获取设备信息
  14.     ArrayList<String> deviceIds = new ArrayList<>();
  15.     onlineDevices.forEach((device) -> {
  16.         //此处设备名称需要开发者根据实际情况进行替换
  17.         if ("Test Phone".equals(device.getDeviceName())) {
  18.             deviceIds.add(device.getDeviceId());
  19.             }
  20.     });
  21.     if(deviceIds.size() == 0){
  22.     return;
  23.     }
  24.     String selectDeviceId = deviceIds.get(0);
  25.     // 根据设备id拉起指定PA
  26.     iSelectResult.onSelectResult(selectDeviceId, context);
  27.    }
  28. }
复制代码
说明
以上代码仅demo演示参考使用
9. 最终效果  
最终智能穿戴侧的应用完成了获取心率、步数等健康数据,并将这些数据定时推送到手机上,而且在心率数据异常时,会拉起远端设备的PA,实现如下通知手机的效果:
智能穿戴端订阅数据:

手机端收到异常通知拉起FA:


10. 恭喜你
恭喜你已经完成了分布式运动健康应用的智能穿戴侧Codelab的开发,并且学到了:
  • 手表侧获取健康数据
  • 手表数据同步到手机
  • 心率异常拉起手机PA并在手机侧通知栏显示通知
  • 线程间通信开发 EventHandler

11. 参考


回帖

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
链接复制成功,分享给好友