HarmonyOS 分布式親子教育
1. 項目介紹
遠程教育,多屏協同是智慧教育的一個重要場景。本篇Codelab通過一個親子早教系統,完成了分布式早教算數題和分布式拼圖游戲兩個綜合案例,旨在幫助開發者快速了解HarmonyOS應用開發、多屏互動和分布式跨設備協同的體驗。
本篇Codelab將為您重點介紹Page Ability、Service Ability、Intent以及分布式任務調度、公共事件等。同時我們還將為您介紹多屏互動,分布式跨設備協同、繪圖畫布的使用、經典拼圖算法。正式介紹之前,我們先對親子早教系統進行展示,讓您快速了解到本篇Codelab所實現的功能。
功能1:早教算數題
點擊早教算數題,系統會為您隨機出一道兩位數的加法,點擊實時輔導會拉起兩個畫布,本地端可以用黑色筆跡進行草稿運算,遠程端可以用紅色筆跡進行實時指導,操作步驟兩端實時同步,效果圖如下所示。

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

想知道上述兩個親子游戲是如何實現的嗎?快來跟隨我們的Codelab進行學習吧。
2. 搭建HarmonyOS環境
搭建HarmonyOS環境
- 安裝DevEco Studio,詳情請參考DevEco Studio下載。
- 設置DevEco Studio開發環境,DevEco Studio開發環境需要依賴于網絡環境,需要連接上網絡才能確保工具的正常使用,可以根據如下兩種情況來配置開發環境
- 如果可以直接訪問Internet,只需進行下載HarmonyOS SDK操作
- 如果網絡不能直接訪問Internet,需要通過代理服務器才可以訪問,請參考配置開發環境
說明:
如需要在手機中運行程序,則需要提前申請證書,如使用模擬器可忽略
準備密鑰和證書請求文件
申請調試證書
你可以通過如下設備完成本CodeLab:
開啟開發者模式的HarmonyOS真機
DevEco Studio中的手機模擬器(模擬器暫不支持分布式調試)
3. 代碼結構解讀
本篇Codelab我們只是對核心代碼進行講解,您可以在最后的參考中下載完整代碼,首先來介紹下整個工程的代碼結構:

- devices:封裝了選擇設備的Dialog,您可直接調用SelectDeviceDialog里面的相關方法。
- point:封裝了繪圖的相關功能,您可直接調用DrawPoint里面的相關方法。
- slice:MainAbilitySlice為應用主頁面,MathGameAbilitySlice為早教算數題主頁面,MathDrawRemSlice為早教算數題繪圖頁面,PictureGameAbilitySlice為拼圖游戲主頁面。
- utils:封裝了公共方法和公共數據。
- MathGameServiceAbility、PictureGameServiceAbility:供遠端連接的Service Ability。
- resources:存放工程使用到的資源文件,其中resources\base\layout下存放xml布局文件;resources\base\media下存放圖片資源。
- config.json:配置文件。
4. 親子早教系統頁面流轉
親子早教系統主頁面和各個游戲頁面的布局如下圖所示,首先給大家介紹一下相關的布局代碼。

