如何使用Java語言在Idea和Android中分別建立服務端和客戶端實現局域網聊天

手把手教你用Java語言在IdeaAndroid中分別建立服務端客戶端實現局域網聊天

目錄

文章目錄

  • 手把手教你用**Java**語言在**Idea**和**Android**中分別建立**服務端**和**客戶端**實現局域網聊天
    • **目錄**
    • @[toc]
    • **基本實現**
    • **問題分析**
    • **服務端**
      • Idea:
        • 結構預覽
        • Server類
          • 代碼解讀
        • ServerReader類
          • 代碼解讀
    • **客戶端**
      • Android:
        • 結構預覽
        • 布局文件 activity_main.xml
          • 代碼解讀
        • MainActivity
          • 代碼解讀
        • 配置網絡
        • 配置網絡

基本實現

  • 實現客戶端和服務端之間的通信

  • 實現服務端轉接客戶端消息,并發送給其他局域網在線成員

  • 實現服務端接收客戶端消息,并判斷相應類型,做出對應應答

  • 實現客戶端消息發送者 發送時間 當前在線用戶基本可視化


問題分析

  1. 服務端開發

    • 在IntelliJ IDEA中創建一個Java項目。
    • 實現一個簡單的TCP服務器,能夠接收客戶端消息并回顯(或廣播)消息給所有已連接的客戶端。
  2. 客戶端開發

    • 在Android Studio中創建一個Android項目。
    • 實現一個簡單的TCP客戶端,能夠發送消息到服務端并顯示從服務端接收到的消息。
  3. 網絡通信

    • 確保服務端和客戶端在同一局域網內,并且客戶端可以正確連接到服務端。
    • 處理多線程問題,確保服務端可以同時處理多個客戶端連接。

服務端

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的類

注:本文所有讀取發送都使用特殊流DataInputStreamDataOutputStream

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();}}}
}
代碼解讀
  1. 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();}}

  1. 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();

  1. 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:gravityandroid: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();
  1. inputField:這是一個引用,指向一個實現了getText()方法的對象,通常是EditTextTextView等視圖組件。它代表了用戶可以輸入文本的地方
  2. getText():這是EditText類中的一個方法,用來獲取當前輸入框內的文本內容。這個方法返回的是一個Editable對象,而不是直接返回字符串類型
  3. toString():由于getText()返回的是Editable對象,為了將其轉換為String類型,需要調用toString()方法。這樣做是為了方便后續對文本的操作,比如比較、存儲或者展示等
  4. 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)]

我滴任務完成辣!

如有問題,可評論留言,鄙人會試著解決

新手剛上路,錯誤之處歡迎指出,大家共勉!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/895366.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/895366.shtml
英文地址,請注明出處:http://en.pswp.cn/news/895366.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java韓順平最新教程,Java工程師進階

簡介 HikariCP 是用于創建和管理連接&#xff0c;利用“池”的方式復用連接減少資源開銷&#xff0c;和其他數據源一樣&#xff0c;也具有連接數控制、連接可靠性測試、連接泄露控制、緩存語句等功能&#xff0c;另外&#xff0c;和 druid 一樣&#xff0c;HikariCP 也支持監控…

如何在 IDE 里使用 DeepSeek?

近期&#xff0c;阿里云百煉平臺重磅推出 DeepSeek-V3、DeepSeek-R1、DeepSeek-R1-Distill-Qwen-32B 等 6 款模型&#xff0c;進一步豐富其 AI 模型矩陣。與此同時&#xff0c;通義靈碼也緊跟步伐&#xff0c;全新上線模型選擇功能&#xff0c;支持基于百煉的 DeepSeek-V3 和 D…

vue中附件下載及打印功能

1.附件dom 注&#xff1a;fileList是由后臺返回的附件數組&#xff0c;數組中包含附件名稱fileName,附件地址url&#xff0c;附件id等信息 <el-form-item label"附件" style"width: 100% !important;" v-if"modelTypeborrowDetail"><d…

chromium-mojo

https://chromium.googlesource.com/chromium/src//refs/heads/main/mojo/README.md 相關類&#xff1a;https://zhuanlan.zhihu.com/p/426069459 Core:https://source.chromium.org/chromium/chromium/src//main:mojo/core/README.md;bpv1;bpt0 embedder:https://source.chr…

網絡安全技術復習總結

1|0第一章 概論 1.網絡安全發展階段包括四個階段&#xff1a;通信安全、計算機安全、網絡安全、網絡空間安全。 2.2017年6月1日&#xff0c;我國第一部全面規范網絡空間安全的基礎性法律《中華人民共和國網絡安全法》正式實施。 3.2021年 6月10日&#xff0c;《中華人民共和…

基于華為云鏡像加速器的Docker環境搭建與項目部署指南

基于華為云鏡像加速器的Docker環境搭建與項目部署指南 一、安裝Docker1.1 更新系統包1.2 安裝必要的依賴包1.3 移除原有的Docker倉庫配置(如果存在)1.4 添加華為云Docker倉庫1.5 安裝Docker CE1.6 啟動Docker服務1.7 驗證Docker是否安裝成功1.8 添加華為云鏡像加速器地址二、…

在SpringBoot服務器端采購上,如何選擇操作系統、Cpu、內存和帶寬、流量套餐

在Spring Boot服務器端采購時&#xff0c;選擇操作系統、CPU、內存、帶寬和流量套餐需根據應用需求、預算和性能要求綜合考慮。以下是具體建議&#xff1a; 1. 操作系統 Linux發行版&#xff08;如Ubuntu、CentOS&#xff09;&#xff1a;適合大多數Spring Boot應用&#xff…

