HarmonyOS基于分布式的点餐系统

程序员的自觉 2021-07-30 15:32:45 4964

本项目可以使用在聚餐时餐馆点餐或者外卖拼单,由一个人打开菜单页分享给同桌所有人员。

所有人员可以在自己手机选择自己需要点的菜,会在同一时间同步到所有人手机,每个人都可以看到他人点的菜单,所有人同时维护一个菜单。

不需要像传统的方式传菜单点菜,服务员手动登记;或者由一个人扫码点菜,其他人均把需要的菜报给点菜人员。

此项目旨在帮助开发者快速了解 HarmonyOS 应用开发、JS-JAVA 通信、跨设备调用 PA 以及分布式数据库的使用。

搭建 HarmonyOS 环境

①安装 DevEco Studio,详情请参考 DevEco Studio下载:
https://developer.harmonyos.com/cn/develop/deveco-studio

②设置 DevEco Studio 开发环境,DevEco Studio 开发环境需要依赖于网络环境。

可以根据如下两种情况来配置开发环境:

如果可以直接访问 Internet,只需进行下载 HarmonyOS SDK 操作。

如果网络不能直接访问 Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/environment_config-0000001052902427

③本程序需要在真机运行,需要提前申请证书。

准备密钥和证书请求文件:

https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ide_debug_device-0000001053822404

申请调试证书:
https://developer.huawei.com/consumer/cn/doc/distribution/app/agc-help-harmonyos-debugapp-0000001172419675

代码结构解读

本教程我们只是对核心代码进行讲解,您可以在最后的参考中下载完整代码,首先来介绍下整个工程的代码结构:

①Java-data:封装菜品实体类和部分字符串常量。

②Java-service:SharePageServiceAbility 供 js 与 java 通信的 PA,此服务中跨设备调用 FA(MainAbility);DBInternalAbility 供 js 与 java 通信的 PA,js 通过此服务调用 java 的分布式数据库工具类。

③Java-utils:封装了数据库操作的工具类。

④Js-common:components 存放公共组件,imgs 存放业务图片,json 存放模拟数据。

⑤Js-pages:detail 商家菜品列表展示页面,index 商家列表展示页面,shoppingCart 结账页。

⑥config.json:配置文件。

跨设备打开点餐页面

①权限申请

本程序开发需要申请以下 4 个权限:

ohos.permission.GET_DISTRIBUTED_DEVICE_INFO:用于允许获取分布式组网内的设备列表和设备信息。

ohos.permission.DISTRIBUTED_DATASYNC:用于允许不同设备间的数据交换。

ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE:用于允许监听分布式组网内的设备状态变化。

ohos.permission.GET_BUNDLE_INFO:用于查询其他应用的信息。

在 config.json 中增加下面权限申请代码:
"reqPermissions": [
{
"name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO"
},
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
},
{
"name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE"
},
{
"name": "ohos.permission.GET_BUNDLE_INFO"
}
]

在 MainAbility.java 的 onStart() 中申请权限,主要代码如下:

private static final String PERMISSION_DATASYNC = "ohos.permission.DISTRIBUTED_DATASYNC";
private static final int MY_PERMISSION_REQUEST_CODE = 1;
private void requestPermission() {
if (verifySelfPermission(PERMISSION_DATASYNC) != IBundleManager.PERMISSION_GRANTED) {
if (canRequestPermission(PERMISSION_DATASYNC)) {
requestPermissionsFromUser(new String[] {PERMISSION_DATASYNC}, MY_PERMISSION_REQUEST_CODE);
}
}
}

②FA(JS API)调用 PA(Java API)

detail 商家菜品列表展示页面(点餐页),点击头部分享按钮,调用 SharePageServiceAbility 与 java 通信:

const ABILITY_TYPE_EXTERNAL = 0;
const ABILITY_TYPE_INTERNAL = 1;// syncOption(Optional, default sync): 0-Sync; 1-Async
const ACTION_SYNC = 0;
const DISTRIBUTE_PAGE = 1000;

shareToOthers: async function(){
var actionData = {};
actionData.restaurantId = this.restaurantId;

   var action = {};
   action.bundleName = 'com.example.ordering';
   action.abilityName = 'com.example.ordering.service.SharePageServiceAbility';
   action.messageCode = DISTRIBUTE_PAGE;
   action.data = actionData;
   action.abilityType = ABILITY_TYPE_EXTERNAL;
   action.syncOption = ACTION_SYNC;

   var result = await FeatureAbility.callAbility(action);

}

