自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

HarmonyOS 分布式親子教育

系統(tǒng) 分布式 OpenHarmony
本篇Codelab通過一個(gè)親子早教系統(tǒng),完整的為您介紹了早教算數(shù)題和益智拼圖游戲兩個(gè)綜合案例,旨在幫助您快速了解HarmonyOS應(yīng)用開發(fā)、多屏互動(dòng)、分布式跨設(shè)備協(xié)同的功能了解。

[[412544]]

想了解更多內(nèi)容,請(qǐng)?jiān)L問:

51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

https://harmonyos.51cto.com

1. 項(xiàng)目介紹

遠(yuǎn)程教育,多屏協(xié)同是智慧教育的一個(gè)重要場(chǎng)景。本篇Codelab通過一個(gè)親子早教系統(tǒng),完成了分布式早教算數(shù)題和分布式拼圖游戲兩個(gè)綜合案例,旨在幫助開發(fā)者快速了解HarmonyOS應(yīng)用開發(fā)、多屏互動(dòng)和分布式跨設(shè)備協(xié)同的體驗(yàn)。

本篇Codelab將為您重點(diǎn)介紹Page Ability、Service Ability、Intent以及分布式任務(wù)調(diào)度、公共事件等。同時(shí)我們還將為您介紹多屏互動(dòng),分布式跨設(shè)備協(xié)同、繪圖畫布的使用、經(jīng)典拼圖算法。正式介紹之前,我們先對(duì)親子早教系統(tǒng)進(jìn)行展示,讓您快速了解到本篇Codelab所實(shí)現(xiàn)的功能。

功能1:早教算數(shù)題

點(diǎn)擊早教算數(shù)題,系統(tǒng)會(huì)為您隨機(jī)出一道兩位數(shù)的加法,點(diǎn)擊實(shí)時(shí)輔導(dǎo)會(huì)拉起兩個(gè)畫布,本地端可以用黑色筆跡進(jìn)行草稿運(yùn)算,遠(yuǎn)程端可以用紅色筆跡進(jìn)行實(shí)時(shí)指導(dǎo),操作步驟兩端實(shí)時(shí)同步,效果圖如下所示。

HarmonyOS 分布式親子教育-鴻蒙HarmonyOS技術(shù)社區(qū)

功能2:益智拼圖游戲

點(diǎn)擊益智拼圖游戲會(huì)拉起一個(gè)九宮格的拼圖游戲(圖片會(huì)隨機(jī)亂序),點(diǎn)擊圖片可以進(jìn)行拼圖。在本地端點(diǎn)擊親子協(xié)同,遠(yuǎn)程端會(huì)拉起一個(gè)一模一樣的游戲頁(yè)面,兩個(gè)設(shè)備可以進(jìn)行實(shí)時(shí)互動(dòng),同步對(duì)圖片進(jìn)行拼接,操作步驟亦可實(shí)時(shí)同步,效果圖如下所示。

HarmonyOS 分布式親子教育-鴻蒙HarmonyOS技術(shù)社區(qū)

想知道上述兩個(gè)親子游戲是如何實(shí)現(xiàn)的嗎?快來跟隨我們的Codelab進(jìn)行學(xué)習(xí)吧。

2. 搭建HarmonyOS環(huán)境

搭建HarmonyOS環(huán)境

  • 安裝DevEco Studio,詳情請(qǐng)參考DevEco Studio下載。
  • 設(shè)置DevEco Studio開發(fā)環(huán)境,DevEco Studio開發(fā)環(huán)境需要依賴于網(wǎng)絡(luò)環(huán)境,需要連接上網(wǎng)絡(luò)才能確保工具的正常使用,可以根據(jù)如下兩種情況來配置開發(fā)環(huán)境
  1. 如果可以直接訪問Internet,只需進(jìn)行下載HarmonyOS SDK操作
  2. 如果網(wǎng)絡(luò)不能直接訪問Internet,需要通過代理服務(wù)器才可以訪問,請(qǐng)參考配置開發(fā)環(huán)境

說明:

如需要在手機(jī)中運(yùn)行程序,則需要提前申請(qǐng)證書,如使用模擬器可忽略

準(zhǔn)備密鑰和證書請(qǐng)求文件

申請(qǐng)調(diào)試證書

你可以通過如下設(shè)備完成本CodeLab:

開啟開發(fā)者模式的HarmonyOS真機(jī)

DevEco Studio中的手機(jī)模擬器(模擬器暫不支持分布式調(diào)試)

3. 代碼結(jié)構(gòu)解讀

本篇Codelab我們只是對(duì)核心代碼進(jìn)行講解,您可以在最后的參考中下載完整代碼,首先來介紹下整個(gè)工程的代碼結(jié)構(gòu):

HarmonyOS 分布式親子教育-鴻蒙HarmonyOS技術(shù)社區(qū)
  • devices:封裝了選擇設(shè)備的Dialog,您可直接調(diào)用SelectDeviceDialog里面的相關(guān)方法。
  • point:封裝了繪圖的相關(guān)功能,您可直接調(diào)用DrawPoint里面的相關(guān)方法。
  • slice:MainAbilitySlice為應(yīng)用主頁(yè)面,MathGameAbilitySlice為早教算數(shù)題主頁(yè)面,MathDrawRemSlice為早教算數(shù)題繪圖頁(yè)面,PictureGameAbilitySlice為拼圖游戲主頁(yè)面。
  • utils:封裝了公共方法和公共數(shù)據(jù)。
  • MathGameServiceAbility、PictureGameServiceAbility:供遠(yuǎn)端連接的Service Ability。
  • resources:存放工程使用到的資源文件,其中resources\base\layout下存放xml布局文件;resources\base\media下存放圖片資源。
  • config.json:配置文件。

