一卷旌收千骑虏,万全身出百重围。这篇文章主要讲述#星光计划2.0#HarmonyOS分布式应用农业大棚数据监测解读相关的知识,希望能为你提供帮助。
【本文正在参与51CTO HarmonyOS技术社区创作者激励计划-星光计划2.0】
文章图片
引言分布式数据服务(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中开启定时任务,模拟农业大棚采集数据,实时保存数据到分布式数据库,然后在主界面,监听数据变化,实时更新数据。当选择迁移的设备时,就可以把数据迁移到相应设备。
文章图片
手机侧应用刚打开时界面
文章图片
TV侧应用刚打开时界面
文章图片
点击右上角迁移按钮,并选择迁移设备(P40-0036)
文章图片
文章图片
迁移后的左侧设备数据和右侧设备数据就会同步一致
附上源码 手机端 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();
- 类似于Android中的listView缓存。 将已经显示在屏幕上的item缓存在ViewHolder中,下次再次出现直接从缓存中读取
### 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=):::
推荐阅读
- SpringCache#yyds干货盘点#
- AWS eks绑定alb 使用aws-load-balancer-controller(Ingress Controller)提供服务
- #yyds干货盘点# JavaScript之手撕callapply
- 小程序下一破局点(钉钉小程序卡片,应用与平台的深度集成)
- Wo??rdpress如果当前用户是
- WordPress(我在本地主机上自定义了一个模板,但是我想购买相同的高级模板)
- WordPress(如何在定制程序中使用活动回调)
- WordPress-如何为自定义生成的内容调用默认页面模板()
- WordPress(如何在小部件中插入主题变量())