③发现设备和打开迁移设备页面

SharePageServiceAbility 中,打开所有同桌人员的 MainAbility 页面:

List deviceInfoList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE); if(deviceInfoList != null && deviceInfoList.size()>0){
for (DeviceInfo info: deviceInfoList){
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId(info.getDeviceId())
.withBundleName("com.example.ordering")
.withAbilityName("com.example.ordering.MainAbility")
.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
.build();
intent.setParam(Constant.INIT_PAGE_PARAM,Constant.PAGE_DETAIL);
intent.setParam(Constant.INIT_RESTAURANTID,param.getRestaurantId());
intent.setOperation(operation);
startAbility(intent);
}
}

④如何路由到菜单页

MainAbility.java 初始化 start 方法中,将餐馆 id、需要路由的页面传给 js 页面:

String init_page = intent.getStringParam(Constant.INIT_PAGE_PARAM);
String init_restaurantId = intent.getStringParam(Constant.INIT_RESTAURANTID);
if(init_page !=null && !init_page.isEmpty()) {
IntentParams params = new IntentParams();
params.setParam(Constant.INIT_PAGE_PARAM, Constant.PAGE_DETAIL);
params.setParam(Constant.INIT_RESTAURANTID, init_restaurantId);
setPageParams(null, params);
}
DBInternalAbility.register(this);
setInstanceName("default");

js-default 模块首页,路由到对应的点餐 detail 页面:

onReady(){
if(this.init_page == "detail"){
router.push({uri:'pages/detail/detail',params:{restaurantId:this.init_restaurantId}});
}
}

分布式数据库数据处理

①权限申请

同上一步的权限申请,无需增加权限申请。

②创建分布式数据库(DbHelper)

要创建分布式数据库,首先要创建分布式数据库管理器实例 KvManager,方法如下:

private KvManager createManager() {
KvManager kvmanager = null;
try {
KvManagerConfig config = new KvManagerConfig(context);
kvmanager = KvManagerFactory.getInstance().createKvManager(config);
} catch (KvStoreException exception) {
HiLog.info(LABEL_LOG, "some exception happen");
}
return kvmanager;
}

KvManager 创建成功后,借助 KvManager 创建 SINGLE_VERSION 分布式数据库,方法如下:

private SingleKvStore createDb(KvManager kvmanager) {
if(kvmanager == null) return null;
SingleKvStore kvStore = null;
try {
Options options = new Options();
options.setCreateIfMissing(true).setEncrypt(false).setKvStoreType(KvStoreType.SINGLE_VERSION);
kvStore = kvmanager.getKvStore(options, STORE_ID);
} catch (KvStoreException exception) {
HiLog.info(LABEL_LOG, "some exception happen");
}
return kvStore;
}

SINGLE_VERSION 分布式数据库是指数据在本地保存是以单个 KV 条目为单位的方式保存,对每个 Key 最多只保存一个条目项。

当数据在本地被用户修改时,不管它是否已经被同步出去,均直接在这个条目上进行修改。

最后是订阅分布式数据库中数据变化,方法如下:

private void subscribeDb(SingleKvStore kvStore) {
if(kvStore == null) return;
KvStoreObserver kvStoreObserverClient = new KvStoreObserverClient();
kvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_REMOTE, kvStoreObserverClient);
}

分布式数据库支持订阅远端和本地的数据变化,示例代码中订阅的方式为订阅远端,订阅本地的参数为 SUBSCRIBE_TYPE_LOCAL;同时还支持订阅全部,参数为 SUBSCRIBE_TYPE_ALL。

③数据查询、插入和删除

数据插入:先构造分布式数据库的 Key(键)和 Value(值),通过 putString 方法将数据写入到数据库中。

具体示例如下:

private void writeData(String key, String value) {
if (key == null || key.isEmpty() || value == null || value.isEmpty()) {
return;
}
singleKvStore.putString(key, value);
}

public void writeData(String name, int count, int deskNum, String restaurantId,String dishId) {
if(kvStore == null) return;
if (name == null || name.isEmpty()) {
return;
}
kvStore.putString(name+restaurantId, name+";"+count+";"+deskNum+";"+Dish.NO_VALUE+";"+restaurantId+";"+dishId);
}

数据查询:根据Key(键)来进行查询,如果指定 Key,则会查询出对应 Key 的数据;如果不指定 Key,既为空,则查询出所有数据。

查询示例代码如下(除查询外,还有部分场景业务代码):

