如何使用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天
手把手教你用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天
目录
文章目录
- 手把手教你用**Java**语言在**Idea**和**Android**中分别建立**服务端**和**客户端**实现局域网聊天
- **目录**
- @[toc]
- **基本实现**
- **问题分析**
- **服务端**
- Idea:
- 结构预览
- Server类
- 代码解读
- ServerReader类
- 代码解读
- **客户端**
- Android:
- 结构预览
- 布局文件 activity_main.xml
- 代码解读
- MainActivity
- 代码解读
- 配置网络
- 配置网络
文章目录
- 手把手教你用**Java**语言在**Idea**和**Android**中分别建立**服务端**和**客户端**实现局域网聊天
- **目录**
- @[toc]
- **基本实现**
- **问题分析**
- **服务端**
- Idea:
- 结构预览
- Server类
- 代码解读
- ServerReader类
- 代码解读
- **客户端**
- Android:
- 结构预览
- 布局文件 activity_main.xml
- 代码解读
- MainActivity
- 代码解读
- 配置网络
- 配置网络
基本实现
-
实现客户端和服务端之间的通信
-
实现服务端转接客户端消息,并发送给其他局域网在线成员
-
实现服务端接收客户端消息,并判断相应类型,做出对应应答
-
实现客户端消息发送者 发送时间 当前在线用户基本可视化
问题分析
-
服务端开发
- 在IntelliJ IDEA中创建一个Java项目。
- 实现一个简单的TCP服务器,能够接收客户端消息并回显(或广播)消息给所有已连接的客户端。
-
客户端开发
- 在Android Studio中创建一个Android项目。
- 实现一个简单的TCP客户端,能够发送消息到服务端并显示从服务端接收到的消息。
-
网络通信
- 确保服务端和客户端在同一局域网内,并且客户端可以正确连接到服务端。
- 处理多线程问题,确保服务端可以同时处理多个客户端连接。
服务端
Idea:
结构预览
在Idea中创建一个名为Server的类
Server类
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;public class Server {// 定义一个集合容器存储所有登陆进来的客户端,以便群发消息给他们// 定义一个Map集合,键是存储客户端的管道,值是这个管道的名称public static final Map<Socket, String> onLineSockets = new HashMap<>();public static void main(String[] args) throws Exception {System.out.println("服务端启动");// 1. 创建服务端ServerSocket对象,绑定端口号,监听客户端连接ServerSocket serverSocket = new ServerSocket(9999);while (true) {System.out.println("等待客户端连接....");Socket socket = serverSocket.accept();new ServerReader(socket).start();System.out.println("一个客户端上线了.... IP:" + socket.getInetAddress().getHostAddress());}}
}
代码解读
- 定义一个Map集合(所有局域网用户共享集合)存储所有登陆进来的客户端,以便群发消息给他们,键是存储客户端的管道,值是这个管道的名称(说白了就是前一个是主键,后一个)
public static final Map<Socket, String> onLineSockets = new HashMap<>();
- 创建服务端ServerSocket对象,绑定端口号,监听客户端连接
ServerSocket serverSocket = new ServerSocket(9999);
- 端口号选择建议范围**(1024~65535)**,其中绝大多数没有被使用
while (true) {System.out.println("等待客户端连接....");Socket socket = serverSocket.accept();new ServerReader(socket).start();System.out.println("一个客户端上线了.... IP:" + socket.getInetAddress().getHostAddress());}
-
使用无限循环,持续监听新的连接
-
**serverSocket.accept()**会堵塞线程,等待连接请求,直到收到一个新的请求,并与客户端建立新的通信管道用来传输数据
-
**new ServerReader(socket).start()**在建立新的管道后,会建立一个新线程用来与管道对应的客户端通信,这样就能实现多客户端之间通信
-
**socket.getInetAddress().getHostAddress()**用于获取新建连接的客户端Ip,并在Server类终端打印,便于服务端查看连接信息
在Idea中创建一个名为ServerReader的类
注:本文所有读取和发送都使用特殊流(DataInputStream与DataOutputStream)
ServerReader类
import java.io.*;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;public class ServerReader extends Thread {private Socket socket;public ServerReader(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {DataInputStream dis = new DataInputStream(socket.getInputStream());while (true) {int type = dis.readInt(); // 1 2switch (type) {case 1:String nickname = dis.readUTF();// 登陆成功,将客户端socket存入在线集合Server.onLineSockets.put(socket, nickname);// 更新全部客户端的在线人数列表updateClientOnLineUserList();break;case 2:String msg = dis.readUTF();sendMsgToAll(msg);break;default:System.out.println("未知的消息类型: " + type);break;}}} catch (Exception e) {System.out.println("客户端断开连接 IP:" + socket.getInetAddress().getHostAddress() + " 时间: " + LocalDateTime.now());Server.onLineSockets.remove(socket); // 把下线的客户端socket从在线集合中移除updateClientOnLineUserList();}}// 给全部在线socket推送当前客户端发来的消息private void sendMsgToAll(String msg) {StringBuilder sb = new StringBuilder();String name = Server.onLineSockets.get(socket);// 获取当前时间LocalDateTime now = LocalDateTime.now();DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");String nowStr = dft.format(now);String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n").append(msg).append("\r\n").toString();// 推送给全部客户端for (Socket clientSocket : Server.onLineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeInt(2); // 1代表告诉客户端接下来是在线人数列表信息 2代表发的是群聊消息dos.writeUTF(msgResult);dos.flush();} catch (IOException e) {e.printStackTrace();}}}// 更新全部客户端的在线人数列表private void updateClientOnLineUserList() {// 拿到全部在线客户端的用户名称,把这些名称转发给全部在线socket管道Collection<String> onLineUsers = Server.onLineSockets.values();for (Socket clientSocket : Server.onLineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeInt(1); // 1代表告诉客户端接下来是在线人数列表信息 2代表发的是群聊消息dos.writeInt(onLineUsers.size());for (String onLineUser : onLineUsers) {dos.writeUTF(onLineUser);}dos.flush();} catch (IOException e) {e.printStackTrace();}}}
}
代码解读
- sendMsgToAll方法
// 给全部在线socket推送当前客户端发来的消息private void sendMsgToAll(String msg) {StringBuilder sb = new StringBuilder();String name = Server.onLineSockets.get(socket);// 获取当前时间LocalDateTime now = LocalDateTime.now();DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");String nowStr = dft.format(now);String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n").append(msg).append("\r\n").toString();// 推送给全部客户端for (Socket clientSocket : Server.onLineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeInt(2); // 1代表告诉客户端接下来是在线人数列表信息 2代表发的是群聊消息dos.writeUTF(msgResult);dos.flush();} catch (IOException e) {e.printStackTrace();}}}
- 从Map集合(onLineSockets)中,拿到当前客户端的用户名
String name = Server.onLineSockets.get(socket);
- 获取当前时间,自定义时间格式dft "yyyy-MM-dd HH:mm:ss EEE a"年 月 日 时 分 秒 星期 上下午
LocalDateTime now = LocalDateTime.now();DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");String nowStr = dft.format(now);
- 拼装消息 用户名+空格+时间+换行回车+消息+换行回车
String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n").append(msg).append("\r\n").toString();
-
将拼装完成的消息,推送给所有当前在线客户端
for循环遍历当前在线客户端
标注消息类型为群聊消息(2)
接着发送拼装完成的消息
刷新管道
for (Socket clientSocket : Server.onLineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeInt(2);dos.writeUTF(msgResult);dos.flush();} catch (IOException e) {e.printStackTrace();}}
-
updateClientOnLineUserList方法
// 更新全部客户端的在线人数列表private void updateClientOnLineUserList() {// 拿到全部在线客户端的用户名称,把这些名称转发给全部在线socket管道Collection<String> onLineUsers = Server.onLineSockets.values();for (Socket clientSocket : Server.onLineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeInt(1); // 1代表告诉客户端接下来是在线人数列表信息 2代表发的是群聊消息dos.writeInt(onLineUsers.size());for (String onLineUser : onLineUsers) {dos.writeUTF(onLineUser);}dos.flush();} catch (IOException e) {e.printStackTrace();}}}
- 拿到全部在线客户端的用户名称,把这些名称转发给全部在线socket管道
Collection<String> onLineUsers = Server.onLineSockets.values();
- 1代表消息类型 告诉客户端接下来是在线人数列表信息
dos.writeInt(1);
- 告诉客户端在线用户数量,客户端循环接收多少次
dos.writeInt(onLineUsers.size());
- 服务端循环发送
for (String onLineUser : onLineUsers) {dos.writeUTF(onLineUser);}
- 刷新管道
dos.flush();
- run方法
public void run() {try {DataInputStream dis = new DataInputStream(socket.getInputStream());while (true) {int type = dis.readInt(); // 1 2switch (type) {case 1:String nickname = dis.readUTF();// 登陆成功,将客户端socket存入在线集合Server.onLineSockets.put(socket, nickname);// 更新全部客户端的在线人数列表updateClientOnLineUserList();break;case 2:String msg = dis.readUTF();sendMsgToAll(msg);break;default:System.out.println("未知的消息类型: " + type);break;}}} catch (Exception e) {System.out.println("客户端断开连接 IP:" + socket.getInetAddress().getHostAddress() + " 时间: " + LocalDateTime.now());Server.onLineSockets.remove(socket); // 把下线的客户端socket从在线集合中移除updateClientOnLineUserList();}}
- 创建一个读取socket管道的对象dis
DataInputStream dis = new DataInputStream(socket.getInputStream());
-
while循环保证该方法一直处于接收消息的状态
-
先获取管道中先发送的数据类型
int type = dis.readInt();
-
switch (type)判断:
如果是1,则为用户名,String nickname = dis.readUTF()读取管道中后发送的内容(用户名),这个时候用户已经登录成功,用Server.onLineSockets.put(socket, nickname)将客户端socket存入在线集合onLineSockets(该集合在前面已经创建在Server类里了)
如果是2,则为群聊消息,String msg = dis.readUTF()读取管道中的后发送的群聊消息,接着调用sendMsgToAll()方法将消息广播给在线用户
如果使用的传输数据的方式不是特殊流,则打印出该消息在特殊流下的形式(可能是一堆乱码)
-
异常:当客户端断开连接后,系统会抛出一个异常,用Server.onLineSockets.remove(socket) 把下线的客户端socket从在线集合中移除,重新调用updateClientOnLineUserList()方法,刷新在线用户列表
catch (Exception e) {System.out.println("客户端断开连接 IP:" + socket.getInetAddress().getHostAddress() + " 时间: " + LocalDateTime.now());Server.onLineSockets.remove(socket);updateClientOnLineUserList();}
客户端
Android:
创建一个空项目Client
因为安卓客户端只有一个Activity和一个布局文件,所以项目构建完成后就不需要再创建其他类和活动了
结构预览
布局文件 activity_main.xml
预览
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="16dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="聊天室"android:textSize="24sp"android:textStyle="bold"android:gravity="center"android:layout_gravity="center_horizontal"android:layout_marginBottom="16dp" /><EditTextandroid:id="@+id/input_field"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="Type a message..."android:inputType="textMultiLine"android:minLines="3"android:maxLines="5" /><Buttonandroid:id="@+id/send_button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发送"android:layout_gravity="end"android:layout_marginTop="8dp" /><ListViewandroid:id="@+id/message_list_view"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:dividerHeight="1dp"android:layout_marginTop="16dp" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="在线用户列表"android:textSize="18sp"android:textStyle="bold"android:layout_marginTop="16dp" /><ListViewandroid:id="@+id/user_list_view"android:layout_width="match_parent"android:layout_height="wrap_content"android:dividerHeight="1dp"android:layout_marginTop="8dp" />
</LinearLayout>
代码解读
涉及到的布局属性
- xml声明,编码方式为utf-8
<?xml version="1.0" encoding="utf-8"?>
- 开始一个线性布局容器
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- 宽高和父本容器同尺寸(全屏)
android:layout_width="match_parent"
android:layout_height="match_parent"
- 线性布局方向为垂直方向
android:orientation="vertical" //horizontal水平从左向右排列,vertical垂直从上向下排列
- 给整个布局设置一个16dp的内边距
android:padding="16dp"
- 添加文本内容
android:text="XXX"
- 文本大小 文本样式 对齐方式
android:textSize="24sp" //大小
android:textStyle="bold" //样式 加粗
android:gravity="center" //对齐方式 居中
- 让添加该属性的控件水平居中
android:layout_gravity="center_horizontal"
- 文本输入框,输入提示:没输入内容时显示提示语句,输入文本后就不可见,起提示作用
android:hint="//提示语句"
- 允许该控件输入框输入多行文本,
android:inputType="textMultiLine" //允许输入多行文本
android:minLines="3" //最少3行
android:maxLines="5" //最多5行
- 控件靠右
android:layout_gravity="end"
这里说下android:gravity和android:layout_gravity区别
android:gravity
作用对象为当前控件内部,比如有一个TextView
的文本内容,如果使用android:gravity="center"
,则会让文本内容在该TextView
内部居中,和TextView
在整个屏幕的位置没关系
android:layout_gravity
作用对象为当前控件,这里还用TextView
举例,如果使用android:layout_gravity"center_horizontal"
,则会让该TextView在屏幕中的位置处于居中状态,和控件内部的内容没关系
- ListView列表项之间的分割线高度
android:dividerHeight="1dp"
- 控件间的距离
android:layout_marginTop="16dp"//当前控件与上方相邻控件的距离
MainActivity
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;public class MainActivity extends AppCompatActivity {private Socket socket;private DataInputStream in;private DataOutputStream out;private Handler handler = new Handler(Looper.getMainLooper());private List<String> messages = new ArrayList<>();private ArrayAdapter<String> messageAdapter;private List<String> onlineUsers = new ArrayList<>();private ArrayAdapter<String> userAdapter;private EditText inputField;private Button sendButton;private ListView messageListView, userListView;private String nickname = "XXX"; // 使用实际的昵称@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);messageListView = findViewById(R.id.message_list_view);userListView = findViewById(R.id.user_list_view);inputField = findViewById(R.id.input_field);sendButton = findViewById(R.id.send_button);messageAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, messages);messageListView.setAdapter(messageAdapter);userAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, onlineUsers);userListView.setAdapter(userAdapter);sendButton.setOnClickListener(v -> sendMessage());// 连接到服务器connectToServer();}private void connectToServer() {new Thread(() -> {try {// 创建Socket对象并连接到服务器socket = new Socket("192.168.68.206", 9999); // 替换为实际服务器IP和端口in = new DataInputStream(socket.getInputStream());out = new DataOutputStream(socket.getOutputStream());// 发送登录请求out.writeInt(1);out.writeUTF(nickname);// 开始接收消息receiveMessages();} catch (IOException e) {e.printStackTrace();handler.post(this::disconnect);}}).start();}private void sendMessage() {String message = inputField.getText().toString().trim();if (!message.isEmpty()) {new Thread(() -> {try {out.writeInt(2);out.writeUTF(message);inputField.setText("");} catch (IOException e) {e.printStackTrace();handler.post(this::disconnect);}}).start();}}private void receiveMessages() {new Thread(() -> {try {while (true) {int type = in.readInt();switch (type) {case 1:int count = in.readInt();for (int i = 0; i < count; i++) {String user = in.readUTF();updateOnlineUsers(user);}break;case 2:String msg = in.readUTF();updateMessage(msg);break;}}} catch (IOException e) {e.printStackTrace();handler.post(this::disconnect);}}).start();}private void updateMessage(String message) {handler.post(() -> {messages.add(message);messageAdapter.notifyDataSetChanged();});}private void updateOnlineUsers(String user) {handler.post(() -> {if (!onlineUsers.contains(user)) {onlineUsers.add(user);}userAdapter.notifyDataSetChanged();});}private void disconnect() {if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}@Overrideprotected void onDestroy() {super.onDestroy();disconnect();}
}
代码解读
在我制作的时候,在写方法这里就遇到了问题,总的来说,就是匿名类内部调用方法,默认调用的是匿名类内部的方法,而不调用外部方法.但是我们一般定义类都在外部定义而不会在匿名类内部定义,所以就有找不到调用类的错误(下面有例子)
这里用MainActivity中的sendMessage方法来举例
上图片!
先不看报错部分,如果大致对比一下,就能发现代码好多行都不一样,是的,因为最上面展示的是更改和优化过的"好代码"
点开小红灯泡
就能看到
当当当当
创建方法? 我disconnect已经创建过了,为什么还要我创建
因为他没有找到啊
打个比方说,匿名类就像是封建派的老顽固,只用自己家有的,外来的?“我匿名类可是天朝上国,还需要你的方法?”(其实自家也没有)
而这个方法呢就像世界的先进技术,别人已经研究好的,拿来就能用,可悲的是他非要用自己的,那怎么办?
不开国门做生意,那就打到你开为止,不用?那就逼着你用
所以强制他一下就好啦
架炮!
在报错这行代码的this
前面加上外部类的的类名+点,咱这里就是MainActivity.,加上后效果如下
这里加上**MainActivity.**就是限制了this
必须调用外部类MainActivity
里的方法disconnect
找不到我就硬塞给你,你还不能不要
但是改好了,还和最上面的"好代码"不一样啊
是的,上面的是用了Lambda
表达式的,拆开代码单独看就是…
这个(老)
和这个(新)
的区别
一开始我以为他俩是等效的,只是后者是用了Lambda
简化过的,代码更简洁了而已,但是他的进步远不止于此
可以看到他并没有被"强制"增加MainActivity.
,这是为什么呢?
这是因为Lambda
表达式中的 this
自动指向外部类实例,因此可以直接使用 this::disconnect
说白了就是人家本身就开放,追求"外界",没必要轰他
同理,简洁代码如下
将这个
换成这个
Lambda
好处多多,在这里就不一一赘述了
所以咱家也是好起来了,与时俱进,都改用"先进技术"了
话说回来,先说控件的定义和初始化吧
成员变量声明
private Socket socket; //用于建立连接
private DataInputStream in; //特殊流接收
private DataOutputStream out; //特殊流发送
private Handler handler = new Handler(Looper.getMainLooper());//用于更新主线程
private List<String> messages = new ArrayList<>();//定义存储消息的ArrayList
private ArrayAdapter<String> messageAdapter;//消息显示适配器
private List<String> onlineUsers = new ArrayList<>();//定义存储在线用户的ArrayList
private ArrayAdapter<String> userAdapter;//用户显示适配器
private EditText inputField; //文本输入框
private Button sendButton; //发送按钮
private ListView messageListView, userListView;//群聊消息列表和在线用户列表
private String nickname = "XXX"; //用户名 使用实际的昵称
在线用户和消息显示其实是一样的,这里就只拿消息来举例:
1.当我们客户端收到消息就把消息存到存储消息的ArrayList
–messages中
2.消息存储好后我们要调用把他显示出来,这时候需要用到适配器,来解决"用什么方式来显示"的问题(不用的话太难看)
3.将存储消息的ArrayList
–messages放到适配器里,选择显示方式,创建该适配器对象,并将该适配器对象调用在群聊消息列表ListView
–messageListView中
打个比方: 现在要吃一顿饭,先拿到饭,找到合适的餐具,才能慢慢享用
详细看下面主线程注释
主线程
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
//初始化控件,在布局文件中找到控件messageListView = findViewById(R.id.message_list_view);userListView = findViewById(R.id.user_list_view);inputField = findViewById(R.id.input_field);sendButton = findViewById(R.id.send_button);
//消息适配器,适配器显示方式android.R.layout.simple_list_item_1,调用显示数据集合messagesmessageAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, messages);//将适配器添加到显示窗口 messageListView.setAdapter(messageAdapter);
//同上userAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, onlineUsers);userListView.setAdapter(userAdapter);
//监听按钮,点击发送消息,调用方法sendButton.setOnClickListener(v -> sendMessage());// 连接到服务器,调用方法connectToServer();}
与网络请求有关的方法和代码,是不能堆在主线程(一般是onCreate
)的,因为Android怕这些耗时操作堵塞主线程,影响用户体验,这里的收发消息和获取用户名都是需要网络的,也就是耗时操作,都需要新开线程来进行
连接+上线方法:
connectToServer
方法
private void connectToServer() {//开线程,进行耗时操作new Thread(() -> {try {// 创建Socket对象并连接到服务器socket = new Socket("192.168.68.206", 9999); // 替换为实际服务器IP和端口//初始化,给数据流连接位置in = new DataInputStream(socket.getInputStream());out = new DataOutputStream(socket.getOutputStream());// 发送登录请求out.writeInt(1);out.writeUTF(nickname);// 开始接收消息,调用方法receiveMessages();} catch (IOException e) {e.printStackTrace();//断开连接后,将用户从在线列表中移除handler.post(this::disconnect);}}).start();}
- 登录请求发送消息类型1,发送用户名
- 端口与服务端一致
发送消息方法:
sendMessage
private void sendMessage() {//从输入框获取消息String message = inputField.getText().toString().trim();//消息不为空,则执行if (!message.isEmpty()) {//开线程,进行耗时操作new Thread(() -> {try {//发送消息out.writeInt(2);out.writeUTF(message);//发送后清空输入框inputField.setText("");} catch (IOException e) {e.printStackTrace();//断开连接后,将用户从在线列表中移除,调用方法handler.post(this::disconnect);}}).start();}
}
- 发送消息类型2,发送输入框获取的消息
String message = inputField.getText().toString().trim();
- inputField:这是一个引用,指向一个实现了
getText()
方法的对象,通常是EditText
或TextView
等视图组件。它代表了用户可以输入文本的地方 - getText():这是
EditText
类中的一个方法,用来获取当前输入框内的文本内容。这个方法返回的是一个Editable
对象,而不是直接返回字符串类型 - toString():由于
getText()
返回的是Editable
对象,为了将其转换为String
类型,需要调用toString()
方法。这样做是为了方便后续对文本的操作,比如比较、存储或者展示等 - trim():这个方法的作用是去除字符串两端的空白字符(包括空格、制表符、换行符等)。这对于确保输入数据的有效性非常有用,因为它可以避免因为意外输入的额外空白而导致逻辑错误或者界面显示问题
接收消息方法:
receiveMessages
private void receiveMessages() {//耗时任务new!new!new!new Thread(() -> {try {//无限循环保证在线状态下,可以实时接收消息while (true) {//接收消息类型int type = in.readInt();//处理消息switch (type) {//类型为1,读取发送来的用户个数,循环读取case 1:int count = in.readInt();for (int i = 0; i < count; i++) {String user = in.readUTF();//添加到在线用户列表集合,调用方法updateOnlineUsers(user);}break;//类型为2,读取消息case 2:String msg = in.readUTF();//添加到消息列表集合,调用方法updateMessage(msg);break;}}} catch (IOException e) {e.printStackTrace();//断开连接后,将用户从在线列表中移除handler.post(this::disconnect);}}).start();}
添加消息方法:
updateMessage
private void updateMessage(final String message) {handler.post(() -> {//将输入参数存入消息集合messages.add(message);//通知适配器,有新消息存入,更新显示内容messageAdapter.notifyDataSetChanged();});}
添加在线用户方法:
updateOnlineUsers
private void updateOnlineUsers(String user) {handler.post(() -> {//判断在线用户集合里是否存在新输入参数,有则不会重复添加if (!onlineUsers.contains(user)) {//参数添加到在线用户集合onlineUsers.add(user);}//通知适配器,有新用户名存入,更新显示内容userAdapter.notifyDataSetChanged();});}
断开连接,删除管道方法:
disconnect
private void disconnect() {//管道无连接,即断开状态,客户端关闭管道if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}// 可以在这里添加重新连接逻辑或提示用户断开连接}
关闭程序方法:
onDestroy
@Overrideprotected void onDestroy() {super.onDestroy();disconnect();}
- 调用父类(这里是
Activity
类)的onDestroy()
方法。这是非常重要的一步,因为它确保了所有必要的清理工作由父类完成。每个Activity都继承自Activity
基类,而该基类的onDestroy()
方法可能包含一些必要的资源释放逻辑。 - 忽略这一步可能会导致内存泄漏或其他未预期的行为
super.onDestroy();
对了,别忘了最重要的一步:
配置网络
在AndroidManifest.xml
中添加如下代码
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
完成后如下
我滴任务完成辣!
如有问题,可评论留言,鄙人会试着解决
新手刚上路,错误之处欢迎指出,大家共勉!
}
//通知适配器,有新用户名存入,更新显示内容
userAdapter.notifyDataSetChanged();
});
}
断开连接,删除管道方法:`disconnect````java
private void disconnect() {//管道无连接,即断开状态,客户端关闭管道if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}// 可以在这里添加重新连接逻辑或提示用户断开连接}
关闭程序方法:
onDestroy
@Overrideprotected void onDestroy() {super.onDestroy();disconnect();}
- 调用父类(这里是
Activity
类)的onDestroy()
方法。这是非常重要的一步,因为它确保了所有必要的清理工作由父类完成。每个Activity都继承自Activity
基类,而该基类的onDestroy()
方法可能包含一些必要的资源释放逻辑。 - 忽略这一步可能会导致内存泄漏或其他未预期的行为
super.onDestroy();
对了,别忘了最重要的一步:
配置网络
在AndroidManifest.xml
中添加如下代码
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
完成后如下
[外链图片转存中…(img-8lTKFuqP-1739372330386)]
我滴任务完成辣!
如有问题,可评论留言,鄙人会试着解决
新手刚上路,错误之处欢迎指出,大家共勉!
相关文章:

如何使用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天
手把手教你用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天 目录 文章目录 手把手教你用**Java**语言在**Idea**和**Android**中分别建立**服务端**和**客户端**实现局域网聊天**目录**[toc]**基本实现****问题分析****服务端**Idea:结构预览Server类代码解…...

Java_多线程
并发和并行 并发 在同一时刻,有多个指令在单个CPU上交替执行 并行 在同一时刻,有多个指令在多个CPU上同时执行 多线程的实现方式 继承Thread类的方式 注意给线程设置名字,启动线程等操作 实现Runable的方式 自己创建一个类然后去实现…...

uniapp开发h5部署到服务器
1.发行>网站-PC Web或手机H5(仅适用于uniapp) 2.填写网站域名 3.编译成功后会生成一个unpackage文件夹找到下面的h5 4.接下来会使用一个工具把h5里面的文件放到服务器上面(WinSCP使用其他能部署的工具也行) 5.登录 6.登录成功后…...
如何在个人电脑本地化部署Deepseek-R1大模型
文章目录 Deepseek概述公司简介DeepSeek模型优势DeepSeek模型发展历史Ollama安装Deepseek版本选择Deepseek支持的客户端工具编程语言客户端库桌面客户端插件类其他工具客户端工具配置cherryStudio配置测试如何使用DeepSeek满血版什么是 DeepSeek R1 满血版?deepseek官方第三方…...
Java中关于JSON的基本使用
Java中关于JSON的基本使用 Java中可以操作json的类库jar有很多,网上一找一大片,每种封装的jar包有自己的方法,需要查阅资料使用,但无非就是json转对象,对象转json这些 Java中常见的json类jar包:Gson,Jackso…...
简化的动态稀疏视觉Transformer的PyTorch代码
存一串代码(简化的动态稀疏视觉Transformer的PyTorch代码) import torch import torch.nn as nn import torch.nn.functional as F class DynamicSparseAttention(nn.Module): def __init__(self, dim, num_heads8, dropout0.1): super().__init__()…...

PADS多层板减少层数
前提 PADS是硬件工程师必备的画图软件,相信很多朋友遇到过为降低成本把6层板改为4层,或8层改为6层的经历,正常是把不需要的两层上所有东西删掉,然后修改层设置,下面举例说明。 首先是将要删除的层上的数据全部删除&a…...

你需要提供管理员权限才能删除此文件夹解决方法
立即高级启动 windows10 搜索“设置”,然后“更新和安全””->“恢复”->“立即重新启动” windows11 搜索“设置”,然后“Windows更新”->“更新历史记录”->“恢复”->“立即重新启动” 疑难解答 点击“疑难解答” 高级选项 启…...

螺旋折线(蓝桥杯18G)
、 #include<iostream> using namespace std; typedef pair<int,int> Dot;//存储坐标 int dy[] { 0,1,0,-1 }; int dx[] { -1,0,1,0 }; int main() {int direction 0,x,y,dis 0;Dot pos make_pair(0,0);cin >> x >> y;for (int i 1;; i) {for (…...

常见的数据仓库有哪些?
数据仓库(Data Warehouse,简称数仓)是企业用于存储、管理和分析大量数据的重要工具,其核心目标是通过整合和处理数据,为决策提供高质量、一致性和可信度的数据支持。在构建和使用数仓时,选择合适的工具和技术至关重要。以下是常见的数仓工具及其特点的详细介绍: 1. Hiv…...
数据科学之数据管理|NumPy数据管
一、Numpy介绍 (一) 什么是numpy NumPy是Python中科学计算的基础包。它是一个Python库,提供多维数组对象,各种派生对象(如掩码数组和矩阵),以及用于数组快速操作的各种API,有包括数学、逻辑、形状操作、排序、选择、输入输出、离散傅立叶变换、基本线性代数,基本统计运…...

LSTM 学习笔记 之pytorch调包每个参数的解释
0、 LSTM 原理 整理优秀的文章 LSTM入门例子:根据前9年的数据预测后3年的客流(PyTorch实现) [干货]深入浅出LSTM及其Python代码实现 整理视频 李毅宏手撕LSTM [双语字幕]吴恩达深度学习deeplearning.ai 1 Pytorch 代码 这里直接调用了nn.l…...

ASUS/华硕飞行堡垒9 FX506H FX706H 原厂Win10系统 工厂文件 带ASUS Recovery恢复
华硕工厂文件恢复系统 ,安装结束后带隐藏分区,带一键恢复,以及机器所有的驱动和软件。 支持型号:FX506HC, FX506HE, FX506HM, FX706HC, FX706HE, FX706HM, FX506HHR, FX706HMB, FX706HEB, FX706HCB, FX506HMB, FX506HEB, FX506HC…...
Unity使用iTextSharp导出PDF-04图形
坐标系 pdf文档页面的原点(0,0)在左下角,向上为y,向右为x。 文档的PageSize可获取页面的宽高数值 单位:像素 绘制矢量图形 使用PdfContentByte类进行绘制,注意文档打开后才有此对象的实例。 绘制方法 …...

JDBC如何连接数据库
首先,我们要去下载JDBC的驱动程序 官网下载地址:https://downloads.mysql.com/archives/c-j/ 选择最新版本就可以 然后回到我们idea点击file - project Structure - Modules, 就行了 参考1:如何解决JDBC连接数据库出现问题且对进行数据库操…...

Unity URP的2D光照简介
官网工程,包括2d光照,动画,动效介绍: https://unity.com/cn/blog/games/happy-harvest-demo-latest-2d-techniques https://docs.unity3d.com/6000.0/Documentation/Manual/urp/Lights-2D-intro.html 人物脸部光照细节和脚上的阴影…...

【IC】AI处理器核心--第二部分 用于处理 DNN 的硬件设计
第 II 部分 用于处理 DNN 的硬件设计 第 3 章 关键指标和设计目标 在过去的几年里,对 DNN 的高效处理进行了大量研究。因此,讨论在比较和评估不同设计和拟议技术的优缺点时应考虑的关键指标非常重要,这些指标应纳入设计考虑中。虽然效率通常…...

从 0 开始本地部署 DeepSeek:详细步骤 + 避坑指南 + 构建可视化(安装在D盘)
个人主页:chian-ocean 前言: 随着人工智能技术的迅速发展,大语言模型在各个行业中得到了广泛应用。DeepSeek 作为一个新兴的 AI 公司,凭借其高效的 AI 模型和开源的优势,吸引了越来越多的开发者和企业关注。为了更好地…...

如何本地部署DeepSeek集成Word办公软件
目录 本地部署DeepSeek安装Ollama下载并部署DeepSeek模型安装ChatBox客户端(可选) 将DeepSeek集成到Word修改Word中的VBA代码执行操作 ✍️相关问答如何在Word中安装和使用VBA宏DeepSeek模型有哪些常见的API接口?如何优化DeepSeek在Word中的集…...
Centos10 Stream 基础配置
NetworkManger 安装 dnf install NetworkManager 查看网络配置 nmcli [rootCentos-S-10 /]# nmcli ens33:已连接 到 ens33"Intel 82545EM"ethernet (e1000), 00:0C:29:08:3E:71, 硬件, mtu 1500ip4 默认inet4 192.168.31.70/24route4 default …...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...