日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
HarmonyOS分布式親子教育

HarmonyOS 分布式親子教育

作者:奶蓋 2021-07-22 10:20:21

系統(tǒng)

分布式

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

想了解更多內(nèi)容,請訪問:

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

1. 項目介紹

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

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

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

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

功能2:益智拼圖游戲

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

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

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

搭建HarmonyOS環(huán)境

  • 安裝DevEco Studio,詳情請參考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,只需進行下載HarmonyOS SDK操作
  2. 如果網(wǎng)絡(luò)不能直接訪問Internet,需要通過代理服務(wù)器才可以訪問,請參考配置開發(fā)環(huán)境

說明:

如需要在手機中運行程序,則需要提前申請證書,如使用模擬器可忽略

準備密鑰和證書請求文件

申請調(diào)試證書

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

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

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

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

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

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

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

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

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

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

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

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

  
 
 
 
  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);  

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

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

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

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

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

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

  
 
 
 
  1. public SelectDeviceDialog(Context context, List 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 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è)備后,需要拉起本地端和遠程端兩個畫布,此訂閱事件為startLocalFa和startRemoteFa,代碼如下所示:

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

 而拼圖游戲需要和另外一臺設(shè)備建立連接,拉起另外一臺設(shè)備的拼圖頁面,此回調(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è)備信息,具體實現(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)鍵步驟。

說明:

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

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

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

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

6. 早教算數(shù)題

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

Step 1 - 選擇設(shè)備

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

Step 2 - 建立連接

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

  
 
 
 
  1. private void initAndConnectDevice(Intent intent) {  
  2.     // 頁面初始化  
  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 ? "本地端" : "遠程端");  
  9.     }  
  10.   
  11.     // 連接遠程服務(wù)  
  12.     if (!remoteDeviceId.isEmpty()) {  
  13.         connectRemotePa(remoteDeviceId);  
  14.     } else {  
  15.         LogUtil.info(TAG, "localDeviceId is null");  
  16.     }  

 其中調(diào)用connectRemotePa方法后會和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); 

 連接成功后會回調(diào)onAbilityConnectDone方法,此時兩臺設(shè)備即建立了連接。而后,可以調(diào)用senDataToRemote方法進行數(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ù),準備要發(fā)送的數(shù)據(jù))

DrawPoint是一個繪圖的工具類,繪制一個點會記錄三個信息:X軸坐標、Y軸坐標、是否是最后一個點。將此信息記錄到數(shù)組pointsX、pointsY、isLastPointArray中,并封裝為List allPoints的數(shù)據(jù)類型,定義如下:

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

 其中,是否為最后一個點是通過TouchEvent.PRIMARY_POINT_UP事件進行區(qū)分的。當檢測到該事件時,會調(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. }  

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

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

 MathDrawRemSlice中initDraw()會初始化畫布,一次繪制完成后會回調(diào)setOnDrawBack方法,其中points是上述步驟中繪制的點,將其存入數(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ù)組pointsX、pointsY、isLastPointArray中,即已經(jīng)準備好了要發(fā)送的數(shù)據(jù)。

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

發(fā)送數(shù)據(jù)涉及到Service Ability、分布式任務(wù)調(diào)度、公共事件三項HarmonyOS能力,如果您還不熟悉相關(guān)基礎(chǔ)知識可以先參考官方文檔進行學(xué)習(xí)。一次繪制完成后,會調(diào)用senDataToRemote方法,將繪制的點信息(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ù)中(因為步驟2是和MathGameServiceAbility建立服務(wù)連接),建立服務(wù)連接后會回調(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方法中,會調(diào)用sendEvent將數(shù)組pointsX、pointsY、isLastPointArray發(fā)送出去。sendEvent是通過公共事件的方式進行數(shù)據(jù)傳遞的,其注冊的公共事件是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中會訂閱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.     }  

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

  
 
 
 
  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ù)后,對遠程端畫布進行繪制  
  7.     drawl.setDrawParams(isLastPoint, pointsX, pointsY);  
  8.     LogUtil.info(TAG, "onReceiveEvent.....");  

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

