概述
基于TS擴(kuò)展的聲明式開發(fā)范式編程語言編寫的一個(gè)分布式郵件系統(tǒng),可以由一臺(tái)設(shè)備拉起另一臺(tái)設(shè)備,每次改動(dòng)郵件內(nèi)容,都會(huì)同步更新兩臺(tái)設(shè)備的信息。效果圖如下:

搭建OpenHarmony開發(fā)環(huán)境
完成本篇Codelab我們首先要完成開發(fā)環(huán)境的搭建,本示例以Hi3516DV300開發(fā)板為例,參照以下步驟進(jìn)行:
- [獲取OpenHarmony系統(tǒng)版本]:標(biāo)準(zhǔn)系統(tǒng)解決方案(二進(jìn)制)。
以3.0版本為例:
- 搭建燒錄環(huán)境。
- 搭建開發(fā)環(huán)境。
- 開始前請(qǐng)參考[工具準(zhǔn)備],完成DevEco Studio的安裝和開發(fā)環(huán)境配置。
- 開發(fā)環(huán)境配置完成后,請(qǐng)參考[使用工程向?qū)創(chuàng)建工程(模板選擇“Empty Ability”),選擇JS或者eTS語言開發(fā)。
- 工程創(chuàng)建完成后,選擇使用[真機(jī)進(jìn)行調(diào)測(cè)]。
2.鴻蒙HarmonyOS與OpenHarmony技術(shù)知識(shí)籽料+mau123789是v直接拿

分布式組網(wǎng)
本章節(jié)以系統(tǒng)自帶的音樂播放器為例,介紹如何完成兩臺(tái)設(shè)備的分布式組網(wǎng)。
硬件準(zhǔn)備:準(zhǔn)備兩臺(tái)燒錄相同的版本系統(tǒng)的Hi3516DV300開發(fā)板A、B、一根網(wǎng)線及TYPE-C轉(zhuǎn)USB線。
保證開發(fā)板A、B上電開機(jī)狀態(tài),網(wǎng)線兩端分別連接開發(fā)板A、B的網(wǎng)口,將TYPE-C轉(zhuǎn)USB線先連接A,使用hdc_std.exe,在命令行輸入hdc_std shell ifconfig eth0 192.168.3.125,設(shè)置成功后,將TYPE-C轉(zhuǎn)USB線連接B,在命令行輸入hdc_std shell ifconfig eth0 192.168.3.126即可。
將設(shè)備A,B設(shè)置為互相信任的設(shè)備。
- 找到系統(tǒng)應(yīng)用“音樂”。

- 設(shè)備A打開音樂,點(diǎn)擊左下角流轉(zhuǎn)按鈕,彈出列表框,在列表中會(huì)展示遠(yuǎn)端設(shè)備的id。

- 選擇遠(yuǎn)端設(shè)備B的id,另一臺(tái)開發(fā)板(設(shè)備B)會(huì)彈出驗(yàn)證的選項(xiàng)框。

- 設(shè)備B點(diǎn)擊允許,設(shè)備B將會(huì)彈出隨機(jī)PIN碼,將設(shè)備B的PIN碼輸入到設(shè)備A的PIN碼填入框中。


配網(wǎng)完畢。
代碼結(jié)構(gòu)解讀
本篇Codelab只對(duì)核心代碼進(jìn)行講解,首先來介紹下整個(gè)工程的代碼結(jié)構(gòu):

