手把手教你用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)]
我滴任務完成辣!
如有問題,可評論留言,鄙人會試著解決
新手剛上路,錯誤之處歡迎指出,大家共勉!