首頁的布局文件為ability_main.xml、頁面控制邏輯MainAbilitySlice;早教系統的布局文件為math_game.xml、頁面控制邏輯MathGameAbilitySlice;拼圖游戲的布局文件為ability_picture.xml、頁面控制邏輯PictureGameAbilitySlice。要實現頁面的相互流轉首先需要在MainAbility中設置路由,并在MainAbilitySlice中實現按鈕的點擊事件。
以跳轉早教算數題為例,首先需要在MainAbility中設置路由,代碼如下所示:
- addActionRoute(CommonData.MATH_PAGE, MathGameAbilitySlice.class.getName());
而后,需要在MainAbilitySlice中添加點擊事件,代碼如下所示:
- private void mathGame() {
- LogUtil.info(TAG, "Click ResourceTable Id_math_game");
- Intent mathGameIntent = new Intent();
- Operation operationMath = new Intent.OperationBuilder()
- .withBundleName(getBundleName())
- .withAbilityName(CommonData.ABILITY_MAIN)
- .withAction(CommonData.MATH_PAGE)
- .build();
- mathGameIntent.setOperation(operationMath);
- startAbility(mathGameIntent);
- }
至此,您已實現跳轉到早教算數題和益智拼圖游戲兩個頁面了:
- 早教算數題的出題邏輯可以參考setQuestion和checkAnswer兩個方法
- 拼圖游戲圖片亂序、移動圖片、判斷游戲結束、重新開始可分別參考pictureRandom、moveFun、gameOverFun、restartFun相關方法
因篇幅有限且相關游戲算法不屬于本篇Codelab想為您重點介紹的HarmonyOS特性,因此不再贅述,您可自行調用相關函數、理解相關代碼實現游戲功能,附件代碼中也已進行了詳細的注釋。以上即完成了單機版本的HarmonyOS應用開發,接下來我們將為您重點介紹基于HarmonyOS分布式能力的相關系統設計。
5. 發現設備和建立連接
在早教算數題中點擊實時輔導或者在拼圖游戲中點擊親子協同,會彈出選擇設備頁面,如下圖所示。我們已經在devices目錄下為您封裝好了選擇設備的dialog,具體代碼請參考DevicesListAdapter(設備列表適配器)、item_device_list.xml (設備列表item布局)、SelectDeviceDialog(選擇設備列表的彈窗)、dialog_select_device.xml(彈窗布局)。

SelectDeviceDialog中選擇設備彈窗的構造函數如下,需要傳入三個參數:上下文、設備列表、選擇結果的回調事件。在業務代碼中調用如下構造函數即可打開選擇設備的彈窗,代碼如下所示:
- public SelectDeviceDialog(Context context, List<DeviceInfo> devices, SelectResultListener listener) {
- initView(context, devices, listener);
- }
其中List
- private void getDevices() {
- if (devices.size() > 0) {
- devices.clear();
- }
- List<DeviceInfo> deviceInfos =
- DeviceManager.getDeviceList(ohos.distributedschedule.interwork.DeviceInfo.FLAG_GET_ONLINE_DEVICE);
- LogUtil.info(TAG, "deviceInfos size is :" + deviceInfos.size());
- devices.addAll(deviceInfos);
- showDevicesDialog();
- }
選擇結果的回調事件可以根據業務的不同傳入不同的回調事件,例如早教算數題中選擇設備后,需要拉起本地端和遠程端兩個畫布,此訂閱事件為startLocalFa和startRemoteFa,代碼如下所示:
- private void showDevicesDialog() {
- new SelectDeviceDialog(this, devices, deviceInfo -> {
- startLocalFa(deviceInfo.getDeviceId());
- startRemoteFa(deviceInfo.getDeviceId());
- }).show();
- }
而拼圖游戲需要和另外一臺設備建立連接,拉起另外一臺設備的拼圖頁面,此回調事件為connectRemotePa,代碼如下所示:
- private void showDevicesDialog() {
- new SelectDeviceDialog(this, devices, deviceInfo -> {
- connectRemotePa(deviceInfo.getDeviceId(), PictureRemoteProxy.REQUEST_START_ABILITY);
- }).show();
- }
需要說明的是回調函數中deviceInfo是單擊選中的設備信息,具體實現在SelectDeviceDialog的initView方法中,代碼如下所示:
- DevicesListAdapter devicesListAdapter = new DevicesListAdapter(devices, context);
- devicesListContainer.setItemProvider(devicesListAdapter);
- devicesListContainer.setItemClickedListener((listContainer, component, position, l) -> {
- listener.callBack(devices.get(position));
- commonDialog.hide();
- });
至此,您已經完成了多設備協同中發現設備和建立連接這一關鍵步驟。
說明:
實現遠程啟動FA,需要至少兩個設備處于同一個分布式網絡中,可以通過如下操作實現:
所有設備接入同一網絡;
所有設備登錄相同華為賬號;
所有設備上開啟"設置->更多連接->多設備協同 "。
6. 早教算數題
點擊早教算數題,系統會為您隨機出一道兩位數的加法,點擊實時輔導會拉起兩個畫布,本地端可以用黑色筆跡進行草稿運算,遠程端可以用紅色筆跡進行實時指導,操作步驟兩端實時同步,效果如下圖所示。接下來我們將為您詳細介紹如何利用HarmonyOS分布式技術實現兩端的同步繪制。