- MainAbility:存放應(yīng)用主頁面。
- pages/index.ets:應(yīng)用主頁面。
- model:存放獲取組網(wǎng)內(nèi)的設(shè)備列表相關(guān)文件。
- RemoteDeviceModel.ets:獲取組網(wǎng)內(nèi)的設(shè)備列表。
- ServiceAbility:存放ServiceAbility相關(guān)文件。
- service.ts:service服務(wù),用于跨設(shè)備連接后通訊。
- resources :存放工程使用到的資源文件。
- resources/rawfile:存放工程中使用的圖片資源文件。
- config.json:配置文件。
實(shí)現(xiàn)頁面布局和樣式
在本章節(jié)中,您將學(xué)會(huì)如何制作一個(gè)簡(jiǎn)單的郵件界面。
實(shí)現(xiàn)主頁面布局和樣式。
在MainAbility/pages/index.ets 主界面文件中布局整個(gè)郵件頁面,包括收件人、發(fā)件人、主題、內(nèi)容等等,代碼如下:
@Entry @Component struct Index { private imageList: any[]= [] @Provide dataList: string[]= ['xiaohua@128.com','xiaoming@128.com','假期溫馨提示','2022年新春佳節(jié)即將來臨,請(qǐng)同學(xué)們細(xì)讀節(jié)前相關(guān)溫馨提示,保持辦公場(chǎng)所環(huán)境整潔,假期期間注意信息及個(gè)人安全,預(yù)祝全體同學(xué)新春快樂,虎虎生威!'] dialogController: CustomDialogController = new CustomDialogController({ builder: CustomDialogExample({ cancel: this.onCancel, confirm: this.onAccept }), cancel: this.existApp, autoCancel: true }) build() { Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) { Column() { Row() { Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { Text('?').fontSize(20).fontColor('#000000') Button('發(fā)送').width(70).fontSize(14).fontColor('#ffffff').backgroundColor('#fc4646') .onClick(() = > { RegisterDeviceListCallback(); this.dialogController.open(); }) } .height(50) .padding({ top: 10, right: 15, bottom: 10, left: 15 }) } Column() { Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) { Text('收件人').width(70).height(30).fontSize(15).fontColor('#969393') Text(this.dataList[0]).width('100%').height(30).fontSize(15).fontColor('#000000') } .padding({ top: 5, right: 15, bottom: 5, left: 15 }) Text().width('100%').height(1).backgroundColor('#f8f6f6') Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) { Text('發(fā)件人').width(70).height(30).fontSize(15).fontColor('#969393') Text(this.dataList[1]).width('100%').height(30).fontSize(15).fontColor('#000000') } .padding({ top: 5, right: 15, bottom: 5, left: 15 }) Text().width('100%').height(1).backgroundColor('#f8f6f6') Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) { Text('主題').width(50).height(30).fontSize(15).fontColor('#969393') Text(this.dataList[2]).width('100%').height(30).fontSize(15).fontColor('#000000') } .padding({ top: 5, right: 15, bottom: 5, left: 15 }) Text().width('100%').height(1).backgroundColor('#f8f6f6') TextArea({ placeholder: 'input your word', text: this.dataList[3]}).height('100%').width('100%') .onChange((value: string) = > { this.dataList[3] = value if(mRemote){ sendMessageToRemoteService(JSON.stringify(this.dataList)); } onDisconnectService(); }) } } Column() { Flex({ direction: FlexDirection.Row }) { List() { ForEach(this.imageList, (item) = > { ListItem() { Image(item).width(50).height(50).objectFit(ImageFit.Contain) }.editable(true) }, item = > item) } .listDirection(Axis.Horizontal) // 排列方向 .divider({ strokeWidth: 2, color: 0xFFFFFF, startMargin: 20, endMargin: 20 }) // 每行之間的分界線 }.width('100%').height(50).backgroundColor('#ccc') Text().width('100%').height(1).backgroundColor('#f8f6f6') Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) { Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) { Button({ stateEffect: false }) { Image($rawfile('icon_photo.png')).width(20).height(20) }.backgroundColor('#ffffff').margin({ right: 20 }) .onClick(() = > { RegisterDeviceListCallback(); this.dialogController.open(); }) Button({ stateEffect: false }) { Image($rawfile('icon_at.png')).width(20).height(20) }.backgroundColor('#ffffff') } Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.End }) { Button({ stateEffect: false }) { Image($rawfile('icon_distributed.png')).width(20).height(20) }.backgroundColor('#ffffff') .onClick(() = > { this.getDeviceList() }) Button({ stateEffect: false }) { Image($rawfile('icon_timer.png')).width(20).height(20) }.backgroundColor('#ffffff').margin({ left: 10, right: 10 }) Button({ stateEffect: false }) { Image($rawfile('icon_enclosure.png')).width(20).height(20) }.backgroundColor('#ffffff') } }.height(50).padding(15) } }.width('100%').padding({ top: 5, bottom: 15 }) } }在入口組件的生命周期函數(shù)aboutToAppear()中調(diào)用訂閱事件。如果Ability是被其他設(shè)備拉起的,在aboutToAppear()中調(diào)用featureAbility.getWant(),可通過want中的參數(shù)重新初始化dataList數(shù)組,入口組件的生命周期函數(shù)aboutToAppear()代碼如下:
async aboutToAppear() { this.subscribeEvent(); let self = this; // 當(dāng)被拉起時(shí),通過want傳遞的參數(shù)同步對(duì)端界面UI await featureAbility.getWant((error, want) = > { var status = want.parameters; if (want.parameters.dataList) { self.dataList = JSON.parse(status.dataList) // 遠(yuǎn)端被拉起后,連接對(duì)端的service if (want.parameters.remoteDeviceId) { let remoteDeviceId = want.parameters.remoteDeviceId onConnectRemoteService(remoteDeviceId) } } }); }
給"發(fā)送"按鈕添加點(diǎn)擊事件。
點(diǎn)擊"發(fā)送"按鈕,調(diào)用拉起彈窗函數(shù),彈窗中顯示可拉起的同局域網(wǎng)下的設(shè)備,代碼如下:Button('發(fā)送').width(70).fontSize(14).fontColor('#ffffff').backgroundColor('#fc4646') .onClick(() = > { RegisterDeviceListCallback(); this.dialogController.open(); })給內(nèi)容區(qū)域Textarea添加onChange事件。
內(nèi)容區(qū)域文字變化會(huì)調(diào)用onChange()方法,每一次的變化都會(huì)調(diào)用sendMessageToRemoteService()方法去同步另一個(gè)設(shè)備的數(shù)據(jù)。其中onChange()和sendMessageToRemoteService()方法代碼如下:TextArea({ placeholder: 'input your word', text: this.dataList[3]}).height('100%').width('100%') .onChange((value: string) = > { this.dataList[3] = value if(mRemote){ sendMessageToRemoteService(JSON.stringify(this.dataList)); } onDisconnectService(); })async function sendMessageToRemoteService(dataList) { if (mRemote == null) { prompt.showToast({ message: "mRemote is null" }); return; } let option = new rpc.MessageOption(); let data = new rpc.MessageParcel(); let reply = new rpc.MessageParcel(); data.writeStringArray(JSON.parse(dataList)); prompt.showToast({ message: "sendMessageToRemoteService" + dataList, duration: 3000 }); await mRemote.sendRequest(1, data, reply, option); let msg = reply.readInt(); }
拉起遠(yuǎn)端FA及連接遠(yuǎn)端Service服務(wù)
在本章節(jié)中,您將學(xué)會(huì)如何拉起在同一組網(wǎng)內(nèi)的設(shè)備上的FA,并且連接遠(yuǎn)端Service服務(wù)。
調(diào)用featureAbility.startAbility()方法,拉起遠(yuǎn)端FA,并同步界面UI。
點(diǎn)擊"分布式拉起"按鈕,調(diào)用RegisterDeviceListCallback()發(fā)現(xiàn)設(shè)備列表,并彈出設(shè)備列表選擇框CustomDialogExample,選擇設(shè)備后拉起遠(yuǎn)端FA。CustomDialogExample()代碼如下:// 設(shè)備列表彈出框 @CustomDialog struct CustomDialogExample { @State editFlag: boolean = false @Consume imageIndexForPosition : number[] @Consume pictureList: string[] controller: CustomDialogController cancel: () = > void confirm: () = > void build() { Column() { List({ space: 10, initialIndex: 0 }) { ForEach(DeviceIdList, (item) = > { ListItem() { Row() { Text(item) .width('87%').height(50).fontSize(10) .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF) .onClick(() = > { onStartRemoteAbility(item,this.imageIndexForPosition,this.pictureList); this.controller.close(); }) Radio({value:item}) .onChange((isChecked) = > { onStartRemoteAbility(item,this.imageIndexForPosition,this.pictureList); this.controller.close(); }).checked(false) } }.editable(this.editFlag) }, item = > item) } }.width('100%').height(200).backgroundColor(0xDCDCDC).padding({ top: 5 }) } }點(diǎn)擊Text組件或者Radio組件都會(huì)調(diào)用onStartRemoteAbility()方法拉起遠(yuǎn)端FA,onStartRemoteAbility()代碼如下:
function onStartRemoteAbility(deviceId,imageIndexForPosition,pictureList: string[]) { AuthDevice(deviceId); let numDevices = remoteDeviceModel.deviceList.length; if (numDevices === 0) { prompt.showToast({ message: "onStartRemoteAbility no device found" }); return; } var params = { imageIndexForPosition: JSON.stringify(imageIndexForPosition), pictureList : JSON.stringify(pictureList), remoteDeviceId : localDeviceId } var wantValue = { bundleName: 'com.huawei.cookbook', abilityName: 'com.example.openharmonypicturegame.MainAbility', deviceId: deviceId, parameters: params }; featureAbility.startAbility({ want: wantValue }).then((data) = > { // 拉起遠(yuǎn)端后,連接遠(yuǎn)端service onConnectRemoteService(deviceId) }); }調(diào)用featureAbility.connectAbility方法,連接遠(yuǎn)端Service服務(wù),連接成功后返回remote對(duì)象。
在featureAbility.startAbility()成功的回調(diào)中調(diào)用onConnectRemoteService()方法,onConnectRemoteService()方法代碼如下:// 連接遠(yuǎn)端Service async function onConnectRemoteService(deviceId) { // 連接成功的回調(diào) async function onConnectCallback(element, remote) { mRemote = remote; } // Service異常死亡的回調(diào) function onDisconnectCallback(element) { } // 連接失敗的回調(diào) function onFailedCallback(code) { prompt.showToast({ message: "onConnectRemoteService onFailed: " + code }); } let numDevices = remoteDeviceModel.deviceList.length; if (numDevices === 0) { prompt.showToast({ message: "onConnectRemoteService no device found" }); return; } connectedAbility = await featureAbility.connectAbility( { deviceId: deviceId, bundleName: "com.huawei.cookbook", abilityName: "com.example.openharmonypicturegame.ServiceAbility", }, { onConnect: onConnectCallback, onDisconnect: onDisconnectCallback, onFailed: onFailedCallback, }, ); }在配置文件config.json需要設(shè)置ServiceAbility的屬性visible為true,代碼如下:
"abilities": [ ... { "visible": true, "srcPath": "ServiceAbility", "name": ".ServiceAbility", "icon": "$media:icon", "srcLanguage": "ets", "description": "$string:description_serviceability", "type": "service" } ],同時(shí),Service側(cè)也需要在onConnect()時(shí)返回IRemoteObject,從而定義與Service進(jìn)行通信的接口。onConnect()需要返回一個(gè)IRemoteObject對(duì)象,OpenHarmony提供了IRemoteObject的默認(rèn)實(shí)現(xiàn),通過繼承rpc.RemoteObject來創(chuàng)建自定義的實(shí)現(xiàn)類。
Service側(cè)把自身的實(shí)例返回給調(diào)用側(cè)的代碼如下:
import rpc from "@ohos.rpc"; import commonEvent from '@ohos.commonEvent'; class FirstServiceAbilityStub extends rpc.RemoteObject{ constructor(des) { if (typeof des === 'string') { super(des); } else { return null; } } onRemoteRequest(code, data, reply, option) { if (code === 1) { let arr = data.readIntArray(); reply.writeInt(100); // 發(fā)布公共事件相關(guān)流程 ... } else { } return true; } } export default { // 創(chuàng)建Service的時(shí)候調(diào)用,用于Service的初始化 onStart() { }, // 在Service銷毀時(shí)調(diào)用。Service應(yīng)通過實(shí)現(xiàn)此方法來清理任何資源,如關(guān)閉線程、注冊(cè)的偵聽器等。 onStop() { }, // 在Ability和Service連接時(shí)調(diào)用,該方法返回IRemoteObject對(duì)象,開發(fā)者可以在該回調(diào)函數(shù)中生成對(duì)應(yīng)Service的IPC通信通道 onConnect(want) { try { let value = JSON.stringify(want); } catch(error) { } return new FirstServiceAbilityStub("[pictureGame] first ts service stub"); }, // 在Ability與綁定的Service斷開連接時(shí)調(diào)用 onDisconnect(want) { let value = JSON.stringify(want); }, // 在Service創(chuàng)建完成之后調(diào)用,該方法在客戶端每次啟動(dòng)該Service時(shí)都會(huì)調(diào)用 onCommand(want, startId) { let value = JSON.stringify(want); } };
RPC跨設(shè)備通訊
在本章節(jié)中,您將學(xué)會(huì)在成功連接遠(yuǎn)端Service服務(wù)的前提下,如何利用RPC進(jìn)行跨設(shè)備通訊。
- 成功連接遠(yuǎn)端Service服務(wù)的前提下,在正文部分增刪文字,都會(huì)完成一次跨設(shè)備通訊,假如在設(shè)備A端輸入文字,消息的傳遞是由設(shè)備A端的FA傳遞到設(shè)備B的Service服務(wù),發(fā)送消息的方法sendMessageToRemoteService()代碼如下:
// 連接成功后發(fā)送消息 async function sendMessageToRemoteService(imageIndexForPosition) { if (mRemote == null) { prompt.showToast({ message: "mRemote is null" }); return; } let option = new rpc.MessageOption(); let data = new rpc.MessageParcel(); let reply = new rpc.MessageParcel(); data.writeIntArray(JSON.parse(imageIndexForPosition)); await mRemote.sendRequest(1, data, reply, option); let msg = reply.readInt(); } - 在B端的Service接收消息,當(dāng)A端成功連接B端Service服務(wù)后,在A端會(huì)返回一個(gè)remote對(duì)象,當(dāng)A端remote對(duì)象調(diào)用sendRequest()方法后,在B端的Service中的onRemoteRequest()方法中會(huì)接收到發(fā)送的消息,其中繼承rpc.RemoteObject的類和onRemoteRequest()方法代碼如下:
class FirstServiceAbilityStub extends rpc.RemoteObject{ constructor(des) { if (typeof des === 'string') { super(des); } else { return null; } } onRemoteRequest(code, data, reply, option) { if (code === 1) { // 從data中接收數(shù)據(jù) let arr = data.readIntArray(); // 回復(fù)接收成功標(biāo)識(shí) reply.writeInt(100); // 發(fā)布公共事件相關(guān)流程 ... } else { } return true; } }
FA訂閱公共事件
在九宮格組件PictureGrid的生命周期函數(shù)aboutToAppear()中,調(diào)用訂閱公共事件方法subscribeEvent(),用來訂閱"publish_moveImage"公共事件,subscribeEvent()代碼如下:
subscribeEvent(){
let self = this;
// 用于保存創(chuàng)建成功的訂閱者對(duì)象,后續(xù)使用其完成訂閱及退訂的動(dòng)作
var subscriber;
// 訂閱者信息
var subscribeInfo = {
events: ["publish_moveImage"],
priority: 100
};
// 設(shè)置有序公共事件的結(jié)果代碼回調(diào)
function SetCodeCallBack(err) {
}
// 設(shè)置有序公共事件的結(jié)果數(shù)據(jù)回調(diào)
function SetDataCallBack(err) {
}
// 完成本次有序公共事件處理回調(diào)
function FinishCommonEventCallBack(err) {
}
// 訂閱公共事件回調(diào)
function SubscribeCallBack(err, data) {
let msgData = data.data;
let code = data.code;
// 設(shè)置有序公共事件的結(jié)果代碼
subscriber.setCode(code, SetCodeCallBack);
// 設(shè)置有序公共事件的結(jié)果數(shù)據(jù)
subscriber.setData(msgData, SetDataCallBack);
// 完成本次有序公共事件處理
subscriber.finishCommonEvent(FinishCommonEventCallBack)
// 處理接收到的數(shù)據(jù)data
self.imageIndexForPosition = data.parameters.imageIndexForPosition;
self.pictureList = [];
self.imageIndexForPosition.forEach(value = > {
if (value == 9) {
self.pictureList.push("--")
} else {
self.pictureList.push(`picture_0` + value + `.png`)
}
});
self.onFinish();
}
// 創(chuàng)建訂閱者回調(diào)
function CreateSubscriberCallBack(err, data) {
subscriber = data;
// 訂閱公共事件
commonEvent.subscribe(subscriber, SubscribeCallBack);
}
// 創(chuàng)建訂閱者
commonEvent.createSubscriber(subscribeInfo, CreateSubscriberCallBack);
}
在FA中訂閱到Service服務(wù)發(fā)布的"publish_moveImage"事件后,在SubscribeCallBack()回調(diào)中重新賦值imageIndexForPosition數(shù)組與pictureList數(shù)組,從而同步更新界面UI。
service發(fā)布公共事件
當(dāng)Service服務(wù)接收到消息后,在onRemoteRequest()發(fā)布公共事件,代碼如下:
onRemoteRequest(code, data, reply, option) {
if (code === 1) {
// 從data中接收數(shù)據(jù)
let arr = data.readIntArray();
// 回復(fù)接收成功標(biāo)識(shí)
reply.writeInt(100);
// 公共事件相關(guān)信息
var params ={
imageIndexForPosition: arr
}
var options = {
// 公共事件的初始代碼
code: 1,
// 公共事件的初始數(shù)據(jù)
data: 'init data',、
// 有序公共事件
isOrdered: true,
bundleName: 'com.huawei.cookbook',
parameters: params
}
// 發(fā)布公共事件回調(diào)
function PublishCallBack() {
}
// 發(fā)布公共事件
commonEvent.publish("publish_moveImage", options, PublishCallBack);
} else {
}
return true;
}
在接收到消息后,把接收到的圖片位置數(shù)組放入params中,然后發(fā)布名稱為"publish_moveImage"的有序公共事件。
審核編輯 黃宇
-
分布式
+關(guān)注
關(guān)注
1文章
1061瀏覽量
76395 -
鴻蒙
+關(guān)注
關(guān)注
60文章
2839瀏覽量
45334 -
HarmonyOS
+關(guān)注
關(guān)注
80文章
2146瀏覽量
35511 -
OpenHarmony
+關(guān)注
關(guān)注
31文章
3920瀏覽量
20676
發(fā)布評(píng)論請(qǐng)先 登錄
HarmonyOS開發(fā)實(shí)例:【分布式數(shù)據(jù)管理】
HarmonyOS應(yīng)用開發(fā)-分布式任務(wù)調(diào)度
HarmonyOS應(yīng)用開發(fā)-分布式設(shè)計(jì)
HarmonyOS分布式數(shù)據(jù)庫,為啥這么牛?
HarmonyOS教程—基于跨設(shè)備遷移和分布式文件能力,實(shí)現(xiàn)郵件的跨設(shè)備編輯和附件的調(diào)用
HarmonyOS分布式應(yīng)用框架深入解讀
HDC2021技術(shù)分論壇:如何高效完成HarmonyOS分布式應(yīng)用測(cè)試?
如何高效完成HarmonyOS分布式應(yīng)用測(cè)試?
通過HarmonyOS分布式能力實(shí)現(xiàn)任務(wù)的跨設(shè)備遷移設(shè)計(jì)資料分享
HarmonyOS應(yīng)用開發(fā)-EducationSystem分布式親子早教系統(tǒng)體驗(yàn)
HarmonyOS應(yīng)用開發(fā)-分布式語音攝像頭體驗(yàn)
HarmonyOS測(cè)試技術(shù)與實(shí)戰(zhàn)-HarmonyOS分布式應(yīng)用特征與挑戰(zhàn)
HarmonyOS測(cè)試技術(shù)與實(shí)戰(zhàn)-分布式應(yīng)用測(cè)試解決方案
HarmonyOS分布式應(yīng)用上架問題分析

HarmonyOS開發(fā)實(shí)例:【分布式郵件】

評(píng)論