DedeBIZ系統審計小結

之前簡單審計過DedeBIZ系統&#xff0c;網上還沒有對這個系統的漏洞有過詳盡的分析&#xff0c;于是重新審計并總結文章&#xff0c;記錄下自己審計的過程。 https://github.com/DedeBIZ/DedeV6/archive/refs/tags/6.2.10.zip &#x1f4cc;DedeBIZ 系統并非基于 MVC 框架&…

業務開發 | 基礎知識 | Maven 快速入門

Maven 快速入門 1.Maven 全面概述 Apache Maven 是一種軟件項目管理和理解工具。基于項目對象模型的概念&#xff08;POM&#xff09;&#xff0c;Maven 可以從中央信息中管理項目的構建&#xff0c;報告和文檔。 2.Maven 基本功能 因此實際上 Maven 的基本功能就是作為 Ja…

人工智能之推薦系統實戰系列(協同過濾,矩陣分解,FM與DeepFM算法)

一.推薦系統介紹和應用 (1)推薦系統通俗解讀 推薦系統就是來了就別想走了。例如在大數據時代中京東越買越想買&#xff0c;抖音越刷越是自己喜歡的東西&#xff0c;微博越刷越過癮。 (2).推薦系統發展簡介 1)推薦系統無處不在&#xff0c;它是根據用戶的行為決定推薦的內容…

2.11 sqlite3數據庫【數據庫的相關操作指令、函數】

練習&#xff1a; 將 epoll 服務器 客戶端拿來用 客戶端&#xff1a;寫一個界面&#xff0c;里面有注冊登錄 服務器&#xff1a;處理注冊和登錄邏輯&#xff0c;注冊的話將注冊的賬號密碼寫入數據庫&#xff0c;登錄的話查詢數據庫中是否存在賬號&#xff0c;并驗證密碼是否正確…

Python(十九)實現各大跨境船公司物流查詢數據處理優化

一、前言 之前已經實現了常用 跨境物流船司 基礎信息查詢功能&#xff0c;如下所示 實現各大跨境船公司[COSCO/ZIM/MSK/MSC/ONE/PIL]的物流信息查詢&#xff1a;https://blog.csdn.net/Makasa/article/details/145484999?spm1001.2014.3001.5501 然后本章在其基礎上做了一些…

CentOS開機自啟動服務內容設置

CentOS開機自啟動服務內容設置 1. 開機后自動配置時鐘同步2. 開機自啟動服務腳本3. 配置開機自動添加路由 1. 開機后自動配置時鐘同步 # cat /etc/rc.local /usr/sbin/ntpdate pool.ntp.org >> /var/log/ntpdate.log需要設置/etc/rc.local的一個權限&#xff1a; # ll …

基于微信小程序的博物館預約系統的設計與實現

hello hello~ &#xff0c;這里是 code袁~&#x1f496;&#x1f496; &#xff0c;歡迎大家點贊&#x1f973;&#x1f973;關注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者簡介&#xff1a;一名喜歡分享和記錄學習的在校大學生…

深度學習框架TensorFlow怎么用?

大家好呀&#xff0c;以下是使用 TensorFlow 的詳細步驟&#xff0c;從安裝到構建和訓練模型&#xff1a; 一、安裝 TensorFlow 安裝 Python&#xff1a;TensorFlow 基于 Python&#xff0c;確保已安裝 Python&#xff08;推薦 Python 3.8 及以上版本&#xff09;。可通過 Pyt…

機器學習 - 特征學習(表示學習)

為了提高機器學習算法的能力&#xff0c;我們需要抽取有效、穩定的特征。 傳統的特征提取是通過人工方式進行的&#xff0c;需要大量的人工和專家知識。一個成功的機器學習系統通常需要嘗試大量的特征&#xff0c;稱為特征工程(Feature Engineering).但即使這樣&#xff0c;人…

【pytest】獲取所有用例名稱并存于數據庫

數據庫操作包&#xff0c;引用前面創建的py文件&#xff0c;【sqlite】python操作sqlite3&#xff08;含測試&#xff09; #!/usr/bin/env python # -*- coding: utf-8 -*- # Time : 2025-02-11 8:45 # Author : duxiaowei # File : get_filename.py # Software: 這個文…

2024年12月中國電子學會青少年軟件編程(Python)等級考試試卷(四級)

青少年軟件編程&#xff08;Python&#xff09;等級考試試卷&#xff08;四級&#xff09; 一、單選題(共25題&#xff0c;共60分) 1.以下有關位置實參和關鍵字實參的表述中&#xff0c;錯誤的選項是?(C) A.位置實參和關鍵字實參可以混用。 B. 形參不占用內存地址。 C.調用函…

c/c++藍橋杯經典編程題100道(18)括號匹配

括號匹配 ->返回c/c藍橋杯經典編程題100道-目錄 目錄 括號匹配 一、題型解釋 二、例題問題描述 三、C語言實現 解法1&#xff1a;棧匹配法&#xff08;難度★&#xff09; 解法2&#xff1a;計數器法&#xff08;僅限單一括號類型&#xff0c;難度★☆&#xff09; …

day02冒泡排序

思路&#xff1a; 外層循環控制循環次數(i<len)&#xff0c;設置swapFlagfalse內層循環j1(j<len-i)&#xff0c;兩兩(j和j-1)比較&#xff0c;逆序則交換內層每次循環結束&#xff0c;沒有交換&#xff0c;則break結束 內層循環j從1開始&#xff0c;小于len&#xff0c;…