Step 1 - 選擇設備
詳見"5 發現設備和建立連接"。
Step 2 - 建立連接
點擊選擇設備后會進入繪圖頁面,在onStart方法中會調用初始化并連接設備的函數initAndConnectDevice,通過intent傳參獲取遠程設備的remoteDeviceId,并調用connectRemotePa進行連接,代碼如下所示:
- private void initAndConnectDevice(Intent intent) {
- // 頁面初始化
- this.context = MathDrawRemSlice.this;
- String remoteDeviceId = intent.getStringParam(CommonData.KEY_REMOTE_DEVICEID);
- isLocal = intent.getBooleanParam(CommonData.KEY_IS_LOCAL, false);
- if (findComponentById(ResourceTable.Id_text_title) instanceof Text) {
- Text textTitle = (Text) findComponentById(ResourceTable.Id_text_title);
- textTitle.setText(isLocal ? "本地端" : "遠程端");
- }
- // 連接遠程服務
- if (!remoteDeviceId.isEmpty()) {
- connectRemotePa(remoteDeviceId);
- } else {
- LogUtil.info(TAG, "localDeviceId is null");
- }
- }
其中調用connectRemotePa方法后會和MathGameServiceAbility建立服務連接,代碼如下所示:
- Intent connectPaIntent = new Intent();
- Operation operation = new Intent.OperationBuilder()
- .withDeviceId(deviceId)
- .withBundleName(getBundleName())
- .withAbilityName(CommonData.MATH_GAME_SERVICE_NAME)
- .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
- .build();
- connectPaIntent.setOperation(operation);
連接成功后會回調onAbilityConnectDone方法,此時兩臺設備即建立了連接。而后,可以調用senDataToRemote方法進行數據發送,代碼如下所示:
- public void onAbilityConnectDone(ElementName elementName, IRemoteObject remote, int resultCode) {
- LogUtil.info(TAG, "onAbilityConnectDone......");
- connectAbility(elementName, remote, requestType);
- }
- private void connectAbility(ElementName elementName, IRemoteObject remote, int requestType) {
- proxy = new PictureRemoteProxy(remote);
- LogUtil.error(TAG, "connectRemoteAbility done");
- if (proxy != null) {
- try {
- proxy.senDataToRemote(requestType);
- } catch (RemoteException e) {
- LogUtil.error(TAG, "onAbilityConnectDone RemoteException");
- }
- }
- }
Step 3 - 繪圖操作(具體業務,準備要發送的數據)
DrawPoint是一個繪圖的工具類,繪制一個點會記錄三個信息:X軸坐標、Y軸坐標、是否是最后一個點。將此信息記錄到數組pointsX、pointsY、isLastPointArray中,并封裝為List
- private float[] pointsX;
- private float[] pointsY;
- private boolean[] isLastPointArray;
- private List<MyPoint> allPoints = new ArrayList<>();
其中,是否為最后一個點是通過TouchEvent.PRIMARY_POINT_UP事件進行區分的。當檢測到該事件時,會調用回調函數callBack.callBack(allPoints) ,代碼如下所示:
- if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
- point.setLastPoint(true);
- allPoints.add(point);
- callBack.callBack(allPoints);
- }
此時會向上回調到setOnDrawBack方法,代碼如下所示:
- public void setOnDrawBack(OnDrawCallBack callBack) {
- this.callBack = callBack;
- }
MathDrawRemSlice中initDraw()會初始化畫布,一次繪制完成后會回調setOnDrawBack方法,其中points是上述步驟中繪制的點,將其存入數組pointsX、pointsY、isLastPointArray中,代碼如下所示:
- drawl.setOnDrawBack(points -> {
- if (points != null && points.size() > 1) {
- pointsX = new float[points.size()];
- pointsY = new float[points.size()];
- isLastPoint = new boolean[points.size()];
- for (int i = 0; i < points.size(); i++) {
- pointsX[i] = points.get(i).getPositionX();
- pointsY[i] = points.get(i).getPositionY();
- isLastPoint[i] = points.get(i).isLastPoint();
- }
- ...
- }
- });
此時就完成了本地畫布繪制并將繪制的點信息存放到了數組pointsX、pointsY、isLastPointArray中,即已經準備好了要發送的數據。
Step 4 - 發送數據
發送數據涉及到Service Ability、分布式任務調度、公共事件三項HarmonyOS能力,如果您還不熟悉相關基礎知識可以先參考官方文檔進行學習。一次繪制完成后,會調用senDataToRemote方法,將繪制的點信息(pointsX、pointsY、isLastPointArray)存放到data中并發送出去,代碼如下所示:
- private void senDataToRemote(int requestType) throws RemoteException {
- LogUtil.info(TAG, "send data to local draw service");
- MessageParcel data = MessageParcel.obtain();
- MessageParcel reply = MessageParcel.obtain();
- MessageOption option = new MessageOption(MessageOption.TF_SYNC);
- try {
- if (pointsX != null && pointsY != null && isLastPoint != null) {
- data.writeFloatArray(pointsX);
- data.writeFloatArray(pointsY);
- data.writeBooleanArray(isLastPoint);
- }
- remote.sendRequest(requestType, data, reply, option);
- int ec = reply.readInt();
- if (ec != ERR_OK) {
- LogUtil.error(TAG, "RemoteException:");
- }
- } catch (RemoteException e) {
- LogUtil.error(TAG, "RemoteException:");
- } finally {
- data.reclaim();
- reply.reclaim();
- }
- }
其中,remote.sendRequest是將數據發送到MathGameServiceAbility的服務中(因為步驟2是和MathGameServiceAbility建立服務連接),建立服務連接后會回調onRemoteRequest方法,代碼如下所示:
- @Override
- public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
- LogUtil.info(TAG, "onRemoteRequest......");
- float[] pointsX = data.readFloatArray();
- float[] pointsY = data.readFloatArray();
- boolean[] isLastPointArray = data.readBooleanArray();
- reply.writeInt(ERR_OK);
- sendEvent(isLastPointArray, pointsX, pointsY);
- return true;
- }
在onRemoteRequest方法中,會調用sendEvent將數組pointsX、pointsY、isLastPointArray發送出去。sendEvent是通過公共事件的方式進行數據傳遞的,其注冊的公共事件是CommonData.MATH_DRAW_EVENT,代碼如下所示:
- private void sendEvent(boolean[] isLastPoint, float[] pointsX, float[] pointsY) {
- LogUtil.info(TAG, "sendEvent......");
- try {
- Intent intent = new Intent();
- Operation operation = new Intent.OperationBuilder()
- .withAction(CommonData.MATH_DRAW_EVENT)
- .build();
- intent.setOperation(operation);
- intent.setParam(CommonData.KEY_POINT_X, pointsX);
- intent.setParam(CommonData.KEY_POINT_Y, pointsY);
- intent.setParam(CommonData.KEY_IS_LAST_POINT, isLastPoint);
- CommonEventData eventData = new CommonEventData(intent);
- CommonEventManager.publishCommonEvent(eventData);
- } catch (RemoteException e) {
- LogUtil.error(TAG, "publishCommonEvent occur exception.");
- }
- }
Step 5 - 接收數據
MathDrawRemSlice中會訂閱CommonData.MATH_DRAW_EVENT的公共事件,代碼如下所示:
- private void subscribe() {
- MatchingSkills matchingSkills = new MatchingSkills();
- matchingSkills.addEvent(CommonData.MATH_DRAW_EVENT);
- matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_SCREEN_ON);
- CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
- subscriber = new MyCommonEventSubscriber(subscribeInfo);
- try {
- CommonEventManager.subscribeCommonEvent(subscriber);
- } catch (RemoteException e) {
- LogUtil.error("", "subscribeCommonEvent occur exception.");
- }
- }
當訂閱到相關事件時會回調onReceiveEvent方法,此時即可將pointsX、pointsY、isLastPointArray解析出來,然后調用drawl.setDrawParams(isLastPointArray, pointsX, pointsY)方法在遠程端進行繪制,代碼如下所示:
- public void onReceiveEvent(CommonEventData commonEventData) {
- Intent intent = commonEventData.getIntent();
- pointsX = intent.getFloatArrayParam(CommonData.KEY_POINT_X);
- pointsY = intent.getFloatArrayParam(CommonData.KEY_POINT_Y);
- isLastPoint = intent.getBooleanArrayParam(CommonData.KEY_IS_LAST_POINT);
- // 接收數據后,對遠程端畫布進行繪制
- drawl.setDrawParams(isLastPoint, pointsX, pointsY);
- LogUtil.info(TAG, "onReceiveEvent.....");
- }
Step 6 - 數據的雙向通信
步驟1-5中為您講解了數據的單向通信,即本地端向遠程端的交互。本例的繪圖是雙向通信,兩端都可以進行繪制,這是怎么做到的呢?原來在步驟1中選擇設備時,會調用startLocalFa和startRemoteFa,其中startLocalFa傳出的remoteDeviceId是所選擇的設備ID,startRemoteFa傳出的remoteDeviceId是當前設備的ID,由此實現了雙向通信,代碼如下所示:
- private void showDevicesDialog() {
- new SelectDeviceDialog(this, devices, deviceInfo -> {
- startLocalFa(deviceInfo.getDeviceId());
- startRemoteFa(deviceInfo.getDeviceId());
- }).show();
- }
- private void startLocalFa(String deviceId) {
- LogUtil.info(TAG, "startLocalFa......");
- Intent intent = new Intent();
- intent.setParam(CommonData.KEY_REMOTE_DEVICEID, deviceId);
- ...
- }
- private void startRemoteFa(String deviceId) {
- LogUtil.info(TAG, "startRemoteFa......");
- String localDeviceId = KvManagerFactory.getInstance()
- .createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getId();
- Intent intent = new Intent();
- intent.setParam(CommonData.KEY_REMOTE_DEVICEID, localDeviceId);
- ...
- }
通過選擇設備、建立連接、發送數據、接收數據這幾個關鍵步驟,您就可以實現兩臺設備的同步繪圖、實時顯示的功能。
—-結束
7. 益智拼圖游戲
點擊益智拼圖游戲會拉起一個九宮格的拼圖游戲(圖片會隨機亂序),點擊圖片可以進行拼圖。在本地端中點擊親子協同,遠程端會拉起一個一模一樣的游戲頁面,兩個設備可以進行實時互動,同步對圖片進行拼接,操作步驟亦可實時同步,效果圖如下所示。接下來我們將為您詳細介紹如何利用HarmonyOS分布式技術實現一個益智拼圖游戲。

