一、引言

在 Harmony OS NEXT / 5.0 / API 12 + 版本的生态下,开发一款简易聊天应用是探索鸿蒙应用开发的有趣实践。本文将详细介绍该聊天应用的功能实现、源码解析以及问题优化策略,帮助开发者快速上手并深入理解相关技术要点。

二、适用版本

本文所涉及的简易聊天功能适用于 Harmony OS NEXT / 5.0 / API 12 + 版本。这些版本为开发者提供了丰富且稳定的 API,使我们能够轻松实现并优化聊天应用所需的网络通信、界面展示等关键功能。

三、效果预览

                                                

四、基础代码结构剖析

  1. 核心组件 ChatApp:作为整个聊天应用的 “中枢神经”,ChatApp 组件统筹管理着聊天过程中的各类状态与逻辑。
    • 状态变量
      • messages 数组:犹如一个 “聊天记录宝库”,它将所有的聊天消息妥善存储,为用户提供随时回顾聊天内容的可能。
      • inputText:恰似输入框的 “贴身记录员”,实时跟踪并记录用户在输入框中输入的每一个字符。
      • isConnected:可看作一个 “连接状态信号灯”,以布尔值的形式直观反映当前应用与服务器的连接状态。
      • ws:作为 WebSocket 对象,它是应用与服务器之间进行数据交互的 “高速通道”,初始值为 null,等待被激活以建立通信。
    • 方法
      • connect 方法:此方法如同一位 “通信搭建师”,负责创建 WebSocket 实例并尝试连接到指定的服务器 wss://toolin.cn/echo。同时,它还为这条 “通道” 配备了四位 “守护使者”,即 open、message、close、error 事件的监听。当连接成功时,就像通道搭建完成,“信号灯” 变绿(设置 isConnected 为 true);接收到消息时,迅速将消息 “存入宝库”,添加到 messages 数组;连接关闭时,“信号灯” 变红(设置 isConnected 为 false);若发生错误,便在控制台这个 “公告板” 上打印错误信息。
      • sendMessage 方法:当输入框中有内容且 “高速通道” 已建立(已连接)时,该方法化身为 “消息快递员”,通过 ws 这个 “通道” 将消息发送出去,然后把消息添加到 messages 数组这个 “宝库” 中,并及时清空输入框,准备迎接下一条消息的到来。

五、核心功能详解

  1. 连接状态管理
    • 状态显示:利用 Text 组件作为 “状态播报员”,在线时大声宣告 “🟢 在线”,并且文字颜色呈现为绿色,醒目地向用户传达当前在线状态;离线时则播报 “🔴 离线”,文字变为红色,提醒用户连接已中断。
    • 按钮交互Button 组件宛如一个 “智能切换开关”,依据当前连接状态显示不同的文字。若当前在线,它显示 “断开连接”,用户点击即如同按下 “关闭按钮”,执行断开连接操作;若当前离线,它显示 “点击连接”,用户点击则像按下 “开启按钮”,尝试建立连接。
  2. 消息收发演示
    • 输入框TextInput 组件仿佛一位 “忠实的书记员”,实时捕捉用户输入的内容,并及时同步更新 inputText 这个 “记录员” 的信息,确保输入内容无一遗漏。
    • 发送按钮:另一个 Button 组件扮演着 “快递发送员” 的角色,当用户点击它时,迅速调用 sendMessage 方法,将输入框中的 “包裹”(消息)通过 “高速通道” 发送出去。
    • 消息展示List 组件就像一个精心布置的 “展示画廊”,它遍历 messages 这个 “聊天记录宝库”,以气泡样式将每条消息精美地展示出来。并且,根据消息是发送方还是接收方,为气泡披上不同颜色的 “外衣”,发送方气泡背景色为 #e3f2fd,接收方气泡背景色为 #ffffff,让用户能够轻松区分不同来源的消息。
  3. 自动重连机制(心跳机制)
    • reconnect 方法:此方法犹如一位 “执着的网络修复工”,借助 setTimeout 这个 “定时器”,在连接断开 3 秒后,准时尝试重新搭建 “高速通道”,努力恢复应用与服务器的连接。
    • 自动重连实现:在 wsclose 事件监听中,不仅将 isConnected 这个 “信号灯” 熄灭(设置为 false),还立即调用 reconnect 方法,让 “网络修复工” 马上投入工作,实现自动重连,确保应用在连接断开时能尽快恢复通信。