4. 親子早教系統(tǒng)頁(yè)面流轉(zhuǎn)

親子早教系統(tǒng)主頁(yè)面和各個(gè)游戲頁(yè)面的布局如下圖所示,首先給大家介紹一下相關(guān)的布局代碼。

HarmonyOS 分布式親子教育-鴻蒙HarmonyOS技術(shù)社區(qū)

首頁(yè)的布局文件為ability_main.xml、頁(yè)面控制邏輯MainAbilitySlice;早教系統(tǒng)的布局文件為math_game.xml、頁(yè)面控制邏輯MathGameAbilitySlice;拼圖游戲的布局文件為ability_picture.xml、頁(yè)面控制邏輯PictureGameAbilitySlice。要實(shí)現(xiàn)頁(yè)面的相互流轉(zhuǎn)首先需要在MainAbility中設(shè)置路由,并在MainAbilitySlice中實(shí)現(xiàn)按鈕的點(diǎn)擊事件。

以跳轉(zhuǎn)早教算數(shù)題為例,首先需要在MainAbility中設(shè)置路由,代碼如下所示:

  1. addActionRoute(CommonData.MATH_PAGE, MathGameAbilitySlice.class.getName()); 

 而后,需要在MainAbilitySlice中添加點(diǎn)擊事件,代碼如下所示:

  1. private void mathGame() {  
  2.     LogUtil.info(TAG, "Click ResourceTable Id_math_game");  
  3.     Intent mathGameIntent = new Intent();  
  4.     Operation operationMath = new Intent.OperationBuilder()  
  5.             .withBundleName(getBundleName())  
  6.             .withAbilityName(CommonData.ABILITY_MAIN)  
  7.             .withAction(CommonData.MATH_PAGE)  
  8.             .build();  
  9.     mathGameIntent.setOperation(operationMath);  
  10.     startAbility(mathGameIntent);  

 至此,您已實(shí)現(xiàn)跳轉(zhuǎn)到早教算數(shù)題和益智拼圖游戲兩個(gè)頁(yè)面了:

  • 早教算數(shù)題的出題邏輯可以參考setQuestion和checkAnswer兩個(gè)方法
  • 拼圖游戲圖片亂序、移動(dòng)圖片、判斷游戲結(jié)束、重新開始可分別參考pictureRandom、moveFun、gameOverFun、restartFun相關(guān)方法

因篇幅有限且相關(guān)游戲算法不屬于本篇Codelab想為您重點(diǎn)介紹的HarmonyOS特性,因此不再贅述,您可自行調(diào)用相關(guān)函數(shù)、理解相關(guān)代碼實(shí)現(xiàn)游戲功能,附件代碼中也已進(jìn)行了詳細(xì)的注釋。以上即完成了單機(jī)版本的HarmonyOS應(yīng)用開發(fā),接下來我們將為您重點(diǎn)介紹基于HarmonyOS分布式能力的相關(guān)系統(tǒng)設(shè)計(jì)。

5. 發(fā)現(xiàn)設(shè)備和建立連接

在早教算數(shù)題中點(diǎn)擊實(shí)時(shí)輔導(dǎo)或者在拼圖游戲中點(diǎn)擊親子協(xié)同,會(huì)彈出選擇設(shè)備頁(yè)面,如下圖所示。我們已經(jīng)在devices目錄下為您封裝好了選擇設(shè)備的dialog,具體代碼請(qǐng)參考DevicesListAdapter(設(shè)備列表適配器)、item_device_list.xml (設(shè)備列表item布局)、SelectDeviceDialog(選擇設(shè)備列表的彈窗)、dialog_select_device.xml(彈窗布局)。

HarmonyOS 分布式親子教育-鴻蒙HarmonyOS技術(shù)社區(qū)

SelectDeviceDialog中選擇設(shè)備彈窗的構(gòu)造函數(shù)如下,需要傳入三個(gè)參數(shù):上下文、設(shè)備列表、選擇結(jié)果的回調(diào)事件。在業(yè)務(wù)代碼中調(diào)用如下構(gòu)造函數(shù)即可打開選擇設(shè)備的彈窗,代碼如下所示:

  1. public SelectDeviceDialog(Context context, List<DeviceInfo> devices, SelectResultListener listener) {  
  2.     initView(context, devices, listener);  

 其中List devices可以通過如下函數(shù)去獲取,代碼如下所示:

  1. private void getDevices() {  
  2.     if (devices.size() > 0) {  
  3.         devices.clear();  
  4.     }  
  5.     List<DeviceInfo> deviceInfos =  
  6.             DeviceManager.getDeviceList(ohos.distributedschedule.interwork.DeviceInfo.FLAG_GET_ONLINE_DEVICE);  
  7.     LogUtil.info(TAG, "deviceInfos size is :" + deviceInfos.size());  
  8.     devices.addAll(deviceInfos);  
  9.     showDevicesDialog();  

 選擇結(jié)果的回調(diào)事件可以根據(jù)業(yè)務(wù)的不同傳入不同的回調(diào)事件,例如早教算數(shù)題中選擇設(shè)備后,需要拉起本地端和遠(yuǎn)程端兩個(gè)畫布,此訂閱事件為startLocalFa和startRemoteFa,代碼如下所示:

  1. private void showDevicesDialog() {  
  2.     new SelectDeviceDialog(this, devices, deviceInfo -> {  
  3.         startLocalFa(deviceInfo.getDeviceId());  
  4.         startRemoteFa(deviceInfo.getDeviceId());  
  5.     }).show();  

 而拼圖游戲需要和另外一臺(tái)設(shè)備建立連接,拉起另外一臺(tái)設(shè)備的拼圖頁(yè)面,此回調(diào)事件為connectRemotePa,代碼如下所示:

  1. private void showDevicesDialog() {  
  2.     new SelectDeviceDialog(this, devices, deviceInfo -> {  
  3.         connectRemotePa(deviceInfo.getDeviceId(), PictureRemoteProxy.REQUEST_START_ABILITY);  
  4.     }).show();  

 需要說明的是回調(diào)函數(shù)中deviceInfo是單擊選中的設(shè)備信息,具體實(shí)現(xiàn)在SelectDeviceDialog的initView方法中,代碼如下所示:

  1. DevicesListAdapter devicesListAdapter = new DevicesListAdapter(devices, context);  
  2. devicesListContainer.setItemProvider(devicesListAdapter);  
  3. devicesListContainer.setItemClickedListener((listContainer, component, position, l) -> {  
  4.     listener.callBack(devices.get(position));  
  5.     commonDialog.hide();  
  6. }); 

 至此,您已經(jīng)完成了多設(shè)備協(xié)同中發(fā)現(xiàn)設(shè)備和建立連接這一關(guān)鍵步驟。

說明:

實(shí)現(xiàn)遠(yuǎn)程啟動(dòng)FA,需要至少兩個(gè)設(shè)備處于同一個(gè)分布式網(wǎng)絡(luò)中,可以通過如下操作實(shí)現(xiàn):

所有設(shè)備接入同一網(wǎng)絡(luò);

所有設(shè)備登錄相同華為賬號(hào);

所有設(shè)備上開啟"設(shè)置->更多連接->多設(shè)備協(xié)同 "。

6. 早教算數(shù)題

點(diǎn)擊早教算數(shù)題,系統(tǒng)會(huì)為您隨機(jī)出一道兩位數(shù)的加法,點(diǎn)擊實(shí)時(shí)輔導(dǎo)會(huì)拉起兩個(gè)畫布,本地端可以用黑色筆跡進(jìn)行草稿運(yùn)算,遠(yuǎn)程端可以用紅色筆跡進(jìn)行實(shí)時(shí)指導(dǎo),操作步驟兩端實(shí)時(shí)同步,效果如下圖所示。接下來我們將為您詳細(xì)介紹如何利用HarmonyOS分布式技術(shù)實(shí)現(xiàn)兩端的同步繪制。

HarmonyOS 分布式親子教育-鴻蒙HarmonyOS技術(shù)社區(qū)

Step 1 - 選擇設(shè)備

詳見"5 發(fā)現(xiàn)設(shè)備和建立連接"。

Step 2 - 建立連接

點(diǎn)擊選擇設(shè)備后會(huì)進(jìn)入繪圖頁(yè)面,在onStart方法中會(huì)調(diào)用初始化并連接設(shè)備的函數(shù)initAndConnectDevice,通過intent傳參獲取遠(yuǎn)程設(shè)備的remoteDeviceId,并調(diào)用connectRemotePa進(jìn)行連接,代碼如下所示:

  1. private void initAndConnectDevice(Intent intent) {  
  2.     // 頁(yè)面初始化  
  3.     this.context = MathDrawRemSlice.this;  
  4.     String remoteDeviceId = intent.getStringParam(CommonData.KEY_REMOTE_DEVICEID);  
  5.     isLocal = intent.getBooleanParam(CommonData.KEY_IS_LOCAL, false);  
  6.     if (findComponentById(ResourceTable.Id_text_title) instanceof Text) {  
  7.         Text textTitle = (Text) findComponentById(ResourceTable.Id_text_title);  
  8.         textTitle.setText(isLocal ? "本地端" : "遠(yuǎn)程端");  
  9.     }  
  10.   
  11.     // 連接遠(yuǎn)程服務(wù)  
  12.     if (!remoteDeviceId.isEmpty()) {  
  13.         connectRemotePa(remoteDeviceId);  
  14.     } else {  
  15.         LogUtil.info(TAG, "localDeviceId is null");  
  16.     }  

 其中調(diào)用connectRemotePa方法后會(huì)和MathGameServiceAbility建立服務(wù)連接,代碼如下所示:

  1. Intent connectPaIntent = new Intent();  
  2. Operation operation = new Intent.OperationBuilder()  
  3.         .withDeviceId(deviceId)  
  4.        .withBundleName(getBundleName())  
  5.        .withAbilityName(CommonData.MATH_GAME_SERVICE_NAME)  
  6.         .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)  
  7.         .build();  
  8. connectPaIntent.setOperation(operation); 

 連接成功后會(huì)回調(diào)onAbilityConnectDone方法,此時(shí)兩臺(tái)設(shè)備即建立了連接。而后,可以調(diào)用senDataToRemote方法進(jìn)行數(shù)據(jù)發(fā)送,代碼如下所示:

  1. public void onAbilityConnectDone(ElementName elementName, IRemoteObject remote, int resultCode) {  
  2.     LogUtil.info(TAG, "onAbilityConnectDone......");  
  3.     connectAbility(elementName, remote, requestType);  
  4. }  
  5.    
  6. private void connectAbility(ElementName elementName, IRemoteObject remote, int requestType) {  
  7.     proxy = new PictureRemoteProxy(remote);  
  8.     LogUtil.error(TAG, "connectRemoteAbility done");  
  9.     if (proxy != null) {  
  10.         try {  
  11.             proxy.senDataToRemote(requestType);  
  12.         } catch (RemoteException e) {  
  13.             LogUtil.error(TAG, "onAbilityConnectDone RemoteException");  
  14.         }  
  15.     }  

 Step 3 - 繪圖操作(具體業(yè)務(wù),準(zhǔn)備要發(fā)送的數(shù)據(jù))

DrawPoint是一個(gè)繪圖的工具類,繪制一個(gè)點(diǎn)會(huì)記錄三個(gè)信息:X軸坐標(biāo)、Y軸坐標(biāo)、是否是最后一個(gè)點(diǎn)。將此信息記錄到數(shù)組pointsX、pointsY、isLastPointArray中,并封裝為L(zhǎng)ist allPoints的數(shù)據(jù)類型,定義如下:

  1. private float[] pointsX;  
  2. private float[] pointsY;  
  3. private boolean[] isLastPointArray;  
  4. private List<MyPoint> allPoints = new ArrayList<>(); 

 其中,是否為最后一個(gè)點(diǎn)是通過TouchEvent.PRIMARY_POINT_UP事件進(jìn)行區(qū)分的。當(dāng)檢測(cè)到該事件時(shí),會(huì)調(diào)用回調(diào)函數(shù)callBack.callBack(allPoints) ,代碼如下所示:

  1. if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {  
  2.     point.setLastPoint(true);  
  3.     allPoints.add(point);  
  4.     callBack.callBack(allPoints);  
  5. }  

 此時(shí)會(huì)向上回調(diào)到setOnDrawBack方法,代碼如下所示:

  1. public void setOnDrawBack(OnDrawCallBack callBack) {  
  2.     this.callBack = callBack;  

 MathDrawRemSlice中initDraw()會(huì)初始化畫布,一次繪制完成后會(huì)回調(diào)setOnDrawBack方法,其中points是上述步驟中繪制的點(diǎn),將其存入數(shù)組pointsX、pointsY、isLastPointArray中,代碼如下所示:

  1. drawl.setOnDrawBack(points -> {  
  2.     if (points != null && points.size() > 1) {  
  3.         pointsX = new float[points.size()];  
  4.         pointsY = new float[points.size()];  
  5.         isLastPoint = new boolean[points.size()];  
  6.         for (int i = 0; i < points.size(); i++) {  
  7.            pointsX[i] = points.get(i).getPositionX();  
  8.            pointsY[i] = points.get(i).getPositionY();  
  9.            isLastPoint[i] = points.get(i).isLastPoint();  
  10.         }  
  11.         ...  
  12.     }  
  13. }); 

 此時(shí)就完成了本地畫布繪制并將繪制的點(diǎn)信息存放到了數(shù)組pointsX、pointsY、isLastPointArray中,即已經(jīng)準(zhǔn)備好了要發(fā)送的數(shù)據(jù)。

Step 4 - 發(fā)送數(shù)據(jù)

發(fā)送數(shù)據(jù)涉及到Service Ability、分布式任務(wù)調(diào)度、公共事件三項(xiàng)HarmonyOS能力,如果您還不熟悉相關(guān)基礎(chǔ)知識(shí)可以先參考官方文檔進(jìn)行學(xué)習(xí)。一次繪制完成后,會(huì)調(diào)用senDataToRemote方法,將繪制的點(diǎn)信息(pointsX、pointsY、isLastPointArray)存放到data中并發(fā)送出去,代碼如下所示:

  1. private void senDataToRemote(int requestType) throws RemoteException {  
  2.     LogUtil.info(TAG, "send data to local draw service");  
  3.     MessageParcel data = MessageParcel.obtain();  
  4.     MessageParcel reply = MessageParcel.obtain();  
  5.     MessageOption option = new MessageOption(MessageOption.TF_SYNC);  
  6.     try {  
  7.         if (pointsX != null && pointsY != null && isLastPoint != null) {  
  8.            data.writeFloatArray(pointsX);  
  9.            data.writeFloatArray(pointsY);  
  10.            data.writeBooleanArray(isLastPoint);  
  11.         }  
  12.         remote.sendRequest(requestType, data, reply, option);  
  13.         int ec = reply.readInt();  
  14.         if (ec != ERR_OK) {  
  15.             LogUtil.error(TAG, "RemoteException:");  
  16.         }  
  17.     } catch (RemoteException e) {  
  18.         LogUtil.error(TAG, "RemoteException:");  
  19.     } finally {  
  20.         data.reclaim();  
  21.         reply.reclaim();  
  22.     }  

 其中,remote.sendRequest是將數(shù)據(jù)發(fā)送到MathGameServiceAbility的服務(wù)中(因?yàn)椴襟E2是和MathGameServiceAbility建立服務(wù)連接),建立服務(wù)連接后會(huì)回調(diào)onRemoteRequest方法,代碼如下所示:

  1. @Override  
  2. public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {  
  3.     LogUtil.info(TAG, "onRemoteRequest......");  
  4.     float[] pointsX = data.readFloatArray();  
  5.     float[] pointsY = data.readFloatArray();  
  6.     boolean[] isLastPointArray = data.readBooleanArray();  
  7.     reply.writeInt(ERR_OK);  
  8.     sendEvent(isLastPointArray, pointsX, pointsY);  
  9.     return true;  

 在onRemoteRequest方法中,會(huì)調(diào)用sendEvent將數(shù)組pointsX、pointsY、isLastPointArray發(fā)送出去。sendEvent是通過公共事件的方式進(jìn)行數(shù)據(jù)傳遞的,其注冊(cè)的公共事件是CommonData.MATH_DRAW_EVENT,代碼如下所示:

  1. private void sendEvent(boolean[] isLastPoint, float[] pointsX, float[] pointsY) {  
  2.     LogUtil.info(TAG, "sendEvent......");  
  3.     try {  
  4.         Intent intent = new Intent();  
  5.         Operation operation = new Intent.OperationBuilder()  
  6.                 .withAction(CommonData.MATH_DRAW_EVENT)  
  7.                 .build();  
  8.         intent.setOperation(operation);  
  9.        intent.setParam(CommonData.KEY_POINT_X, pointsX);  
  10.        intent.setParam(CommonData.KEY_POINT_Y, pointsY);  
  11.        intent.setParam(CommonData.KEY_IS_LAST_POINT, isLastPoint);  
  12.         CommonEventData eventData = new CommonEventData(intent);  
  13.         CommonEventManager.publishCommonEvent(eventData);  
  14.     } catch (RemoteException e) {  
  15.         LogUtil.error(TAG, "publishCommonEvent occur exception.");  
  16.     }  

Step 5 - 接收數(shù)據(jù)

MathDrawRemSlice中會(huì)訂閱CommonData.MATH_DRAW_EVENT的公共事件,代碼如下所示:

  1. private void subscribe() {  
  2.     MatchingSkills matchingSkills = new MatchingSkills();  
  3.     matchingSkills.addEvent(CommonData.MATH_DRAW_EVENT);  
  4.     matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_SCREEN_ON);  
  5.     CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);  
  6.     subscriber = new MyCommonEventSubscriber(subscribeInfo);  
  7.     try {  
  8.         CommonEventManager.subscribeCommonEvent(subscriber);  
  9.     } catch (RemoteException e) {  
  10.         LogUtil.error("""subscribeCommonEvent occur exception.");  
  11.     }  

當(dāng)訂閱到相關(guān)事件時(shí)會(huì)回調(diào)onReceiveEvent方法,此時(shí)即可將pointsX、pointsY、isLastPointArray解析出來,然后調(diào)用drawl.setDrawParams(isLastPointArray, pointsX, pointsY)方法在遠(yuǎn)程端進(jìn)行繪制,代碼如下所示:

  1. public void onReceiveEvent(CommonEventData commonEventData) {  
  2.     Intent intent = commonEventData.getIntent();  
  3.     pointsX = intent.getFloatArrayParam(CommonData.KEY_POINT_X);  
  4.     pointsY = intent.getFloatArrayParam(CommonData.KEY_POINT_Y);  
  5.     isLastPoint = intent.getBooleanArrayParam(CommonData.KEY_IS_LAST_POINT);  
  6.     // 接收數(shù)據(jù)后,對(duì)遠(yuǎn)程端畫布進(jìn)行繪制  
  7.     drawl.setDrawParams(isLastPoint, pointsX, pointsY);  
  8.     LogUtil.info(TAG, "onReceiveEvent.....");  

Step 6 - 數(shù)據(jù)的雙向通信

步驟1-5中為您講解了數(shù)據(jù)的單向通信,即本地端向遠(yuǎn)程端的交互。本例的繪圖是雙向通信,兩端都可以進(jìn)行繪制,這是怎么做到的呢?原來在步驟1中選擇設(shè)備時(shí),會(huì)調(diào)用startLocalFa和startRemoteFa,其中startLocalFa傳出的remoteDeviceId是所選擇的設(shè)備ID,startRemoteFa傳出的remoteDeviceId是當(dāng)前設(shè)備的ID,由此實(shí)現(xiàn)了雙向通信,代碼如下所示:

  1. private void showDevicesDialog() {  
  2.     new SelectDeviceDialog(this, devices, deviceInfo -> {  
  3.         startLocalFa(deviceInfo.getDeviceId());  
  4.         startRemoteFa(deviceInfo.getDeviceId());  
  5.     }).show();  
  6. }  
  7. private void startLocalFa(String deviceId) {  
  8.     LogUtil.info(TAG, "startLocalFa......");  
  9.     Intent intent = new Intent();  
  10.     intent.setParam(CommonData.KEY_REMOTE_DEVICEID, deviceId);  
  11.     ...  
  12. }  
  13. private void startRemoteFa(String deviceId) {  
  14.     LogUtil.info(TAG, "startRemoteFa......");  
  15.     String localDeviceId = KvManagerFactory.getInstance()  
  16.             .createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getId();  
  17.     Intent intent = new Intent();  
  18.     intent.setParam(CommonData.KEY_REMOTE_DEVICEID, localDeviceId);  
  19.     ...  

通過選擇設(shè)備、建立連接、發(fā)送數(shù)據(jù)、接收數(shù)據(jù)這幾個(gè)關(guān)鍵步驟,您就可以實(shí)現(xiàn)兩臺(tái)設(shè)備的同步繪圖、實(shí)時(shí)顯示的功能。

—-結(jié)束

7. 益智拼圖游戲

點(diǎn)擊益智拼圖游戲會(huì)拉起一個(gè)九宮格的拼圖游戲(圖片會(huì)隨機(jī)亂序),點(diǎn)擊圖片可以進(jìn)行拼圖。在本地端中點(diǎn)擊親子協(xié)同,遠(yuǎn)程端會(huì)拉起一個(gè)一模一樣的游戲頁(yè)面,兩個(gè)設(shè)備可以進(jìn)行實(shí)時(shí)互動(dòng),同步對(duì)圖片進(jìn)行拼接,操作步驟亦可實(shí)時(shí)同步,效果圖如下所示。接下來我們將為您詳細(xì)介紹如何利用HarmonyOS分布式技術(shù)實(shí)現(xiàn)一個(gè)益智拼圖游戲。

HarmonyOS 分布式親子教育-鴻蒙HarmonyOS技術(shù)社區(qū)

Step 1 - 選擇設(shè)備

詳見"5 發(fā)現(xiàn)設(shè)備和建立連接"這一章節(jié)。

Step 2 - 建立連接并拉起設(shè)備

點(diǎn)擊親子協(xié)同后會(huì)記錄選擇設(shè)備的deviceId,調(diào)用connectRemotePa方法后會(huì)和PictureGameServiceAbility建立服務(wù)連接,其中標(biāo)識(shí)位傳遞的是REQUEST_START_ABILITY,代碼如下所示:

  1. private void showDevicesDialog() {  
  2.     new SelectDeviceDialog(this, devices, deviceInfo -> {  
  3.         connectRemotePa(deviceInfo.getDeviceId(), PictureRemoteProxy.REQUEST_START_ABILITY);  
  4.     }).show();  
  5. }  
  6. private void connectRemotePa(String deviceId, int requestType) {  
  7.     if (!deviceId.isEmpty()) {  
  8.         Intent connectPaIntent = new Intent();  
  9.         Operation operation = new Intent.OperationBuilder()  
  10.                 .withDeviceId(deviceId)  
  11.                 .withBundleName(getBundleName())  
  12.                 .withAbilityName(CommonData.PICTURE_GAME_SERVICE_NAME)  
  13.                 .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)  
  14.                 .build();  
  15.         connectPaIntent.setOperation(operation);  
  16.     ...  

連接成功后會(huì)回調(diào)onAbilityConnectDone方法,此時(shí)兩臺(tái)設(shè)備即建立了連接。然后調(diào)用sendDataToRemote方法進(jìn)行數(shù)據(jù)發(fā)送,其中requestType為REQUEST_START_ABILITY,代碼如下所示:

  1. public void onAbilityConnectDone(ElementName elementName, IRemoteObject remote, int resultCode) {  
  2.     LogUtil.info(TAG, "onAbilityConnectDone......");  
  3.     connectAbility(elementName, remote, requestType);  
  4. }  
  5.    
  6. private void connectAbility(ElementName elementName, IRemoteObject remote, int requestType) {  
  7.     proxy = new PictureRemoteProxy(remote);  
  8.     LogUtil.error(TAG, "connectRemoteAbility done");  
  9.     if (proxy != null) {  
  10.         try {  
  11.             proxy.sendDataToRemote(requestType);  
  12.         } catch (RemoteException e) {  
  13.             LogUtil.error(TAG, "onAbilityConnectDone RemoteException");  
  14.         }  
  15.     }  

PictureGameServiceAbility服務(wù)中接收到了senDataToRemote的消息后會(huì)回調(diào)onRemoteRequest,因?yàn)榇藭r(shí)code為REQUEST_START_ABILITY,所以會(huì)拉起遠(yuǎn)程端的拼圖頁(yè)面,代碼如下所示:

  1. public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {  
  2.     ...  
  3.     if (code == REQUEST_START_ABILITY) {  
  4.         LogUtil.error(TAG, "RemoteServiceAbility::isFirstStart:");  
  5.         Intent secondIntent = new Intent();  
  6.         Operation operation = new Intent.OperationBuilder().withDeviceId("")  
  7.                 .withBundleName(getBundleName())  
  8.                 .withAbilityName(CommonData.ABILITY_MAIN)  
  9.                 .withAction(CommonData.PICTURE_PAGE)  
  10.                 .build();  
  11.     ...  
  12.     } else {  
  13.     ...  
  14.     }  
  15.     ...  

以上即完成了本地端到遠(yuǎn)程端的單向通信。遠(yuǎn)程端設(shè)備拉起后,會(huì)在PictureGameAbilitySlice執(zhí)行onStart方法,進(jìn)而執(zhí)行initRemoteView方法,其中會(huì)再次調(diào)用connectRemotePa方法,此時(shí)傳遞的標(biāo)識(shí)位為REQUEST_SEND_DATA,不會(huì)重復(fù)拉起本地端設(shè)備的頁(yè)面,只會(huì)建立數(shù)據(jù)連接,代碼如下所示:

  1. private void initRemoteView(Intent intent) {  
  2.     if (!isLocal) {  
  3.         remoteDeviceId = intent.getStringParam(CommonData.KEY_REMOTE_DEVICEID);  
  4.         connectRemotePa(remoteDeviceId, PictureRemoteProxy.REQUEST_SEND_DATA);  
  5.         if (imageIndex != null) {  
  6.             updateDataInfo(intent);  
  7.         }  
  8.     }  

如上步驟即完成了本地端和遠(yuǎn)程端的雙向通信,實(shí)現(xiàn)了數(shù)據(jù)互傳的功能。

Step 3 - 拼圖操作(具體業(yè)務(wù),準(zhǔn)備要發(fā)送的數(shù)據(jù))

因篇幅有限且拼圖游戲不屬于本篇Codelab想為您重點(diǎn)介紹的HarmonyOS特性,因此不再贅述,請(qǐng)讀者自行理解。我們告訴您的結(jié)論是,完成一次拼圖操作需要記錄三個(gè)關(guān)鍵數(shù)據(jù):移動(dòng)圖片的下標(biāo)moveImageId,移動(dòng)圖片的位置movePosition和最終排列的圖片下標(biāo)imageIndex。點(diǎn)擊一次拼圖,我們就會(huì)記錄以上三個(gè)數(shù)據(jù),代碼如下所示:

  1. private class ImageClick implements Component.ClickedListener {  
  2.     @Override  
  3.     public void onClick(Component component) {  
  4.         int imageId = component.getId();  
  5.         for (int position = 0; position < imageIndex.length; position++) {  
  6.             if (imageId == imageResourceTable[position]) {  
  7.                 // 完成圖片移動(dòng),并記錄移動(dòng)信息  
  8.                 moveFun(imageId, position);  
  9.                 moveImageId = imageId;  
  10.                 movePosition = position;  
  11.             }  
  12.         }  
  13.         // 刷新頁(yè)面顯示  
  14.         setImageAndDecodeBounds(imageIndex);  
  15.         // 發(fā)送數(shù)據(jù)  
  16.         senDataToRemoteFun();  
  17.     }  

Step 4 - 發(fā)送數(shù)據(jù)

發(fā)送數(shù)據(jù)的流程和早教算數(shù)題一樣,我們?cè)俅螢槟鲈敿?xì)介紹,您可參考早教算數(shù)題進(jìn)行對(duì)照學(xué)習(xí)。一次拼圖完成后,會(huì)調(diào)用senDataToRemote方法,將關(guān)鍵信息(imageIndex、moveImageId、movePosition)存放到data中并發(fā)送出去,代碼如下所示:

  1. private void senDataToRemote(int requestType) throws RemoteException {  
  2.     MessageParcel data = MessageParcel.obtain();  
  3.      ...  
  4.     try {  
  5.      ...  
  6.         data.writeIntArray(imageIndex);  
  7.         data.writeInt(moveImageId);  
  8.         data.writeInt(movePosition);  
  9.         remote.sendRequest(requestType, data, reply, option);  
  10.      ...  
  11.     } catch (RemoteException e) {  
  12.      ...  
  13.     } finally {  
  14.      ...  
  15.     }  

其中,remote.sendRequest是將數(shù)據(jù)發(fā)送到PictureGameServiceAbility的服務(wù)中(因?yàn)椴襟E2是和PictureGameServiceAbility建立服務(wù)連接),建立服務(wù)連接后會(huì)回調(diào)onRemoteRequest方法。接收順序應(yīng)該和發(fā)送順序一致(imageIndex、moveImageId、movePosition),否則會(huì)操作接收錯(cuò)誤的情況,代碼如下所示:

  1. public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {  
  2.     LogUtil.info(TAG, "onRemoteRequest......");  
  3.     int[] imageIndex = data.readIntArray();  
  4.     int moveImageId = data.readInt();  
  5.     int movePosition = data.readInt();  
  6.      ...  
  7.     LogUtil.info(TAG, "receive number:" + imageIndex.length);  
  8.     reply.writeInt(ERR_OK);  
  9.     if (code == REQUEST_START_ABILITY) {  
  10.      ...  
  11.     } else {  
  12.         sendEvent(imageIndex, moveImageId, movePosition);  
  13.     }  
  14.     return true;  

在onRemoteRequest方法中,會(huì)調(diào)用sendEvent將imageIndex、moveImageId、movePosition發(fā)送出去。sendEvent是通過注冊(cè)公共事件的方式進(jìn)行數(shù)據(jù)傳遞的,其注冊(cè)的公共事件是CommonData. PICTURE_GAME_EVENT,代碼如下所示:

  1. private void sendEvent(int[] imageIndex, int moveImageId, int movePosition) {  
  2.     try {  
  3.         Intent intent = new Intent();  
  4.         Operation operation = new Intent.OperationBuilder()  
  5.                 .withAction(CommonData.PICTURE_GAME_EVENT)  
  6.                 .build();  
  7.         intent.setOperation(operation);  
  8.         intent.setParam(CommonData.KEY_IMAGE_INDEX, imageIndex);  
  9.         intent.setParam(CommonData.KEY_MOVE_IMAGE_ID, moveImageId);  
  10.         intent.setParam(CommonData.KEY_MOVE_POSITION, movePosition);  
  11.         CommonEventData eventData = new CommonEventData(intent);  
  12.         CommonEventManager.publishCommonEvent(eventData);  
  13.     } catch (RemoteException e) {  
  14.         LogUtil.error(TAG, "publishCommonEvent occur exception.");  
  15.     }  

Step 5 - 接收數(shù)據(jù)

接收數(shù)據(jù)的代碼流程和早教算數(shù)題一樣,我們?cè)俅螢槟鲈敿?xì)介紹,您可參考早教算數(shù)題進(jìn)行對(duì)照學(xué)習(xí)。PictureGameAbilitySlice會(huì)訂閱CommonData. PICTURE_GAME_EVENT的公共事件,代碼如下所示:

  1. private void subscribe() {  
  2.     MatchingSkills matchingSkills = new MatchingSkills();  
  3.     matchingSkills.addEvent(CommonData.PICTURE_GAME_EVENT);  
  4.     matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_SCREEN_ON);  
  5.     CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);  
  6.     subscriber = new MyCommonEventSubscriber(subscribeInfo);  
  7.     try {  
  8.         CommonEventManager.subscribeCommonEvent(subscriber);  
  9.     } catch (RemoteException e) {  
  10.         LogUtil.error("""subscribeCommonEvent occur exception.");  
  11.     }  

當(dāng)訂閱到相關(guān)事件時(shí)會(huì)回調(diào)onReceiveEvent方法,此時(shí)即可將imageIndex、moveImageId、movePosition解析出來,并調(diào)用updateDataInfo更新對(duì)端的布局文件,代碼如下所示:

  1. @Override  
  2. public void onReceiveEvent(CommonEventData commonEventData) {  
  3.     ...  
  4.     Intent intent = commonEventData.getIntent();  
  5.     updateDataInfo(intent);  
  6. }  
  7.    
  8. private void updateDataInfo(Intent intent) {  
  9.     imageIndex = intent.getIntArrayParam(CommonData.KEY_IMAGE_INDEX);  
  10.     moveImageId = intent.getIntParam(CommonData.KEY_MOVE_IMAGE_ID, -1);  
  11.     movePosition = intent.getIntParam(CommonData.KEY_MOVE_POSITION, -1);  
  12.     getUITaskDispatcher().delayDispatch(() -> setImageAndDecodeBounds(imageIndex), DELAY_TIME);  

通過選擇設(shè)備、建立連接、發(fā)送數(shù)據(jù)、接收數(shù)據(jù)這幾個(gè)關(guān)鍵步驟,您就可以實(shí)現(xiàn)兩臺(tái)設(shè)備的同步拼圖的功能。以上兩個(gè)案例,我們完整的學(xué)習(xí)了兩臺(tái)設(shè)備之間的數(shù)據(jù)交互,體驗(yàn)了HarmonyOS分布式特性,相信您一定有所收獲。

—-結(jié)束

說明:

以上代碼僅demo演示參考使用,產(chǎn)品化的代碼需要考慮數(shù)據(jù)校驗(yàn)和國(guó)際化。

8. 回顧和總結(jié)

本篇Codelab通過一個(gè)親子早教系統(tǒng),完整的為您介紹了早教算數(shù)題和益智拼圖游戲兩個(gè)綜合案例,旨在幫助您快速了解HarmonyOS應(yīng)用開發(fā)、多屏互動(dòng)、分布式跨設(shè)備協(xié)同的功能了解。您需要重點(diǎn)掌握跨設(shè)備協(xié)同、分布式任務(wù)調(diào)度、公共事件三項(xiàng)HarmonyOS能力。特別的,我們通過拆解步驟的方式詳細(xì)為您介紹了如何在兩臺(tái)設(shè)備之間進(jìn)行數(shù)據(jù)傳遞,這是您需要重點(diǎn)學(xué)習(xí)和掌握的知識(shí)點(diǎn)。

另外,本篇Codelab的兩個(gè)案例還可以通過分布式數(shù)據(jù)服務(wù)和分布式文件服務(wù)去優(yōu)化代碼,我們將在后續(xù)Codelab中為您介紹這方面的知識(shí)點(diǎn)和案例。

9. 恭喜您

  • 目前您已經(jīng)成功完成了Codelab并且學(xué)到了:
  • 常用布局、自定義控件的使用
  • Page Ability、Service Ability、Intent
  • 多屏互動(dòng)、分布式跨設(shè)備協(xié)同、分布式任務(wù)調(diào)度
  • 公共事件

10. 參考

gitee源碼

github源碼

想了解更多內(nèi)容,請(qǐng)?jiān)L問:

51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

https://harmonyos.51cto.com

 

責(zé)任編輯:jianghua 來源: 鴻蒙社區(qū)
相關(guān)推薦

2021-05-28 09:52:00

鴻蒙HarmonyOS應(yīng)用

2020-11-06 12:12:35

HarmonyOS

2019-10-10 09:16:34

Zookeeper架構(gòu)分布式

2023-05-29 14:07:00

Zuul網(wǎng)關(guān)系統(tǒng)

2017-09-01 05:35:58

分布式計(jì)算存儲(chǔ)

2019-06-19 15:40:06

分布式鎖RedisJava

2021-07-23 08:57:32

鴻蒙HarmonyOS應(yīng)用

2017-10-27 08:40:44

分布式存儲(chǔ)剪枝系統(tǒng)

2023-10-26 18:10:43

分布式并行技術(shù)系統(tǒng)

2024-03-01 09:53:34

2018-07-17 08:14:22

分布式分布式鎖方位

2022-06-27 08:21:05

Seata分布式事務(wù)微服務(wù)

2023-05-12 08:23:03

分布式系統(tǒng)網(wǎng)絡(luò)

2020-09-29 19:20:05

鴻蒙

2023-02-11 00:04:17

分布式系統(tǒng)安全

2022-06-21 08:27:22

Seata分布式事務(wù)

2011-03-28 13:39:45

nagios分布式

2022-10-25 14:05:47

共識(shí)算法系統(tǒng)

2017-07-26 15:08:05

大數(shù)據(jù)分布式事務(wù)

2021-12-13 11:07:10

鴻蒙HarmonyOS應(yīng)用
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)