#星光计划2.0#HarmonyOS分布式应用农业大棚数据监测解读

一卷旌收千骑虏,万全身出百重围。这篇文章主要讲述#星光计划2.0#HarmonyOS分布式应用农业大棚数据监测解读相关的知识,希望能为你提供帮助。
【本文正在参与51CTO HarmonyOS技术社区创作者激励计划-星光计划2.0】

#星光计划2.0#HarmonyOS分布式应用农业大棚数据监测解读

文章图片

引言分布式数据服务(Distributed Data Service,DDS) 为应用程序提供不同设备间数据库数据分布式的能力。通过调用分布式数据接口,应用程序将数据保存到分布式数据库中。通过结合帐号、应用和数据库三元组,分布式数据服务对属于不同应用的数据进行隔离,保证不同应用之间的数据不能通过分布式数据服务互相访问。在通过可信认证的设备间,分布式数据服务支持应用数据相互同步,为用户提供在多种终端设备上最终一致的数据访问体验。
功能介绍此次基于HarmonyOS的分布式数据服务能力,一方面模拟农业大棚的温度、湿度、二氧化碳浓度等数据的采集,并在手机端进行采集数据展示;另一方面,可以把手机端的数据,迁移到其他设备(如智慧屏、手表、PAD等),可以做一些数据分析展示。
前提:在不同设备之间,要实现分布式数据服务的同步能力,需要同一个华为账号登录、并一个应用包名、同一个网络之间进行,也可以是两个设备同时开启蓝牙。
开发指南 1.在config.json中添加permisssion权限// 添加在abilities同一目录层级
"reqPermissions": ["name": "ohos.permission.DISTRIBUTED_DATASYNC"]

2.在MainAbility中添加权限
@Override public void onStart(Intent intent) super.onStart(intent); super.setMainRoute(MainAbilitySlice.class.getName()); //实现Ability的代码中显式声明需要使用多设备协同访问的权限 requestPermissionsFromUser(new String[] "ohos.permission.DISTRIBUTED_DATASYNC", 0);

3.根据配置构造分布式数据库管理类实例KvManager以及创建分布式数据库对象SingleKvStore//实现数据库的初始化
// 初入的参数context: Context context = getApplicationContext()获得;storeId为分布式数据库id,String类型,可自行定义,例如“testApp”。 public static SingleKvStore initOrGetDB(Context context, String storeId) KvManagerConfig kvManagerConfig = new KvManagerConfig(context); kvManager = KvManagerFactory.getInstance().createKvManager(kvManagerConfig); Options options = new Options(); options.setCreateIfMissing(true) .setEncrypt(false) .setKvStoreType(KvStoreType.SINGLE_VERSION) //数据库类型:单版本分布式数据库 .setAutoSync(true); //自动同步为true,手动同步为false singleKvStore = kvManager.getKvStore(options, storeId); return singleKvStore;

4.将数据写入单版本分布式数据库
//以key-value形式存储到分布式数据库 try //将采集的数据以key-value形式存入分布式数据库中 DataModle dataModle=new DataModle(); dataModle.setTemp(temp); dataModle.setHumi(humi); dataModle.setCo2(co2); String jsonString= ZSONObject.toZSONString(dataModle); singleKvStore.putString("data",jsonString); catch (KvStoreException e) LogUtils.debug(TAG, "DataServiceAbility::updateData()"+e.getMessage());

5.订阅分布式数据变化。客户端需要实现KvStoreObserver接口,监听数据变化```try
//订阅类型SubscribeType.SUBSCRIBE_TYPE_ALL意思可以同步到本机和其他外围设备
innerKvStoreObserver = new InnerKvStoreObserver();
singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_ALL, innerKvStoreObserver);
catch (KvStoreException e)
e.printStackTrace();

public class InnerKvStoreObserver implements KvStoreObserver
@Override
public void onChange(ChangeNotification changeNotification)
//刷新页面上的数据,同样有一个坑,onChange方法实质上,在一个子线程里执行
MainAbilitySlice.taskDispatcher.asyncDispatch(() ->
//在这里执行页面ui组件的显示刷新
flushUIData();
);


## 6.获取分布式数据库数据

//查询分布式数据的数据,获取数据可以通过get(String key)或者 getEntries(String key)方法获取数据
List< Entry> entries = singleKvStore.getEntries(" data" );
if (entries.size() > 0)
ZSONObject zsonObject = ZSONObject.stringToZSON(entries.get(0).getValue().getString());
double temp = zsonObject.getDouble(" temp" );
double humi = zsonObject.getDouble(" humi" );
double co2 = zsonObject.getDouble(" co2" );
String strTemp = String.format(" %.1f" , temp);
String strHumi = String.format(" %.1f" , humi);
String strCO2 = String.format(" %.1f" , co2);
tvTemp.setText(strTemp+" ℃" );
tvHumi.setText(strHumi+" %RH" );
tvCo2.setText(strCO2+" ppm" );

## 7.解除订阅,一般在页面销毁时调用,也就是在onStop()中调用

if (singleKvStore != null)
singleKvStore.unSubscribe(innerKvStoreObserver);

## 8.同步数据到其他设备。获取已连接的设备列表,选择同步方式进行数据同步 ```//查看在线设备 List< DeviceInfo> onlineDevices = DeviceManager .getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE); List< String> deviceIdList = new ArrayList< > (); for (DeviceInfo deviceInfo : deviceInfoList) deviceIdList.add(deviceInfo.getId()); //迁移到指定设备,传入设备ID列表 singleKvStore.sync(deviceIdList, SyncMode.PUSH_ONLY);

项目中采用在后台service中开启定时任务,模拟农业大棚采集数据,实时保存数据到分布式数据库,然后在主界面,监听数据变化,实时更新数据。当选择迁移的设备时,就可以把数据迁移到相应设备。
#星光计划2.0#HarmonyOS分布式应用农业大棚数据监测解读

文章图片