public String queryData(String restaurantId) {
this.restaurantId = restaurantId;
if(kvStore == null) return null;
List entryList = kvStore.getEntries("");
String json = "[";
try {
for (Entry entry : entryList) {
String name = entry.getKey();
String[] values = entry.getValue().getString().split(";");
if(restaurantId == null || restaurantId.equals(values[4])) {
json += "{" +
"\"name\":\"" + values[0] + "\"," +
"\"count\":" + values[1] + "," +
"\"deskNum\":" + values[2] + "," +
"\"address\":\"" + values[3] + "\"," +
"\"restaurantId\":\"" + values[4] +"\"," +
"\"id\":\"" + values[5] +
"\"},";
}
}
} catch (KvStoreException exception) {
HiLog.info(LABEL_LOG,"the value must be String");
}

json += "]";
if(json.contains(",]")){
    json = json.replace(",]","]");
}

return json;

}

数据删除:删除操作可以直接调用 delete() 方法,但是需要传递事先定义好的 key(键)。

示例代码如下:

public void deleteData(String key) {
if(kvStore == null) return;
if (key.isEmpty()) {
return;
}
kvStore.delete(key+restaurantId);
HiLog.info(LABEL_LOG, "deleteContact key= " + key);
}

数据库删除:删除操作可以直接调用 deleteKvStore() 方法,但是需要传递事先定义好的 STORE_ID 参数。

示例代码如下:

public void deleteDb(){
if(kvmanager == null) return;
kvmanager.closeKvStore(kvStore);
kvmanager.deleteKvStore(STORE_ID);
}

④分布式数据库的同步

在进行数据同步之前,首先需要先获取当前组网环境中的设备列表,然后指定同步方式(PULL_ONLY,PUSH_ONLY,PUSH_PULL)进行同步。

以 PUSH_PULL 方式为例,示例代码如下:

public void syncData(String restaurantId) {
this.restaurantId = restaurantId;
if(kvmanager == null || kvStore == null) return;
List deviceInfoList = kvmanager.getConnectedDevicesInfo(DeviceFilterStrategy.NO_FILTER);
List deviceIdList = new ArrayList<>();
for (DeviceInfo deviceInfo : deviceInfoList) {
deviceIdList.add(deviceInfo.getId());
}
HiLog.info(LABEL_LOG, "device size= " + deviceIdList.size());
if (deviceIdList.size() == 0) {
String result = queryData(this.restaurantId);
if(kvStoreLishner != null) {
kvStoreLishner.updataUI(result);
}
return;
}
kvStore.registerSyncCallback(new SyncCallback() {
@Override
public void syncCompleted(Map<String, Integer> map) {
String result = queryData(getRestaurantId());
if(kvStoreLishner != null) {
kvStoreLishner.updataUI(result);
}
kvStore.unRegisterSyncCallback();
}
});
kvStore.sync(deviceIdList, SyncMode.PUSH_PULL);
}

以上代码除数据同步外,还有部分场景业务代码。

点餐页面数据同步

①跨设备 FA 页面打开,初始数据同步

初始打开点单 detail 页,同步组网内其他设备已经点好的菜单,在生命周期 onShow 方法中调用 subscribeInternal 方法订阅 PA-DBInternalAbility。

生命中期 onHide 方法中调用 unsubscribeInternal 方法取消订阅 PA-DBInternalAbility。

具体代码如下:

subscribeInternal: async function() {
var that = this;
var actionData = {};
actionData.restaurantId = this.restaurantId;
var action = {};
action.bundleName = 'com.example.ordering';
action.abilityName = 'com.example.ordering.service.DBInternalAbility';
action.messageCode = ACTION_MESSAGE_CODE_SUBSCRIBE;
action.data = actionData;
action.abilityType = ABILITY_TYPE_INTERNAL;
action.syncOption = ACTION_SYNC;
var result = await FeatureAbility.subscribeAbilityEvent(action, function (callbackData) {
var callbackJson = JSON.parse(callbackData);
that.queryData = JSON.parse(callbackJson.data.abilityEvent);
that.initDetailAndCart();
});
},

unsubscribeInternal: async function() {
var action = {};
action.bundleName = 'com.example.ordering';
action.abilityName = 'com.example.ordering.service.DBInternalAbility';
action.messageCode = ACTION_MESSAGE_CODE_UNSUBSCRIBE;
action.abilityType = ABILITY_TYPE_INTERNAL;
action.syncOption = ACTION_SYNC;

   var result = await FeatureAbility.unsubscribeAbilityEvent(action);

}