六、源码详情解读

// 从 @ohos.net.webSocket 导入 webSocket 模块,用于 WebSocket 通信
import webSocket from '@ohos.net.webSocket';
// 从 @kit.ArkUI 导入 promptAction,用于显示弹窗
import { promptAction } from '@kit.ArkUI';

// 定义 Message 类,表示一条聊天消息
class Message {
    // 消息类型,sent 表示发送,received 表示接收
    type: string;
    // 消息内容
    content: string;
    // 时间戳
    timestamp: number;

    constructor(type: string, content: string) {
        this.type = type;
        this.content = content;
        this.timestamp = new Date().getTime();
    }
}

// 定义 WebSocketDemo 组件
@Component
export struct WebSocketDemo {
    // 连接状态
    @State isConnected: boolean = false;
    // 输入框内容
    @State inputMessage: string = '';
    // 聊天记录数组
    @State messages: Message[] = [];
    // WebSocket 对象
    private ws: webSocket.WebSocket | null = null;

    // 初始化 WebSocket 连接
    initWebSocket() {
        const socketUrl = 'wss://toolin.cn/echo';
        this.ws = webSocket.create(socketUrl);

        if (this.ws) {
            // 连接成功事件
            this.ws.on('open', () => {
                this.isConnected = true;
            });

            // 接收消息事件
            this.ws.on('message', (data) => {
                const message = new Message('received', data);
                this.addMessage(message);
            });

            // 连接关闭事件
            this.ws.on('close', () => {
                this.isConnected = false;
                this.reconnect();
            });

            // 错误事件
            this.ws.on('error', (error) => {
                console.error('WebSocket error:', error);
                this.showAlert('连接错误', 'WebSocket 连接出现错误,请检查网络');
            });

            // 发起连接
            this.ws.connect();
        }
    }

    // 添加消息到聊天记录
    addMessage(message: Message) {
        this.messages.push(message);
    }

    // 显示弹窗
    showAlert(title: string, content: string) {
        promptAction.showToast({
            message: content,
            duration: 3000
        });
    }

    // 重新连接
    reconnect() {
        setTimeout(() => {
            this.initWebSocket();
        }, 3000);
    }

    // 发送消息
    sendMessage() {
        if (this.isConnected && this.inputMessage.trim()!== '') {
            const message = new Message('sent', this.inputMessage);
            this.addMessage(message);
            if (this.ws) {
                this.ws.send(this.inputMessage);
            }
            this.inputMessage = '';
        }
    }

    // 构建组件界面
    build() {
        Column() {
            // 显示连接状态
            Text(this.isConnected? '🟢 在线' : '🔴 离线')
               .fontSize(16)
               .fontWeight(500)
               .fontColor(this.isConnected? '#00ff00' : '#ff0000');

            Row() {
                // 连接/断开连接按钮
                Button(this.isConnected? '断开连接' : '点击连接')
                   .width(120)
                   .height(40)
                   .onClick(() => {
                        if (this.isConnected) {
                            if (this.ws) {
                                this.ws.close();
                            }
                        } else {
                            this.initWebSocket();
                        }
                    });

                // 清空记录按钮
                Button('清空记录')
                   .width(120)
                   .height(40)
                   .onClick(() => {
                        this.messages = [];
                    });
            }
           .padding({ top: 20 });

            Row() {
                // 消息输入框
                TextInput({ placeholder: '请输入消息', text: $this.inputMessage })
                   .width(250)
                   .height(40)
                   .padding({ left: 10, right: 10 });

                // 发送按钮
                Button('发送')
                   .width(80)
                   .height(40)
                   .onClick(() => {
                        this.sendMessage();
                    });
            }
           .padding({ top: 20 });

            // 消息列表
            List(this.messages) {
                Item({ index: 0 }) {
                    Row() {
                        if (this.item.type ==='sent') {
                            Column() {
                                Text(this.item.content)
                                   .backgroundColor('#e3f2fd')
                                   .padding(10)
                                   .cornerRadius(10);
                                Text(new Date(this.item.timestamp).toLocaleTimeString())
                                   .fontSize(12)
                                   .alignSelf(TextAlign.End);
                            }
                           .layoutWeight(1);
                        } else {
                            Column() {
                                Text(this.item.content)
                                   .backgroundColor('#ffffff')
                                   .padding(10)
                                   .cornerRadius(10);
                                Text(new Date(this.item.timestamp).toLocaleTimeString())
                                   .fontSize(12)
                                   .alignSelf(TextAlign.Start);
                            }
                           .layoutWeight(1);
                        }
                    }
                }
            }
           .width('100%')
           .layoutWeight(1);
        }
       .width('100%')
       .height('100%')
       .padding(20);
    }
}