步驟1-5中為您講解了數(shù)據(jù)的單向通信,即本地端向遠程端的交互。本例的繪圖是雙向通信,兩端都可以進行繪制,這是怎么做到的呢?原來在步驟1中選擇設(shè)備時,會調(diào)用startLocalFa和startRemoteFa,其中startLocalFa傳出的remoteDeviceId是所選擇的設(shè)備ID,startRemoteFa傳出的remoteDeviceId是當前設(shè)備的ID,由此實現(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ù)這幾個關(guān)鍵步驟,您就可以實現(xiàn)兩臺設(shè)備的同步繪圖、實時顯示的功能。

—-結(jié)束

7. 益智拼圖游戲

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

Step 1 - 選擇設(shè)備

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

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

點擊親子協(xié)同后會記錄選擇設(shè)備的deviceId,調(diào)用connectRemotePa方法后會和PictureGameServiceAbility建立服務(wù)連接,其中標識位傳遞的是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.     ...  

連接成功后會回調(diào)onAbilityConnectDone方法,此時兩臺設(shè)備即建立了連接。然后調(diào)用sendDataToRemote方法進行數(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的消息后會回調(diào)onRemoteRequest,因為此時code為REQUEST_START_ABILITY,所以會拉起遠程端的拼圖頁面,代碼如下所示:

  
 
 
 
  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.     ...  

以上即完成了本地端到遠程端的單向通信。遠程端設(shè)備拉起后,會在PictureGameAbilitySlice執(zhí)行onStart方法,進而執(zhí)行initRemoteView方法,其中會再次調(diào)用connectRemotePa方法,此時傳遞的標識位為REQUEST_SEND_DATA,不會重復(fù)拉起本地端設(shè)備的頁面,只會建立數(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.     }  

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

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

因篇幅有限且拼圖游戲不屬于本篇Codelab想為您重點介紹的HarmonyOS特性,因此不再贅述,請讀者自行理解。我們告訴您的結(jié)論是,完成一次拼圖操作需要記錄三個關(guān)鍵數(shù)據(jù):移動圖片的下標moveImageId,移動圖片的位置movePosition和最終排列的圖片下標imageIndex。點擊一次拼圖,我們就會記錄以上三個數(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.                 // 完成圖片移動,并記錄移動信息  
  8.                 moveFun(imageId, position);  
  9.                 moveImageId = imageId;  
  10.                 movePosition = position;  
  11.             }  
  12.         }  
  13.         // 刷新頁面顯示  
  14.         setImageAndDecodeBounds(imageIndex);  
  15.         // 發(fā)送數(shù)據(jù)  
  16.         senDataToRemoteFun();  
  17.     }  

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

發(fā)送數(shù)據(jù)的流程和早教算數(shù)題一樣,我們再次為您做詳細介紹,您可參考早教算數(shù)題進行對照學(xué)習(xí)。一次拼圖完成后,會調(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ù)中(因為步驟2是和PictureGameServiceAbility建立服務(wù)連接),建立服務(wù)連接后會回調(diào)onRemoteRequest方法。接收順序應(yīng)該和發(fā)送順序一致(imageIndex、moveImageId、movePosition),否則會操作接收錯誤的情況,代碼如下所示:

  
 
 
 
  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方法中,會調(diào)用sendEvent將imageIndex、moveImageId、movePosition發(fā)送出去。sendEvent是通過注冊公共事件的方式進行數(shù)據(jù)傳遞的,其注冊的公共事件是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ù)題一樣,我們再次為您做詳細介紹,您可參考早教算數(shù)題進行對照學(xué)習(xí)。PictureGameAbilitySlice會訂閱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.     }  

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

  
 
 
 
  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ù)這幾個關(guān)鍵步驟,您就可以實現(xiàn)兩臺設(shè)備的同步拼圖的功能。以上兩個案例,我們完整的學(xué)習(xí)了兩臺設(shè)備之間的數(shù)據(jù)交互,體驗了HarmonyOS分布式特性,相信您一定有所收獲。

—-結(jié)束

說明:

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

8. 回顧和總結(jié)

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

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

9. 恭喜您

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

10. 參考

gitee源碼

github源碼


文章題目:HarmonyOS分布式親子教育
網(wǎng)頁路徑:http://www.5511xx.com/article/dhdpoce.html