手机侧应用刚打开时界面
#星光计划2.0#HarmonyOS分布式应用农业大棚数据监测解读

文章图片

TV侧应用刚打开时界面
#星光计划2.0#HarmonyOS分布式应用农业大棚数据监测解读

文章图片

点击右上角迁移按钮,并选择迁移设备(P40-0036)
#星光计划2.0#HarmonyOS分布式应用农业大棚数据监测解读

文章图片

#星光计划2.0#HarmonyOS分布式应用农业大棚数据监测解读

文章图片

迁移后的左侧设备数据和右侧设备数据就会同步一致
附上源码 手机端 1. PhoneAbilitySlice
public class PhoneAbilitySlice extends AbilitySlice private SingleKvStore singleKvStore; private InnerKvStoreObserver innerKvStoreObserver; private Intent serviceIntent; private Text tvTemp; private Text tvHumi; private Text tvCo2; private DeviceData chooseDevice; private DevicesProvider devicesProvider; private CommonDialog commonDialog; private String TAG="MainAbilitySlice"; private List< String> deviceIdList; @Override public void onStart(Intent intent) super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_main); //设置沉浸式状态栏getWindow().addFlags(WindowManager.LayoutConfig.MARK_TRANSLUCENT_STATUS); initView(); initService(); try //获取数据库 singleKvStore = DBUtils.initOrGetDB(this, DBUtils.KV_STORE_NAME); innerKvStoreObserver = new InnerKvStoreObserver(); //订阅分布式数据库 singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_ALL, innerKvStoreObserver); catch (KvStoreException e) LogUtils.debug(TAG, "MainAbilitySlice::onStart/"+e.getMessage()); private void initService() //启动ServiceAbility serviceIntent = new Intent(); Operation operation = new Intent.OperationBuilder() .withDeviceId("") .withBundleName("com.isoftstone.distributeddata") .withAbilityName("com.isoftstone.distributeddata.DataServiceAbility") .build(); serviceIntent.setOperation(operation); startAbility(serviceIntent); private void initView() tvTemp = (Text) findComponentById(ResourceTable.Id_text_temp); tvHumi = (Text) findComponentById(ResourceTable.Id_text_humi); tvCo2 = (Text) findComponentById(ResourceTable.Id_text_co2); Button bt = (Button) findComponentById(ResourceTable.Id_bt_continue); bt.setClickedListener(component -> if (component.getId() == ResourceTable.Id_bt_continue) //查看在线设备 List< DeviceInfo> onlineDevices = DeviceManager .getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE); List< DeviceData> deviceDatas = new ArrayList< > (); if (onlineDevices == null || onlineDevices.size() < 1) CustomerDialog.showToastDialog(getAbility(), "无组网在线设备"); else for (DeviceInfo deviceInfo : onlineDevices) deviceDatas.add(new DeviceData(false, deviceInfo)); showDevices(deviceDatas); ); private void showDevices(List< DeviceData> deviceDatas) chooseDevice = null; commonDialog = new CommonDialog(this); Component component = LayoutScatter.getInstance(this) .parse(ResourceTable.Layout_dialog_layout_device, null, true); ListContainer listContainer = (ListContainer) component.findComponentById(ResourceTable.Id_list_container_device); devicesProvider = new DevicesProvider(this, deviceDatas); listContainer.setItemProvider(devicesProvider); listContainer.setItemClickedListener((listContainer1, component1, position, l) -> chooseDevice = deviceDatas.get(position); for (int i = 0; i < deviceDatas.size(); i++) if (i == position) deviceDatas.set(i, new DeviceData(true, deviceDatas.get(i).getDeviceInfo())); else deviceDatas.set(i, new DeviceData(false, deviceDatas.get(i).getDeviceInfo())); devicesProvider = new DevicesProvider(this, deviceDatas); listContainer1.setItemProvider(devicesProvider); ); Text tvCancle = (Text) component.findComponentById(ResourceTable.Id_operate_no); Text tvSure = (Text) component.findComponentById(ResourceTable.Id_operate_yes); tvCancle.setClickedListener(component12 -> commonDialog.destroy()); tvSure.setClickedListener(component13 -> if (chooseDevice == null) CustomerDialog.showToastDialog(this, "请选择设备"); else try deviceIdList=new ArrayList< > (); deviceIdList.add(chooseDevice.getDeviceInfo().getDeviceId()); //手动同步的设备列表 singleKvStore.sync(deviceIdList, SyncMode.PUSH_ONLY); commonDialog.destroy(); catch (IllegalStateException e) //流转异常捕获,防止进程存在再次发起流转 LogUtils.debug(TAG, "MainAbilitySlice::singleKvStore.sync()/"+e.getMessage()); ); commonDialog.setSize(MATCH_PARENT, MATCH_CONTENT); commonDialog.setAlignment(LayoutAlignment.BOTTOM); commonDialog.setCornerRadius(10); commonDialog.setAutoClosable(true); commonDialog.setContentCustomComponent(component); commonDialog.setTransparent(true); commonDialog.show(); public class InnerKvStoreObserver implements KvStoreObserver @Override public void onChange(ChangeNotification changeNotification) //onChange方法实质上,在一个子线程里执行 getUITaskDispatcher().asyncDispatch(() -> //在这里执行页面ui组件的显示刷新 asyncUpdateData(); ); public void asyncUpdateData() //查询分布式数据的数据 List< Entry> entries = singleKvStore.getEntries("data"); if (entries.size() > 0) ZSONObject zsonObject = ZSONObject.stringToZSON(entries.get(0).getValue().getString()); double temp = zsonObject.getDouble("temp"); double humi = zsonObject.getDouble("humi"); double co2 = zsonObject.getDouble("co2"); String strTemp = String.format("%.1f", temp); String strHumi = String.format("%.1f", humi); String strCO2 = String.format("%.1f", co2); tvTemp.setText(strTemp+"℃"); tvHumi.setText(strHumi+"%RH"); tvCo2.setText(strCO2+"ppm"); //手动同步的设备列表 if(singleKvStore!=null) if(deviceIdList!=null& & deviceIdList.size()> 0) singleKvStore.sync(deviceIdList, SyncMode.PUSH_ONLY); @Override public void onActive() super.onActive(); @Override public void onForeground(Intent intent) super.onForeground(intent); @Override protected void onStop() super.onStop(); //销毁service stopAbility(serviceIntent); //删除数据库 DBUtils.clearDB(); //解除订阅 if (singleKvStore != null) if(innerKvStoreObserver!=null) singleKvStore.unSubscribe(innerKvStoreObserver);