Step 1 - 選擇設備
詳見"5 發現設備和建立連接"這一章節。
Step 2 - 建立連接并拉起設備
點擊親子協同后會記錄選擇設備的deviceId,調用connectRemotePa方法后會和PictureGameServiceAbility建立服務連接,其中標識位傳遞的是REQUEST_START_ABILITY,代碼如下所示:
- private void showDevicesDialog() {
- new SelectDeviceDialog(this, devices, deviceInfo -> {
- connectRemotePa(deviceInfo.getDeviceId(), PictureRemoteProxy.REQUEST_START_ABILITY);
- }).show();
- }
- private void connectRemotePa(String deviceId, int requestType) {
- if (!deviceId.isEmpty()) {
- Intent connectPaIntent = new Intent();
- Operation operation = new Intent.OperationBuilder()
- .withDeviceId(deviceId)
- .withBundleName(getBundleName())
- .withAbilityName(CommonData.PICTURE_GAME_SERVICE_NAME)
- .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
- .build();
- connectPaIntent.setOperation(operation);
- ...
- }
連接成功后會回調onAbilityConnectDone方法,此時兩臺設備即建立了連接。然后調用sendDataToRemote方法進行數據發送,其中requestType為REQUEST_START_ABILITY,代碼如下所示:
- public void onAbilityConnectDone(ElementName elementName, IRemoteObject remote, int resultCode) {
- LogUtil.info(TAG, "onAbilityConnectDone......");
- connectAbility(elementName, remote, requestType);
- }
- private void connectAbility(ElementName elementName, IRemoteObject remote, int requestType) {
- proxy = new PictureRemoteProxy(remote);
- LogUtil.error(TAG, "connectRemoteAbility done");
- if (proxy != null) {
- try {
- proxy.sendDataToRemote(requestType);
- } catch (RemoteException e) {
- LogUtil.error(TAG, "onAbilityConnectDone RemoteException");
- }
- }
- }
PictureGameServiceAbility服務中接收到了senDataToRemote的消息后會回調onRemoteRequest,因為此時code為REQUEST_START_ABILITY,所以會拉起遠程端的拼圖頁面,代碼如下所示:
- public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
- ...
- if (code == REQUEST_START_ABILITY) {
- LogUtil.error(TAG, "RemoteServiceAbility::isFirstStart:");
- Intent secondIntent = new Intent();
- Operation operation = new Intent.OperationBuilder().withDeviceId("")
- .withBundleName(getBundleName())
- .withAbilityName(CommonData.ABILITY_MAIN)
- .withAction(CommonData.PICTURE_PAGE)
- .build();
- ...
- } else {
- ...
- }
- ...
- }
以上即完成了本地端到遠程端的單向通信。遠程端設備拉起后,會在PictureGameAbilitySlice執行onStart方法,進而執行initRemoteView方法,其中會再次調用connectRemotePa方法,此時傳遞的標識位為REQUEST_SEND_DATA,不會重復拉起本地端設備的頁面,只會建立數據連接,代碼如下所示:
- private void initRemoteView(Intent intent) {
- if (!isLocal) {
- remoteDeviceId = intent.getStringParam(CommonData.KEY_REMOTE_DEVICEID);
- connectRemotePa(remoteDeviceId, PictureRemoteProxy.REQUEST_SEND_DATA);
- if (imageIndex != null) {
- updateDataInfo(intent);
- }
- }
- }
如上步驟即完成了本地端和遠程端的雙向通信,實現了數據互傳的功能。
Step 3 - 拼圖操作(具體業務,準備要發送的數據)
因篇幅有限且拼圖游戲不屬于本篇Codelab想為您重點介紹的HarmonyOS特性,因此不再贅述,請讀者自行理解。我們告訴您的結論是,完成一次拼圖操作需要記錄三個關鍵數據:移動圖片的下標moveImageId,移動圖片的位置movePosition和最終排列的圖片下標imageIndex。點擊一次拼圖,我們就會記錄以上三個數據,代碼如下所示:
- private class ImageClick implements Component.ClickedListener {
- @Override
- public void onClick(Component component) {
- int imageId = component.getId();
- for (int position = 0; position < imageIndex.length; position++) {
- if (imageId == imageResourceTable[position]) {
- // 完成圖片移動,并記錄移動信息
- moveFun(imageId, position);
- moveImageId = imageId;
- movePosition = position;
- }
- }
- // 刷新頁面顯示
- setImageAndDecodeBounds(imageIndex);
- // 發送數據
- senDataToRemoteFun();
- }
- }
Step 4 - 發送數據
發送數據的流程和早教算數題一樣,我們再次為您做詳細介紹,您可參考早教算數題進行對照學習。一次拼圖完成后,會調用senDataToRemote方法,將關鍵信息(imageIndex、moveImageId、movePosition)存放到data中并發送出去,代碼如下所示:
- private void senDataToRemote(int requestType) throws RemoteException {
- MessageParcel data = MessageParcel.obtain();
- ...
- try {
- ...
- data.writeIntArray(imageIndex);
- data.writeInt(moveImageId);
- data.writeInt(movePosition);
- remote.sendRequest(requestType, data, reply, option);
- ...
- } catch (RemoteException e) {
- ...
- } finally {
- ...
- }
- }
其中,remote.sendRequest是將數據發送到PictureGameServiceAbility的服務中(因為步驟2是和PictureGameServiceAbility建立服務連接),建立服務連接后會回調onRemoteRequest方法。接收順序應該和發送順序一致(imageIndex、moveImageId、movePosition),否則會操作接收錯誤的情況,代碼如下所示:
- public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
- LogUtil.info(TAG, "onRemoteRequest......");
- int[] imageIndex = data.readIntArray();
- int moveImageId = data.readInt();
- int movePosition = data.readInt();
- ...
- LogUtil.info(TAG, "receive number:" + imageIndex.length);
- reply.writeInt(ERR_OK);
- if (code == REQUEST_START_ABILITY) {
- ...
- } else {
- sendEvent(imageIndex, moveImageId, movePosition);
- }
- return true;
- }
在onRemoteRequest方法中,會調用sendEvent將imageIndex、moveImageId、movePosition發送出去。sendEvent是通過注冊公共事件的方式進行數據傳遞的,其注冊的公共事件是CommonData. PICTURE_GAME_EVENT,代碼如下所示:
- private void sendEvent(int[] imageIndex, int moveImageId, int movePosition) {
- try {
- Intent intent = new Intent();
- Operation operation = new Intent.OperationBuilder()
- .withAction(CommonData.PICTURE_GAME_EVENT)
- .build();
- intent.setOperation(operation);
- intent.setParam(CommonData.KEY_IMAGE_INDEX, imageIndex);
- intent.setParam(CommonData.KEY_MOVE_IMAGE_ID, moveImageId);
- intent.setParam(CommonData.KEY_MOVE_POSITION, movePosition);
- CommonEventData eventData = new CommonEventData(intent);
- CommonEventManager.publishCommonEvent(eventData);
- } catch (RemoteException e) {
- LogUtil.error(TAG, "publishCommonEvent occur exception.");
- }
- }
Step 5 - 接收數據
接收數據的代碼流程和早教算數題一樣,我們再次為您做詳細介紹,您可參考早教算數題進行對照學習。PictureGameAbilitySlice會訂閱CommonData. PICTURE_GAME_EVENT的公共事件,代碼如下所示:
- private void subscribe() {
- MatchingSkills matchingSkills = new MatchingSkills();
- matchingSkills.addEvent(CommonData.PICTURE_GAME_EVENT);
- matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_SCREEN_ON);
- CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
- subscriber = new MyCommonEventSubscriber(subscribeInfo);
- try {
- CommonEventManager.subscribeCommonEvent(subscriber);
- } catch (RemoteException e) {
- LogUtil.error("", "subscribeCommonEvent occur exception.");
- }
- }
當訂閱到相關事件時會回調onReceiveEvent方法,此時即可將imageIndex、moveImageId、movePosition解析出來,并調用updateDataInfo更新對端的布局文件,代碼如下所示:
- @Override
- public void onReceiveEvent(CommonEventData commonEventData) {
- ...
- Intent intent = commonEventData.getIntent();
- updateDataInfo(intent);
- }
- private void updateDataInfo(Intent intent) {
- imageIndex = intent.getIntArrayParam(CommonData.KEY_IMAGE_INDEX);
- moveImageId = intent.getIntParam(CommonData.KEY_MOVE_IMAGE_ID, -1);
- movePosition = intent.getIntParam(CommonData.KEY_MOVE_POSITION, -1);
- getUITaskDispatcher().delayDispatch(() -> setImageAndDecodeBounds(imageIndex), DELAY_TIME);
- }
通過選擇設備、建立連接、發送數據、接收數據這幾個關鍵步驟,您就可以實現兩臺設備的同步拼圖的功能。以上兩個案例,我們完整的學習了兩臺設備之間的數據交互,體驗了HarmonyOS分布式特性,相信您一定有所收獲。
—-結束
說明:
以上代碼僅demo演示參考使用,產品化的代碼需要考慮數據校驗和國際化。
8. 回顧和總結
本篇Codelab通過一個親子早教系統,完整的為您介紹了早教算數題和益智拼圖游戲兩個綜合案例,旨在幫助您快速了解HarmonyOS應用開發、多屏互動、分布式跨設備協同的功能了解。您需要重點掌握跨設備協同、分布式任務調度、公共事件三項HarmonyOS能力。特別的,我們通過拆解步驟的方式詳細為您介紹了如何在兩臺設備之間進行數據傳遞,這是您需要重點學習和掌握的知識點。
另外,本篇Codelab的兩個案例還可以通過分布式數據服務和分布式文件服務去優化代碼,我們將在后續Codelab中為您介紹這方面的知識點和案例。
9. 恭喜您
- 目前您已經成功完成了Codelab并且學到了:
- 常用布局、自定義控件的使用
- Page Ability、Service Ability、Intent
- 多屏互動、分布式跨設備協同、分布式任務調度
- 公共事件
10. 參考