赞
踩
本篇Codelab是基于TS扩展的声明式开发范式编程语言编写的一个分布式邮件系统,可以由一台设备拉起另一台设备,每次改动邮件内容,都会同步更新两台设备的信息。效果图如下:
说明: 本示例涉及使用系统接口,需要手动替换Full SDK才能编译通过。
完成本篇Codelab我们首先要完成开发环境的搭建,本示例以Hi3516DV300开发板为例,参照以下步骤进行:
以3.0版本为例:
2.搭建烧录环境。
3.搭建开发环境。
本章节以系统自带的音乐播放器为例,介绍如何完成两台设备的分布式组网。
本篇Codelab只对核心代码进行讲解,首先来介绍下整个工程的代码结构:
在本章节中,您将学会如何制作一个简单的邮件界面。
- @Entry
- @Component
- struct Index {
- private imageList: any[]= []
- @Provide dataList: string[]= ['xiaohua@128.com','xiaoming@128.com','假期温馨提示','2022年新春佳节即将来临,请同学们细读节前相关温馨提示,保持办公场所环境整洁,假期期间注意信息及个人安全,预祝全体同学新春快乐,虎虎生威!']
-
- 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('发送').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('发件人').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 })
- }
- }
在入口组件的生命周期函数aboutToAppear()中调用订阅事件。如果Ability是被其他设备拉起的,在aboutToAppear()中调用featureAbility.getWant(),可通过want中的参数重新初始化dataList数组,入口组件的生命周期函数aboutToAppear()代码如下:
- async aboutToAppear() {
- this.subscribeEvent();
- let self = this;
- // 当被拉起时,通过want传递的参数同步对端界面UI
- await featureAbility.getWant((error, want) => {
- var status = want.parameters;
- if (want.parameters.dataList) {
- self.dataList = JSON.parse(status.dataList)
- // 远端被拉起后,连接对端的service
- if (want.parameters.remoteDeviceId) {
- let remoteDeviceId = want.parameters.remoteDeviceId
- onConnectRemoteService(remoteDeviceId)
- }
- }
- });
- }
2.给"发送"按钮添加点击事件。
点击"发送"按钮,调用拉起弹窗函数,弹窗中显示可拉起的同局域网下的设备,代码如下:
- Button('发送').width(70).fontSize(14).fontColor('#ffffff').backgroundColor('#fc4646')
- .onClick(() => {
- RegisterDeviceListCallback();
- this.dialogController.open();
- })
3.给内容区域Textarea添加onChange事件。
内容区域文字变化会调用onChange()方法,每一次的变化都会调用sendMessageToRemoteService()方法去同步另一个设备的数据。其中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();
-
- }
在本章节中,您将学会如何拉起在同一组网内的设备上的FA,并且连接远端Service服务。
- // 设备列表弹出框
- @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 })
- }
- }
点击Text组件或者Radio组件都会调用onStartRemoteAbility()方法拉起远端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) => {
- // 拉起远端后,连接远端service
- onConnectRemoteService(deviceId)
- });
- }
2.调用featureAbility.connectAbility方法,连接远端Service服务,连接成功后返回remote对象。
在featureAbility.startAbility()成功的回调中调用onConnectRemoteService()方法,onConnectRemoteService()方法代码如下:
- // 连接远端Service
- async function onConnectRemoteService(deviceId) {
- // 连接成功的回调
- async function onConnectCallback(element, remote) {
- mRemote = remote;
- }
- // Service异常死亡的回调
- function onDisconnectCallback(element) {
- }
- // 连接失败的回调
- 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需要设置ServiceAbility的属性visible为true,代码如下:
- "abilities": [
- ...
- {
- "visible": true,
- "srcPath": "ServiceAbility",
- "name": ".ServiceAbility",
- "icon": "$media:icon",
- "srcLanguage": "ets",
- "description": "$string:description_serviceability",
- "type": "service"
- }
- ],
同时,Service侧也需要在onConnect()时返回IRemoteObject,从而定义与Service进行通信的接口。onConnect()需要返回一个IRemoteObject对象,OpenHarmony提供了IRemoteObject的默认实现,通过继承rpc.RemoteObject来创建自定义的实现类。
Service侧把自身的实例返回给调用侧的代码如下:
- 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);
- // 发布公共事件相关流程
- ...
-
-
- } else {
- }
- return true;
- }
- }
-
- export default {
- // 创建Service的时候调用,用于Service的初始化
- onStart() {
- },
- // 在Service销毁时调用。Service应通过实现此方法来清理任何资源,如关闭线程、注册的侦听器等。
- onStop() {
- },
- // 在Ability和Service连接时调用,该方法返回IRemoteObject对象,开发者可以在该回调函数中生成对应Service的IPC通信通道
- onConnect(want) {
- try {
- let value = JSON.stringify(want);
- } catch(error) {
- }
- return new FirstServiceAbilityStub("[pictureGame] first ts service stub");
- },
- // 在Ability与绑定的Service断开连接时调用
- onDisconnect(want) {
- let value = JSON.stringify(want);
- },
- // 在Service创建完成之后调用,该方法在客户端每次启动该Service时都会调用
- onCommand(want, startId) {
- let value = JSON.stringify(want);
- }
- };
在本章节中,您将学会在成功连接远端Service服务的前提下,如何利用RPC进行跨设备通讯。
- // 连接成功后发送消息
- 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();
- }
2.在B端的Service接收消息,当A端成功连接B端Service服务后,在A端会返回一个remote对象,当A端remote对象调用sendRequest()方法后,在B端的Service中的onRemoteRequest()方法中会接收到发送的消息,其中继承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中接收数据
- let arr = data.readIntArray();
- // 回复接收成功标识
- reply.writeInt(100);
- // 发布公共事件相关流程
- ...
-
- } else {
- }
- return true;
- }
- }
在本章节中,您将学会如何通过CommonEvent订阅公共事件,详细信息请参考CommonEvent开发指南。在九宫格组件PictureGrid的生命周期函数aboutToAppear()中,调用订阅公共事件方法subscribeEvent(),用来订阅"publish_moveImage"公共事件,subscribeEvent()代码如下:
- subscribeEvent(){
- let self = this;
- // 用于保存创建成功的订阅者对象,后续使用其完成订阅及退订的动作
- var subscriber;
- // 订阅者信息
- var subscribeInfo = {
- events: ["publish_moveImage"],
- priority: 100
-
- };
-
- // 设置有序公共事件的结果代码回调
- function SetCodeCallBack(err) {
- }
- // 设置有序公共事件的结果数据回调
- function SetDataCallBack(err) {
- }
- // 完成本次有序公共事件处理回调
- function FinishCommonEventCallBack(err) {
- }
- // 订阅公共事件回调
- function SubscribeCallBack(err, data) {
- let msgData = data.data;
- let code = data.code;
- // 设置有序公共事件的结果代码
- subscriber.setCode(code, SetCodeCallBack);
- // 设置有序公共事件的结果数据
- subscriber.setData(msgData, SetDataCallBack);
- // 完成本次有序公共事件处理
- subscriber.finishCommonEvent(FinishCommonEventCallBack)
- // 处理接收到的数据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();
- }
-
- // 创建订阅者回调
- function CreateSubscriberCallBack(err, data) {
- subscriber = data;
- // 订阅公共事件
- commonEvent.subscribe(subscriber, SubscribeCallBack);
- }
-
- // 创建订阅者
- commonEvent.createSubscriber(subscribeInfo, CreateSubscriberCallBack);
- }
在FA中订阅到Service服务发布的"publish_moveImage"事件后,在SubscribeCallBack()回调中重新赋值imageIndexForPosition数组与pictureList数组,从而同步更新界面UI。
在本章节中,您将学会如何通过CommonEvent发布公共事件,详细信息请参考CommonEvent开发指南。
当Service服务接收到消息后,在onRemoteRequest()发布公共事件,代码如下:
- onRemoteRequest(code, data, reply, option) {
- if (code === 1) {
- // 从data中接收数据
- let arr = data.readIntArray();
- // 回复接收成功标识
- reply.writeInt(100);
- // 公共事件相关信息
- var params ={
- imageIndexForPosition: arr
- }
- var options = {
- // 公共事件的初始代码
- code: 1,
- // 公共事件的初始数据
- data: 'init data',、
- // 有序公共事件
- isOrdered: true,
- bundleName: 'com.huawei.cookbook',
- parameters: params
-
- }
- // 发布公共事件回调
- function PublishCallBack() {
- }
- // 发布公共事件
- commonEvent.publish("publish_moveImage", options, PublishCallBack);
-
- } else {
- }
- return true;
- }
在接收到消息后,把接收到的图片位置数组放入params中,然后发布名称为"publish_moveImage"的有序公共事件。
为了帮助大家更深入有效的学习到鸿蒙开发知识点,小编特意给大家准备了一份全套最新版的HarmonyOS NEXT学习资源,获取完整版方式请点击→《HarmonyOS教学视频》
获取白皮书完整版方式请点击→《鸿蒙生态应用开发白皮书V2.0PDF》
一、入门必看
二、HarmonyOS 概念
更多了解更多鸿蒙开发的相关知识可以参考:《鸿蒙 (Harmony OS)开发学习手册》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。