2. DataServiceAbility
public class DataServiceAbility extends Ability private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo"); private SingleKvStore singleKvStore; private NotificationRequest request; private Timer mTimer; private TimerTask mTimerTask; private String TAG="DataServiceAbility"; @Override public void onStart(Intent intent) LogUtils.debug(TAG, "DataServiceAbility::onStart"); super.onStart(intent); //创建前台service createForeService(); try singleKvStore = DBUtils.initOrGetDB(this, DBUtils.KV_STORE_NAME); catch (Exception e) LogUtils.debug(TAG, "DataServiceAbility::onStart()"+e.getMessage()); //每隔10秒,模拟传感器采集一次大棚数据。 if(mTimer==null) mTimer=new Timer(); if(mTimerTask==null) mTimerTask=new TimerTask() @Override public void run() updateData(); ; mTimer.schedule(mTimerTask,0,1000*10); private void updateData() //获取随机温度0-100; double temp= new Random().nextDouble()*100; //获取随机湿度0-100; double humi= new Random().nextDouble()*100; //获取随机CO2浓度0-1000 double co2= new Random().nextDouble()*1000; try //将采集的数据以key-value形式存入分布式数据库中 DataModle dataModle=new DataModle(); dataModle.setTemp(temp); dataModle.setHumi(humi); dataModle.setCo2(co2); String jsonString= ZSONObject.toZSONString(dataModle); singleKvStore.putString("data",jsonString); catch (KvStoreException e) LogUtils.debug(TAG, "DataServiceAbility::updateData()"+e.getMessage()); private void createForeService() // 创建通知,其中1005为notificationId request = new NotificationRequest(1005); NotificationRequest.NotificationNormalContent content = new NotificationRequest.NotificationNormalContent(); content.setTitle("农业大棚").setText("数据采集服务开启中"); NotificationRequest.NotificationContent notificationContent = new NotificationRequest.NotificationContent(content); request.setContent(notificationContent); //绑定通知,1005为创建通知时传入的notificationId keepBackgroundRunning(1005, request); @Override public void onBackground() super.onBackground(); LogUtils.debug(TAG, "DataServiceAbility::onBackground()"); @Override public void onStop() super.onStop(); if (mTimerTask != null) mTimerTask.cancel(); mTimerTask=null; if (mTimer != null) mTimer.cancel(); mTimer=null; //停止前台Service。 cancelBackgroundRunning(); LogUtils.debug(TAG, "DataServiceAbility::onStop()"); @Override public void onCommand(Intent intent, boolean restart, int startId) @Override public IRemoteObject onConnect(Intent intent) return null; @Override public void onDisconnect(Intent intent)

3.DBUtils
public class DBUtils private static KvManager kvManager; private static SingleKvStore singleKvStore; public static String KV_STORE_NAME="farm_data"; //具体的实现数据库的初始化 public static SingleKvStore initOrGetDB(Context context, String storeId) KvManagerConfig kvManagerConfig = new KvManagerConfig(context); kvManager = KvManagerFactory.getInstance().createKvManager(kvManagerConfig); Options options = new Options(); options.setCreateIfMissing(true) .setEncrypt(false) .setKvStoreType(KvStoreType.SINGLE_VERSION) .setAutoSync(false); //自动同步为true,手动同步为false singleKvStore = kvManager.getKvStore(options, storeId); return singleKvStore; // 如果数据库中的字段有修改,只能先关闭,后删除,然后重新创建才生效 public static void clearDB() kvManager.closeKvStore(singleKvStore); kvManager.deleteKvStore(KV_STORE_NAME);