PA-DBInternalAbility 在 onRemoteRequest 方法中对 js 传输过来的订阅与取消订阅进行两种事件进行处理。

具体代码如下:

case SUBSCRIBE: {
remoteObjectHandler = data.readRemoteObject();
String zsonStr = data.readString();
Dish param = new Dish();
try {
param = ZSONObject.stringToClass(zsonStr, Dish.class);
} catch (RuntimeException e) {
HiLog.error(LABEL, "convert failed.");
}
dbHelper.syncData(param.getRestaurantId());
break;
}
// 取消订阅,置空对端的remoteHandler
case UNSUBSCRIBE: {
remoteObjectHandler = null;
break;
}

dbHelper.syncData 数据库同步方法已经在上面进行介绍,同步方法中数据库同步之后,会查询分布式数据库数据通过 kvStoreLishner.updataUI(result) 更新界面数据。

updataUI 具体实现代码如下:

public void updataUI(String result) {
try {
MessageParcel data = MessageParcel.obtain();
MessageParcel reply = MessageParcel.obtain();
MessageOption option = new MessageOption();
Map<String, Object> zsonEvent = new HashMap<String, Object>();
zsonEvent.put("abilityEvent", result);
data.writeString(ZSONObject.toZSONString(zsonEvent));
if(remoteObjectHandler != null)
remoteObjectHandler.sendRequest(100, data, reply, option);
reply.reclaim();
data.reclaim();
} catch (RemoteException e) {

}
}

PA-DBInternalAbility 需要在 MainAbility 中的 onStart 方法中调用 DBInternalAbility.register 进行注册。

onStop 方法中调用 DBInternalAbility.unregister 进行取消注册,具体注册、取消注册代码如下:

/**

  • Internal ability registration.
    */
    public static void register(AbilityContext abilityContext) {
    instance = new DBInternalAbility();
    instance.onRegister(abilityContext);
    }

    private void onRegister(AbilityContext abilityContext) {
    dbHelper = new DbHelper(abilityContext,kvStoreLishner);
    dbHelper.initDbManager();
    this.abilityContext = abilityContext;
    this.setInternalAbilityHandler((code, data, reply, option) -> {
    return this.onRemoteRequest(code, data, reply, option);
    });
    }

    /**

  • Internal ability unregistration.
    */
    public static void unregister() {
    instance.onUnregister();
    }

    private void onUnregister() {
    dbHelper.deleteDb();
    abilityContext = null;
    this.setInternalAbilityHandler(null);
    }

②某个设备 FA 页面操作,其他设备 FA 页面数据同步

因为在创建数据库时,我们对分布式数据库的变化进行了订阅,所以每次数据库变动均会调用 kvStoreLishner.updataUI 更新界面数据。

具体代码如下:
public void initDbManager() {
kvmanager = createManager();
kvStore = createDb(kvmanager);
subscribeDb(kvStore);
}

/**

  • 最后是订阅分布式数据库中数据变化
    */
    private void subscribeDb(SingleKvStore kvStore) {
    if(kvStore == null) return;
    KvStoreObserver kvStoreObserverClient = new KvStoreObserverClient();
    kvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_REMOTE, kvStoreObserverClient);
    }

/**

  • Receive database messages
    */
    private class KvStoreObserverClient implements KvStoreObserver {

    @Override
    public void onChange(ChangeNotification notification) {
    String result = queryData(getRestaurantId());
    if(kvStoreLishner != null) {
    kvStoreLishner.updataUI(result);
    }
    }
    }

    public interface KvStoreLishner{
    void updataUI(String result);
    }

最终实现效果

本篇通过一个分布式点餐系统,完整的为您介绍了多人在线点餐数据共享案例,旨在帮助您快速了解 HarmonyOS 应用开发、JS-JAVA 通信、获取可迁移设备、打开迁移设备的页面、分布式数据库使用。

我们通过拆解步骤的方式详细为您介绍了如何在多台设备之间进行数据共享、页面分享,这是您需要重点学习和掌握的知识点。

特别说明,运行时需要至少两台手机处于同一个分布式网络中,可以通过操作如下配置实现:
所有设备接入同一网络

所有设备登录相同华为帐号

所有设备上开启"设置->更多连接->多设备协同 "

所有设备上开启蓝牙功能

代码参考

代码参考如下:
https://gitee.com/chinasoft6_ohos/distributed-ordering

来源:鸿蒙技术社区

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 91 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
程序员的自觉
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区