上述源码完整地呈现了鸿蒙简易聊天应用的实现。从导入必要的模块,到定义消息类和组件,再到各个功能方法的实现以及界面的构建,每一部分都紧密协作。WebSocketDemo 组件通过管理各种状态变量和方法,实现了连接状态管理、消息收发以及自动重连等核心功能。在界面构建部分,通过合理运用 ColumnRowTextButtonTextInputList 等组件,打造出一个简洁且功能完备的聊天界面。

七、问题优化策略

1、权限配置问题

  • 存在原因:网络请求在鸿蒙系统中需要相应的权限支持,如同车辆行驶需要道路许可。当应用缺少网络权限时,网络请求就会失败,并在控制台报错 PERMISSION_DENIED
  • 解决方法:在 module.json5 配置文件中添加网络权限配置,为网络请求授予许可。配置如下
"requestPermissions": [  
    {  
        "name": "ohos.permission.INTERNET",  
    }  
]  

2、消息安全性风险

  • 过滤方法:通过 sanitizeMessage 方法对消息内容进行过滤,以防范潜在的安全风险。该方法类似于一个 “消息安检员”,将消息中的 <> 替换为实体字符,有效防止跨站脚本攻击(XSS)。同时,限制消息长度为 500 字符,避免过长消息可能引发的性能问题或其他异常。
// 消息内容过滤  
private sanitizeMessage(content: string): string {  
    return content  
      .replace(/</g, '&lt;')  
      .replace(/>/g, '&gt;')  
      .substring(0, 500); // 限制消息长度  
}  

在发送消息前,调用此方法对消息内容进行处理,确保消息的安全性。例如,在 sendMessage 方法中修改为:

sendMessage() {
    if (this.isConnected && this.inputMessage.trim()!== '') {
        const sanitizedMessage = this.sanitizeMessage(this.inputMessage);
        const message = new Message('sent', sanitizedMessage);
        this.addMessage(message);
        if (this.ws) {
            this.ws.send(sanitizedMessage);
        }
        this.inputMessage = '';
    }
}

3、防止频繁发送

  • 控制逻辑:定义 lastSendTime 记录上次发送消息的时间,以此作为频率控制的依据。在 sendMessage 方法中,每次发送消息时检查当前时间与 lastSendTime 的间隔。若间隔小于 1000 毫秒(1 秒),则提示用户 “发送太频繁啦!” 并阻止发送;否则,执行正常发送逻辑,并更新 lastSendTime
private lastSendTime: number = 0;

sendMessage() {
    if (Date.now() - this.lastSendTime < 1000) {
        promptAction.showToast({ message: '发送太频繁啦!' });
        return;
    }
    if (this.isConnected && this.inputMessage.trim()!== '') {
        const sanitizedMessage = this.sanitizeMessage(this.inputMessage);
        const message = new Message('sent', sanitizedMessage);
        this.addMessage(message);
        if (this.ws) {
            this.ws.send(sanitizedMessage);
        }
        this.inputMessage = '';
        this.lastSendTime = Date.now();
    }
}

八、总结

通过这个简化版的鸿蒙聊天应用,开发者能够深入了解在 Harmony OS NEXT / 5.0 / API 12 + 版本下开发聊天应用的关键技术和流程。在实际开发中,可以此为基础,逐步拓展功能,如增加用户认证提升安全性、实现群聊功能丰富交互场景、支持多媒体消息增强用户体验等。同时,务必重视应用的安全性和稳定性,合理处理权限配置、消息安全等问题,为用户打造可靠、优质的应用。关于鸿蒙应用开发中聊天功能的更多进阶技巧和优化思路,我会在后续博客中持续分享,感兴趣的话欢迎关注。对于这个聊天应用的实现和优化,你若有任何疑问或想法,欢迎随时交流。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