4. MainAbility
```public class MainAbility extends Ability @Override
br/>@Override
super.onStart(intent);
super.setMainRoute(PhoneAbilitySlice.class.getName());
//实现Ability的代码中显式声明需要使用多设备协同访问的权限
requestPermissionsFromUser(new String[]" ohos.permission.DISTRIBUTED_DATASYNC" ,, 0);


### 5. DataModle

public class DataModle
private double temp;
private double humi;
private double co2;
public double getTemp()
return temp;

public void setTemp(double temp)
this.temp = temp;

public double getHumi()
return humi;

public void setHumi(double humi)
this.humi = humi;

public double getCo2()
return co2;

public void setCo2(double co2)
this.co2 = co2;


## 6. DeviceData

public class DeviceData
private boolean isChecked;
private DeviceInfo deviceInfo;
/**
  • DeviceData
  • @param isChecked isChecked
  • @param deviceInfo deviceInfo
    */
    public DeviceData(boolean isChecked, DeviceInfo deviceInfo)
    this.isChecked = isChecked;
    this.deviceInfo = deviceInfo;

    public DeviceInfo getDeviceInfo()
    return deviceInfo;

    public void setDeviceInfo(DeviceInfo deviceInfo)
    this.deviceInfo = deviceInfo;

    public boolean isChecked()
    return isChecked;

    public void setChecked(boolean checked)
    isChecked = checked;


    ### 7. DevicesProvider

    public class DevicesProvider extends BaseItemProvider
    private List< DeviceData> data;
    private LayoutScatter layoutScatter;
    public DevicesProvider(Context context, List< DeviceData> data)
    this.data = https://www.songbingjia.com/android/data;
    this.layoutScatter = LayoutScatter.getInstance(context);

    @Override
    public int getCount()
    return data.size();

    @Override
    public Object getItem(int i)
    return data.get(i);

    @Override
    public long getItemId(int i)
    return i;

    @Override
    public Component getComponent(int position, Component component,
    ComponentContainer componentContainer)
    ViewHolder viewHolder;
    // component相当于android中的view,其他的和Android中ListView的适配器adapter差不多。
    // 名字区别也不大,不过Android中ListView基本被淘汰了。
    if (component == null)
    component = layoutScatter.parse(ResourceTable.Layout_dialog_device_item, null, false);
    viewHolder = new ViewHolder();
    viewHolder.imgType = (Image) component.findComponentById(ResourceTable.Id_item_type);
    viewHolder.tvName = (Text) component.findComponentById(ResourceTable.Id_item_name);
    viewHolder.imgCheck = (Image) component.findComponentById(ResourceTable.Id_item_check);
    component.setTag(viewHolder);
    else
    viewHolder = (ViewHolder) component.getTag();

    DeviceData deviceData = https://www.songbingjia.com/android/data.get(position);
    DeviceType deviceType=deviceData.getDeviceInfo().getDeviceType();
    switch (deviceType)
    case SMART_WATCH:
    viewHolder.imgType.setPixelMap(ResourceTable.Media_dv_watch);
    break;
    case SMART_PAD:
    viewHolder.imgType.setPixelMap(ResourceTable.Media_dv_pad);
    break;
    case SMART_PHONE:
    viewHolder.imgType.setPixelMap(ResourceTable.Media_dv_phone);
    break;

    viewHolder.tvName.setText(deviceData.getDeviceInfo().getDeviceName());
    if(deviceData.isChecked())
    viewHolder.imgCheck.setImageAndDecodeBounds(ResourceTable.Media_check2); elseviewHolder.imgCheck.setImageAndDecodeBounds(ResourceTable.Media_uncheck2);

    return component;

    /**
    • 类似于Android中的listView缓存。 将已经显示在屏幕上的item缓存在ViewHolder中,下次再次出现直接从缓存中读取
      */
      private static class ViewHolder
      private Image imgType;
      private Text tvName;
      private Image imgCheck;

      ### 8. CustomerDialog

      public class CustomerDialog
      public static void showToastDialog(Context context, String str)
      DirectionalLayout toastLayout = (DirectionalLayout) LayoutScatter.getInstance(context)
      .parse(ResourceTable.Layout_toast_dialog, null, false);
      Text text = (Text) toastLayout.findComponentById(ResourceTable.Id_toast);
      text.setText(str);
      new ToastDialog(context)
      .setContentCustomComponent(toastLayout)
      .setSize(DirectionalLayout.LayoutConfig.MATCH_CONTENT,
      DirectionalLayout.LayoutConfig.MATCH_CONTENT)
      .setAlignment(LayoutAlignment.CENTER)
      .show();


      ### 9. MyApplication ```public class MyApplication extends AbilityPackage @Override public void onInitialize() super.onInitialize();

### 10. config.json 文件 ``` "app": "bundleName": " com.isoftstone.distributeddata ", "vendor": "isoftstone", "version": "code": 1000000, "name": "1.0.0", "deviceConfig": , "module": "package": "com.isoftstone.distributeddata", "name": ".MyApplication", "mainAbility": "com.isoftstone.distributeddata.MainAbility", "deviceType": [ "phone" ], "distro": "deliveryWithInstall": true, "moduleName": "entry", "moduleType": "entry", "installationFree": false , "reqPermissions": ["name": "ohos.permission.DISTRIBUTED_DATASYNC" ,//添加此权限,才能查找分布式在线设备 "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO" ,//将service设置为前台服务,需要添加的权限 "name": "ohos.permission.KEEP_BACKGROUND_RUNNING" ], "abilities": ["skills": ["entities": [ "entity.system.home" ], "actions": [ "action.system.home" ]], "orientation": "unspecified", "name": "com.isoftstone.distributeddata.MainAbility", "icon": "$media:icon", "description": "$string:mainability_description", "label": "$string:entry_MainAbility", "type": "page", "launchType": "standard" ,"name": "com.isoftstone.distributeddata.DataServiceAbility", "icon": "$media:icon", "description": "$string:dataserviceability_description", "type": "service", "visible": true, "backgroundModes": [ "dataTransfer", "location" ]], "metaData": "customizeData": ["name": "hwc-theme", "value": "androidhwext:style/Theme.Emui.NoTitleBar", "extra": ""]

11.ability_main.xml布局文件
< ?xml version="1.0" encoding="utf-8"?> < DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos: ohos: ohos:orientation="vertical"> < DependentLayout ohos: ohos: ohos:orientation="horizontal" ohos:top_padding="50vp" ohos:bottom_padding="20vp" ohos:left_padding="20vp" ohos:right_padding="20vp" ohos:background_element="$graphic:background_ability_main"> < Text ohos: ohos: ohos:text="农业大棚数据监测" ohos:text_size="26vp" ohos:text_color="#ffffff" ohos:center_in_parent="true"/> < Button ohos:id="$+id:bt_continue" ohos: ohos: ohos:background_element="$media:conti" ohos:align_parent_right="true" ohos:vertical_center="true"/> < /DependentLayout> < DependentLayout ohos: ohos: ohos:background_element="#f4f5f7" ohos:left_padding="15vp" ohos:right_padding="15vp" ohos:top_padding="10vp" ohos:bottom_padding="10vp"> < Text ohos: ohos: ohos:text="温度:" ohos:text_color="#000000" ohos:text_size="18fp" ohos:vertical_center="true"/> < Text ohos:id="$+id:text_temp" ohos: ohos: ohos:text="数据正在采集 " ohos:text_color="#00ff00" ohos:text_size="18fp" ohos:left_margin="50vp" ohos:vertical_center="true"/> < /DependentLayout> < Component ohos: ohos: ohos:background_element="#FFFFFF"/> < DependentLayout ohos: ohos: ohos:background_element="#f4f5f7" ohos:left_padding="15vp" ohos:right_padding="15vp" ohos:top_padding="10vp" ohos:bottom_padding="10vp"> < Text ohos: ohos: ohos:text="湿度:" ohos:text_color="#000000" ohos:text_size="18fp" ohos:vertical_center="true"/> < Text ohos:id="$+id:text_humi" ohos: ohos: ohos:text="数据正在采集 " ohos:text_color="#00ff00" ohos:text_size="18fp" ohos:left_margin="50vp" ohos:vertical_center="true"/> < /DependentLayout> < Component ohos: ohos: ohos:background_element="#FFFFFF"/> < DependentLayout ohos: ohos: ohos:background_element="#f4f5f7" ohos:left_padding="15vp" ohos:right_padding="15vp" ohos:top_padding="10vp" ohos:bottom_padding="10vp"> < Text ohos: ohos: ohos:text="CO2:" ohos:text_color="#000000" ohos:text_size="18fp" ohos:vertical_center="true"/> < Text ohos:id="$+id:text_co2" ohos: ohos: ohos:text="数据正在采集 " ohos:text_color="#00ff00" ohos:text_size="18fp" ohos:left_margin="50vp" ohos:vertical_center="true"/> < /DependentLayout> < /DirectionalLayout>

12. dialog_device_item.xml
< ?xml version="1.0" encoding="utf-8"?> < DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos: ohos: ohos:orientation="horizontal"> < Image ohos:id="$+id:item_type" ohos: ohos: ohos:image_src="https://www.songbingjia.com/android/$media:dv_phone" ohos:layout_alignment="vertical_center" ohos:left_margin="10vp" ohos:scale_mode="inside"/> < Text ohos:id="$+id:item_name" ohos: ohos: ohos:max_text_lines="1" ohos:text="Huawei P40" ohos:text_alignment="vertical_center" ohos:text_size="15fp" ohos:weight="1"/> < Image ohos:id="$+id:item_check" ohos: ohos: ohos:image_src="https://www.songbingjia.com/android/$media:uncheck2" ohos:layout_alignment="vertical_center" ohos:right_margin="15vp"/> < /DirectionalLayout>

13.dialog_layout_device.xml
```< ?xml version=" 1.0" encoding=" utf-8" ?>
< DirectionalLayout
xmlns:ohos=" http://schemas.huawei.com/res/ohos"
ohos:height=" 340vp"
ohos:width=" match_parent"
ohos:orientation=" vertical" >
< DirectionalLayout ohos: ohos: ohos:background_element="$graphic:background_white_radius_10" ohos:bottom_margin="50vp" ohos:left_margin="20vp" ohos:orientation="vertical" ohos:right_margin="20vp"> < Text ohos: ohos: ohos:left_padding="20vp" ohos:text="迁移到其他设备" ohos:text_alignment="vertical_center" ohos:text_color="#000000" ohos:text_size="16fp"/> < ListContainer ohos:id="$+id:list_container_device" ohos: ohos: ohos:weight="1"/> < DirectionalLayout ohos: ohos: ohos:orientation="horizontal"> < Text ohos:id="$+id:operate_no" ohos: ohos: ohos:text="取消" ohos:text_alignment="center" ohos:text_color="#1e90ff" ohos:text_size="17fp" ohos:weight="1"/> < Component ohos: ohos: ohos:background_element="#cccccc" ohos:layout_alignment="vertical_center"/> < Text ohos:id="$+id:operate_yes" ohos: ohos: ohos:text="确定" ohos:text_alignment="center" ohos:text_color="#1e90ff" ohos:text_size="17fp" ohos:weight="1"/> < /DirectionalLayout> < /DirectionalLayout>

< /DirectionalLayout>
### 14. toast_dialog.xml ```< ?xml version="1.0" encoding="utf-8"?> < DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos: ohos: ohos:orientation="vertical" ohos:background_element="$graphic:background_gray_circle"> < Text ohos:id="$+id:toast" ohos: ohos: ohos:left_padding="16vp" ohos:right_padding="16vp" ohos:top_padding="4vp" ohos:bottom_padding="4vp" ohos:layout_alignment="center" ohos:text_size="20fp"/> < /DirectionalLayout>

TV端 1. TVAbilitySlice
```public class TVAbilitySlice extends AbilitySlice
private Text tvTemp; private Text tvTempMax; private Text tvTempMin; private Text tvHumi; private Text tvHumiMax; private Text tvHumiMin; private Text tvCgas; private Text tvCgasMax; private Text tvCgasMin; private Text tvTempStatus; private Text tvHumiStatus; private Text tvCgasStatus; private ProgressBar rgbTem; private ProgressBar rgbHumi; private ProgressBar rgbCgas; private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x0001, "my_log"); private double temp; private double humi; private double cGas; private SingleKvStore singleKvStore; private KvStoreObserverClient kvStoreObserverClient; @Override public void onStart(Intent intent) super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_tv); //设置沉浸式状态栏 getWindow().addFlags(WindowManager.LayoutConfig.MARK_TRANSLUCENT_STATUS); tvTemp = (Text) findComponentById(ResourceTable.Id_tvTemp); tvTempMax = (Text) findComponentById(ResourceTable.Id_tvMaxTemp); tvTempMin = (Text) findComponentById(ResourceTable.Id_tvMinTemp); rgbTem = (RoundProgressBar) findComponentById(ResourceTable.Id_rgb_tem); tvHumi = (Text) findComponentById(ResourceTable.Id_tvHumi); tvHumiMax = (Text) findComponentById(ResourceTable.Id_tvMaxHumi); tvHumiMin = (Text) findComponentById(ResourceTable.Id_tvMinHumi); rgbHumi = (RoundProgressBar) findComponentById(ResourceTable.Id_rgb_humi); tvCgas = (Text) findComponentById(ResourceTable.Id_tvCgas); tvCgasMax = (Text) findComponentById(ResourceTable.Id_tvMaxCgas); tvCgasMin = (Text) findComponentById(ResourceTable.Id_tvMinCgas); rgbCgas = (RoundProgressBar) findComponentById(ResourceTable.Id_rgb_gas); tvTempStatus = (Text) findComponentById(ResourceTable.Id_tvTempStatus); tvHumiStatus = (Text) findComponentById(ResourceTable.Id_tvHumiStatus); tvCgasStatus = (Text) findComponentById(ResourceTable.Id_tvCgasStatus); try KvManagerConfig config = new KvManagerConfig(getContext()); KvManager kvManager = KvManagerFactory.getInstance().createKvManager(config); Options CREATE = new Options(); CREATE.setCreateIfMissing(true).setEncrypt(false) .setKvStoreType(KvStoreType.SINGLE_VERSION) .setAutoSync(true); singleKvStore = kvManager.getKvStore(CREATE, DBUtils.KV_STORE_NAME); kvStoreObserverClient = new KvStoreObserverClient(); singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_ALL, kvStoreObserverClient); catch (Exception e) e.printStackTrace(); private class KvStoreObserverClient implements KvStoreObserver @Override public void onChange(ChangeNotification notification) //onChange方法实质上,在一个子线程里执行 getUITaskDispatcher().asyncDispatch(() -> //在这里执行页面ui组件的显示刷新 asyncUpdateData(); ); public void asyncUpdateData() //查询分布式数据的数据 List< Entry> entries = singleKvStore.getEntries("data"); if (entries.size() > 0) ZSONObject zsonObject = ZSONObject.stringToZSON(entries.get(0).getValue().getString()); double temp = zsonObject.getDouble("temp"); double humi = zsonObject.getDouble("humi"); double co2 = zsonObject.getDouble("co2"); String strTemp = String.format("%.1f", temp); String strHumi = String.format("%.1f", humi); String strCO2 = String.format("%.1f", co2); initView(strTemp,strHumi,strCO2); private void initView(String strTemp,String strHumi,String strCO2) temp = Double.valueOf(strTemp); int tempMax = 45; int tempMin = -10; humi = Double.valueOf(strHumi); int humiMax = 70; int humiMin = 10; cGas = Double.valueOf(strCO2); int cGasMax = 1000; if (temp > -100) if (temp > tempMax || temp < tempMin) tvTemp.setTextColor(Color.RED); tvTempStatus.setText("异常"); tvTempStatus.setTextColor(Color.RED); rgbTem.setProgressColor(Color.RED); else tvTemp.setTextColor(Color.GREEN); tvTempStatus.setText("正常"); tvTempStatus.setTextColor(Color.GREEN); rgbTem.setProgressColor(Color.GREEN); else tvTemp.setTextColor(Color.BLACK); tvTempStatus.setTextColor(Color.BLACK); tvTempStatus.setText("未知"); rgbTem.setProgressColor(Color.GREEN); tvTempMax.setText(tempMax + "℃"); tvTempMin.setText(tempMin + "℃"); if (humi > -100) if (humi > humiMax || humi < humiMin) tvHumi.setTextColor(Color.RED); tvHumiStatus.setText("异常"); tvHumiStatus.setTextColor(Color.RED); rgbHumi.setProgressColor(Color.RED); else tvHumi.setTextColor(Color.GREEN); tvHumiStatus.setText("正常"); tvHumiStatus.setTextColor(Color.GREEN); rgbHumi.setProgressColor(Color.GREEN); else tvHumi.setTextColor(Color.BLACK); tvHumiStatus.setText("未知"); tvHumiStatus.setTextColor(Color.BLACK); rgbHumi.setProgressColor(Color.GREEN); tvHumiMax.setText(humiMax + "% RH"); tvHumiMin.setText(humiMin + "% RH"); if (cGas > -100) if (cGas > cGasMax) tvCgas.setTextColor(Color.RED); tvCgasStatus.setText("异常"); tvCgasStatus.setTextColor(Color.RED); rgbCgas.setProgressColor(Color.RED); else tvCgas.setTextColor(Color.GREEN); tvCgasStatus.setText("正常"); tvCgasStatus.setTextColor(Color.GREEN); else tvCgas.setTextColor(Color.BLACK); tvCgasStatus.setText("未知"); tvCgasStatus.setTextColor(Color.BLACK); rgbCgas.setProgressColor(Color.GREEN); tvCgasMax.setText(cGasMax + " ppm"); tvCgasMin.setText(0 + " ppm"); if (temp < = -100) tvTemp.setText("未知"); rgbTem.setProgressValue(0); else tvTemp.setText(temp + "℃"); rgbTem.setProgressValue((int) temp); if (humi < = -100) tvHumi.setText("未知"); rgbHumi.setProgressValue(0); else tvHumi.setText(humi + "% RH"); rgbHumi.setProgressValue((int) humi); if (cGas < = -100) tvCgas.setText("未知"); rgbCgas.setProgressValue(0); else tvCgas.setText(cGas + " ppm"); rgbCgas.setProgressValue((int) cGas); @Override protected void onStop() super.onStop(); //解除订阅

//if (singleKvStore != null)
//if(kvStoreObserverClient!=null)
//singleKvStore.unSubscribe(kvStoreObserverClient);
//
//


### 2. TVAbility

public class TVAbility extends Ability
@Override
public void onStart(Intent intent)
super.onStart(intent);
super.setMainRoute(TVAbilitySlice.class.getName());
//实现Ability的代码中显式声明需要使用多设备协同访问的权限
requestPermissionsFromUser(new String[]" ohos.permission.DISTRIBUTED_DATASYNC" ,, 0);


### 3. ability_tv.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:orientation=" vertical"
ohos:background_element=" $media:haibao" >
【#星光计划2.0#HarmonyOS分布式应用农业大棚数据监测解读】< Text
ohos:height=" match_content"
ohos:width=" match_content"
ohos:text=" 大棚数据监测"
ohos:text_color=" #ffffff"
ohos:text_size=" 26vp"
ohos:layout_alignment=" center"
ohos:top_margin=" 50vp" />
< DependentLayout
ohos:height=" match_parent"
ohos:width=" match_parent" >
< DirectionalLayout ohos: ohos: ohos:left_padding="10vp" ohos:right_padding="10vp" ohos:center_in_parent="true" ohos:orientation="horizontal"> < DirectionalLayout ohos: ohos: ohos:weight="1" ohos:layout_alignment="center" ohos:orientation="vertical"> < Text ohos: ohos: ohos:text="温度数据" ohos:text_size="20vp" ohos:padding="5vp" ohos:text_color="#ffffff" ohos:layout_alignment="center"/> < DirectionalLayout ohos: ohos: ohos:background_element="#ffffff" ohos:top_margin="15vp" ohos:left_margin="20vp" ohos:right_margin="20vp" ohos:layout_alignment="center" ohos:bottom_margin="15vp" ohos:visibility="invisible"/> < DirectionalLayout ohos: ohos: ohos:layout_alignment="center" ohos:orientation="horizontal"> < DirectionalLayout ohos: ohos: ohos:orientation="vertical"> < Text ohos: ohos: ohos:text="最高温度阀值:" ohos:text_size="16vp" ohos:weight="1" ohos:text_color="#000000"/> < Text ohos: ohos: ohos:text="当前温度:" ohos:text_size="16vp" ohos:weight="1" ohos:text_color="#000000"/> < Text ohos: ohos: ohos:text="最低温度阀值:" ohos:text_size="16vp" ohos:weight="1" ohos:text_color="#000000"/> < /DirectionalLayout> < DependentLayout ohos: ohos:> < RoundProgressBar ohos:id="$+id:rgb_tem" ohos: ohos: ohos:progress_ ohos:progress="0" ohos:max="100" ohos:start_angle="215" ohos:max_angle="290" ohos:progress_color="#00ff00" ohos:center_in_parent="true"/> < Text ohos:id="$+id:tvMaxTemp" ohos: ohos: ohos:text_size="15vp" ohos:text_color="#000000" ohos:align_parent_top="true" ohos:text="未知" ohos:top_margin="8vp" ohos:horizontal_center="true"/> < Text ohos:id="$+id:tvTemp" ohos: ohos: ohos:text_size="15vp" ohos:text_color="#000000" ohos:text="未知" ohos:center_in_parent="true"/> < Text ohos:id="$+id:tvMinTemp" ohos: ohos: ohos:text_size="14vp" ohos:text_color="#000000" ohos:text="未知" ohos:bottom_margin="8vp" ohos:align_parent_bottom="true" ohos:horizontal_center="true"/> < /DependentLayout> < /DirectionalLayout> < DirectionalLayout ohos: ohos: ohos:background_element="#ffffff" ohos:top_margin="15vp" ohos:left_margin="20vp" ohos:right_margin="20vp" ohos:layout_alignment="center" ohos:bottom_margin="15vp" ohos:visibility="invisible"/> < DirectionalLayout ohos: ohos: ohos:layout_alignment="center" ohos:orientation="horizontal"> < Text ohos: ohos: ohos:text="温度状态:" ohos:text_size="18vp" ohos:text_color="#000000"/> < Text ohos:id="$+id:tvTempStatus" ohos: ohos: ohos:text="未知" ohos:text_size="18vp" ohos:text_color="#000000"/> < /DirectionalLayout> < /DirectionalLayout> < DirectionalLayout ohos: ohos: ohos:background_element="#000000" ohos:top_margin="6vp" ohos:layout_alignment="center"/> < DirectionalLayout ohos: ohos: ohos:weight="1" ohos:layout_alignment="center" ohos:orientation="vertical"> < Text ohos: ohos: ohos:text="湿度数据" ohos:text_size="20vp" ohos:padding="5vp" ohos:text_color="#ffffff" ohos:layout_alignment="center"/> < DirectionalLayout ohos: ohos: ohos:background_element="#ffffff" ohos:top_margin="15vp" ohos:left_margin="20vp" ohos:right_margin="20vp" ohos:layout_alignment="center" ohos:bottom_margin="15vp" ohos:visibility="invisible"/> < DirectionalLayout ohos: ohos: ohos:layout_alignment="center" ohos:orientation="horizontal"> < DirectionalLayout ohos: ohos: ohos:orientation="vertical"> < Text ohos: ohos: ohos:text="最大湿度阀值:" ohos:text_size="16vp" ohos:weight="1" ohos:text_color="#000000"/> < Text ohos: ohos: ohos:text="当前湿度:" ohos:text_size="16vp" ohos:weight="1" ohos:text_color="#000000"/> < Text ohos: ohos: ohos:text="最小湿度阀值:" ohos:text_size="16vp" ohos:weight="1" ohos:text_color="#000000"/> < /DirectionalLayout> < DependentLayout ohos: ohos:> < RoundProgressBar ohos:id="$+id:rgb_humi" ohos: ohos: ohos:progress_ ohos:progress="0" ohos:max="100" ohos:start_angle="215" ohos:max_angle="290" ohos:progress_color="#00ff00" ohos:center_in_parent="true"/> < Text ohos:id="$+id:tvMaxHumi" ohos: ohos: ohos:text_size="15vp" ohos:text_color="#000000" ohos:text="未知" ohos:top_margin="8vp" ohos:horizontal_center="true" ohos:align_parent_top="true"/> < Text ohos:id="$+id:tvHumi" ohos: ohos: ohos:text_size="15vp" ohos:text_color="#000000" ohos:text="未知" ohos:center_in_parent="true"/> < Text ohos:id="$+id:tvMinHumi" ohos: ohos: ohos:text_size="15vp" ohos:text_color="#000000" ohos:horizontal_center="true" ohos:text="未知" ohos:bottom_margin="8vp" ohos:align_parent_bottom="true"/> < /DependentLayout> < /DirectionalLayout> < DirectionalLayout ohos: ohos: ohos:background_element="#ffffff" ohos:top_margin="15vp" ohos:left_margin="20vp" ohos:right_margin="20vp" ohos:layout_alignment="center" ohos:bottom_margin="15vp" ohos:visibility="invisible"/> < DirectionalLayout ohos: ohos: ohos:layout_alignment="center" ohos:orientation="horizontal"> < Text ohos: ohos: ohos:text="湿度状态:" ohos:text_size="18vp" ohos:text_color="#000000"/> < Text ohos:id="$+id:tvHumiStatus" ohos: ohos: ohos:text="未知" ohos:text_size="18vp" ohos:text_color="#000000"/> < /DirectionalLayout> < /DirectionalLayout> < DirectionalLayout ohos: ohos: ohos:background_element="#000000" ohos:top_margin="6vp" ohos:layout_alignment="center"/> < DirectionalLayout ohos: ohos: ohos:weight="1" ohos:layout_alignment="center" ohos:orientation="vertical"> < Text ohos: ohos: ohos:text="CO2数据" ohos:text_size="20vp" ohos:padding="5vp" ohos:text_color="#ffffff" ohos:layout_alignment="center"/> < DirectionalLayout ohos: ohos: ohos:background_element="#ffffff" ohos:top_margin="15vp" ohos:left_margin="20vp" ohos:right_margin="20vp" ohos:layout_alignment="center" ohos:bottom_margin="15vp" ohos:visibility="invisible"/> < DirectionalLayout ohos: ohos: ohos:layout_alignment="center" ohos:orientation="horizontal"> < DirectionalLayout ohos: ohos: ohos:orientation="vertical"> < Text ohos: ohos: ohos:text="最大气体增量:" ohos:text_size="16vp" ohos:weight="1" ohos:text_color="#000000"/> < Text ohos: ohos: ohos:text="当前增量:" ohos:text_size="16vp" ohos:weight="1" ohos:text_color="#000000"/> < Text ohos: ohos: ohos:text="最小气体增量:" ohos:text_size="16vp" ohos:weight="1" ohos:text_color="#000000"/> < /DirectionalLayout> < DependentLayout ohos: ohos:> < RoundProgressBar ohos:id="$+id:rgb_gas" ohos: ohos: ohos:progress_ ohos:progress="0" ohos:max="1000" ohos:start_angle="215" ohos:max_angle="290" ohos:progress_color="#00ff00" ohos:center_in_parent="true"/> < Text ohos:id="$+id:tvMaxCgas" ohos: ohos: ohos:text_size="15vp" ohos:text="未知" ohos:text_color="#000000" ohos:top_margin="8vp" ohos:horizontal_center="true" ohos:align_parent_top="true"/> < Text ohos:id="$+id:tvCgas" ohos: ohos: ohos:text_size="15vp" ohos:text="未知" ohos:text_color="#000000" ohos:center_in_parent="true"/> < Text ohos:id="$+id:tvMinCgas" ohos: ohos: ohos:text_size="15vp" ohos:text="未知" ohos:text_color="#000000" ohos:bottom_margin="8vp" ohos:horizontal_center="true" ohos:align_parent_bottom="true"/> < /DependentLayout> < /DirectionalLayout> < DirectionalLayout ohos: ohos: ohos:background_element="#ffffff" ohos:top_margin="15vp" ohos:left_margin="20vp" ohos:right_margin="20vp" ohos:layout_alignment="center" ohos:bottom_margin="15vp" ohos:visibility="invisible"/> < DirectionalLayout ohos: ohos: ohos:layout_alignment="center" ohos:orientation="horizontal"> < Text ohos: ohos: ohos:text="气体状态:" ohos:text_size="18vp" ohos:text_color="#000000"/> < Text ohos:id="$+id:tvCgasStatus" ohos: ohos: ohos:text="未知" ohos:text_size="18vp" ohos:text_color="#000000"/> < /DirectionalLayout> < /DirectionalLayout> < /DirectionalLayout>

< /DependentLayout>
< /DirectionalLayout>
### 4.config.json


" app" :
" bundleName" : " com.isoftstone. distributeddata " ,
" vendor" : " isoftstone" ,
" version" :
" code" : 1000000,
" name" : " 1.0.0"

,
" deviceConfig" : ,
" module" :
" package" : " com.isoftstone.distributeddata" ,
" name" : " .MyApplication" ,
" mainAbility" : " com.isoftstone.distributeddata.com.isoftstone.distributeddata.TVAbility" ,
" deviceType" : [
" phone"
],
" distro" :
" deliveryWithInstall" : true,
" moduleName" : " entry" ,
" moduleType" : " entry" ,
" installationFree" : false
,
" reqPermissions" : [

" name" : " ohos.permission.DISTRIBUTED_DATASYNC"

],
" abilities" : [

" skills" : [

" entities" : [
" entity.system.home"
],
" actions" : [
" action.system.home"
]

],
" orientation" : " landscape" ,
" name" : " com.isoftstone.distributeddata.TVAbility" ,
" icon" : " $media:icon" ,
" description" : " $string:tvability_description" ,
" label" : " $string:entry_TVAbility" ,
" type" : " page" ,
" launchType" : " standard"

],
" metaData" :
" customizeData" : [

" name" : " hwc-theme" ,
" value" : " androidhwext:style/Theme.Emui.NoTitleBar" ,
" extra" : " "

]



## 更多原创内容请关注:[软通动力HarmonyOS学院](https://harmonyos.51cto.com/column/30)[想了解更多关于鸿蒙的内容,请访问:](https://harmonyos.51cto.com/#bkwz)[51CTO和华为官方合作共建的鸿蒙技术社区](https://harmonyos.51cto.com/#bkwz)https://harmonyos.51cto.com/#bkwz::: hljs-center![21_9.jpg](https://s2.51cto.com/images/20210924/1632469265578939.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=):::


    推荐阅读