Android使用多線(xiàn)程進(jìn)行網(wǎng)絡(luò)聊天室通信
TCP/IP通信協(xié)議是一種可靠的網(wǎng)絡(luò)協(xié)議,它在通信的兩端各建立一個(gè)Socket,從而在通信的兩端之間形成網(wǎng)絡(luò)虛擬鏈路。一旦建立了虛擬的網(wǎng)絡(luò)鏈路,兩端的程序就可以通過(guò)虛擬鏈路進(jìn)行通信了。Java對(duì)基于TCP協(xié)議的網(wǎng)絡(luò)通信提供了良好的封裝,Java使用Socket對(duì)象來(lái)代表兩端通信接口,并通過(guò)Socket產(chǎn)生IO流來(lái)進(jìn)行網(wǎng)絡(luò)通信。
下面的程序Demo是實(shí)現(xiàn)一個(gè)簡(jiǎn)單的C/S聊天室的應(yīng)用,每個(gè)客戶(hù)端該包含兩條線(xiàn)程:一條負(fù)責(zé)生成主界面,響應(yīng)用戶(hù)動(dòng)作,并將用戶(hù)輸入的數(shù)據(jù)寫(xiě)入Socket對(duì)應(yīng)的輸出流中;另一條負(fù)責(zé)讀取Socket對(duì)應(yīng)的輸入流中的數(shù)據(jù)(從服務(wù)器發(fā)送過(guò)來(lái)的數(shù)據(jù)),并負(fù)責(zé)將這些數(shù)據(jù)在程序界面上顯示出來(lái)。
客戶(hù)端程序是一個(gè)Android應(yīng)用,因此需要?jiǎng)?chuàng)建一個(gè)Android項(xiàng)目,這個(gè)Android應(yīng)用的界面中包含兩個(gè)文本框:一個(gè)用于接收用戶(hù)的輸入;另一個(gè)用于顯示聊天信息。界面中還有一個(gè)按鈕,當(dāng)用戶(hù)單擊該按鈕時(shí),程序向服務(wù)器發(fā)送聊天信息。
layout/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"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <!-- 定義一個(gè)文本框,它用于接收用戶(hù)的輸入 --> <EditText android:id="@+id/input" android:layout_width="280dp" android:layout_height="wrap_content" /> <Button android:id="@+id/send" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="8dp" android:text="發(fā)送" /> </LinearLayout> <!-- 定義一個(gè)文本框,它用于顯示來(lái)自服務(wù)器的信息 --> <TextView android:id="@+id/show" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffff" android:gravity="top" android:textColor="#f000" android:textSize="18sp" /> </LinearLayout>
客戶(hù)端的Activity負(fù)責(zé)生成程序界面,并為程序的按鈕單擊事件綁定事件監(jiān)聽(tīng)器,當(dāng)用戶(hù)單擊按鈕時(shí)向服務(wù)器發(fā)送信息。
MainActivity.java邏輯代碼如下:
package com.fukaimei.multithreadclient;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// 定義界面上的兩個(gè)文本框
EditText input;
TextView show;
// 定義界面上的一個(gè)按鈕
Button send;
Handler handler;
// 定義與服務(wù)器通信的子線(xiàn)程
ClientThread clientThread;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
input = (EditText) findViewById(R.id.input);
send = (Button) findViewById(R.id.send);
show = (TextView) findViewById(R.id.show);
handler = new Handler() // ②
{
@Override
public void handleMessage(Message msg) {
// 如果消息來(lái)自于子線(xiàn)程
if (msg.what == 0x123) {
// 將讀取的內(nèi)容追加顯示在文本框中
show.append("\n" + msg.obj.toString());
}
}
};
clientThread = new ClientThread(handler);
// 客戶(hù)端啟動(dòng)ClientThread線(xiàn)程創(chuàng)建網(wǎng)絡(luò)連接、讀取來(lái)自服務(wù)器的數(shù)據(jù)
new Thread(clientThread).start(); // ①
send.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
// 當(dāng)用戶(hù)按下發(fā)送按鈕后,將用戶(hù)輸入的數(shù)據(jù)封裝成Message
// 然后發(fā)送給子線(xiàn)程的Handler
Message msg = new Message();
msg.what = 0x234;
msg.obj = input.getText().toString();
clientThread.revHandler.sendMessage(msg);
// 清空input文本框
input.setText("");
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
當(dāng)用戶(hù)單擊該程序界面中的“發(fā)送”按鈕后,程序?qū)?huì)把input輸入框中的內(nèi)容發(fā)送給clientThread的revHandler對(duì)象,clientThread負(fù)責(zé)將用戶(hù)輸入的內(nèi)容發(fā)送給服務(wù)器。
ClientThread子線(xiàn)程負(fù)責(zé)建立與遠(yuǎn)程服務(wù)器的連接,并負(fù)責(zé)與遠(yuǎn)程服務(wù)器通信,讀到數(shù)據(jù)之后便通過(guò)Handler對(duì)象發(fā)送一條消息;當(dāng)ClientThread子線(xiàn)程收到UI線(xiàn)程發(fā)送過(guò)來(lái)的消息后,還負(fù)責(zé)將用戶(hù)輸入的內(nèi)容發(fā)送給遠(yuǎn)程服務(wù)器。
ClientThread.java邏輯代碼如下:
package com.fukaimei.multithreadclient;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class ClientThread implements Runnable {
private static final String TAG = "ClientThread";
private Socket s;
// 定義向UI線(xiàn)程發(fā)送消息的Handler對(duì)象
private Handler handler;
// 定義接收UI線(xiàn)程的消息的Handler對(duì)象
public Handler revHandler;
// 該線(xiàn)程所處理的Socket所對(duì)應(yīng)的輸入流
BufferedReader br = null;
OutputStream os = null;
public ClientThread(Handler handler) {
this.handler = handler;
}
public void run() {
try {
s = new Socket("172.xx.xx.xxx", 30000);
br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
os = s.getOutputStream();
// 啟動(dòng)一條子線(xiàn)程來(lái)讀取服務(wù)器響應(yīng)的數(shù)據(jù)
new Thread() {
@Override
public void run() {
String content = null;
// 不斷讀取Socket輸入流中的內(nèi)容
try {
while ((content = br.readLine()) != null) {
// 每當(dāng)讀到來(lái)自服務(wù)器的數(shù)據(jù)之后,發(fā)送消息通知程序
// 界面顯示該數(shù)據(jù)
Message msg = new Message();
msg.what = 0x123;
msg.obj = content;
handler.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
// 為當(dāng)前線(xiàn)程初始化Looper
Looper.prepare();
// 創(chuàng)建revHandler對(duì)象
revHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 接收到UI線(xiàn)程中用戶(hù)輸入的數(shù)據(jù)
if (msg.what == 0x234) {
// 將用戶(hù)在文本框內(nèi)輸入的內(nèi)容寫(xiě)入網(wǎng)絡(luò)
try {
os.write((msg.obj.toString() + "\r\n")
.getBytes("utf-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
// 啟動(dòng)Looper
Looper.loop();
} catch (SocketTimeoutException e1) {
Log.d(TAG, "網(wǎng)絡(luò)連接超時(shí)!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面線(xiàn)程的功能也非常簡(jiǎn)單,它只是不斷地獲取Socket輸入流中的內(nèi)容,當(dāng)讀到Socket輸入流中的內(nèi)容后,便通過(guò)Handler對(duì)象發(fā)送一條消息,消息負(fù)責(zé)攜帶讀到的數(shù)據(jù)。除此之外,該子線(xiàn)程還負(fù)責(zé)讀取UI線(xiàn)程發(fā)送的消息,接收到消息之后,該子線(xiàn)程負(fù)責(zé)中攜帶的數(shù)據(jù)發(fā)送給遠(yuǎn)程服務(wù)器。
服務(wù)器端應(yīng)該包含多條線(xiàn)程,每個(gè)Socket對(duì)應(yīng)一條線(xiàn)程,該線(xiàn)程負(fù)責(zé)讀取Socket對(duì)應(yīng)輸入流,并將讀到的數(shù)據(jù)向每個(gè)Socket輸出流發(fā)送一遍,因此需要在服務(wù)器端使用List來(lái)保存所有的Socket。
下面是服務(wù)器端的代碼。程序?yàn)榉?wù)器提供了兩個(gè)類(lèi):一個(gè)是創(chuàng)建ServerSocket監(jiān)聽(tīng)的主類(lèi);另一個(gè)是負(fù)責(zé)處理每個(gè)Socket通信的線(xiàn)程類(lèi)。
/MultiThreadServer/src/MyServer.java邏輯代碼如下:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class MyServer {
// 定義保存所有Socket的ArrayList
public static ArrayList<Socket> socketList = new ArrayList<Socket>();
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(30000);
while (true) {
// 此行代碼會(huì)阻塞,將一直等待別人的連接
Socket s = ss.accept();
socketList.add(s);
// 每當(dāng)客戶(hù)端連接后啟動(dòng)一條ServerThread線(xiàn)程為該客戶(hù)端服務(wù)
new Thread(new ServerThread(s)).start();
}
}
}
上面的程序是服務(wù)器端只負(fù)責(zé)接收客戶(hù)端Socket的連接請(qǐng)求,每當(dāng)客戶(hù)端Socket連接到該ServerSocket之后,程序?qū)?duì)應(yīng)Socket加入socketList集合中保存,并為該Socket啟動(dòng)一條線(xiàn)程,該程序負(fù)責(zé)處理該Socket所有的通信任務(wù)。服務(wù)器端線(xiàn)程類(lèi)的代碼如下。
/MultiThreadServer/src/ServerThread.java邏輯代碼如下:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Iterator;
// 負(fù)責(zé)處理每條線(xiàn)程通信的線(xiàn)程類(lèi)
public class ServerThread implements Runnable {
// 定義當(dāng)前線(xiàn)程所處理的Socket
Socket s = null;
// 該線(xiàn)程所處理的Socket所對(duì)應(yīng)的輸入流
BufferedReader br = null;
public ServerThread(Socket s) throws IOException {
this.s = s;
// 初始化該Socket對(duì)應(yīng)的輸入流
br = new BufferedReader(new InputStreamReader(s.getInputStream(), "utf-8"));
}
@Override
public void run() {
String content = null;
// 采用循環(huán)不斷從Socket中讀取客戶(hù)端發(fā)送過(guò)來(lái)的數(shù)據(jù)
while ((content = readFromClient()) != null) {
// 遍歷socketList中的每個(gè)Socket
// 將讀取的內(nèi)容向每個(gè)Socket發(fā)送一次
for (Iterator<Socket> it = MyServer.socketList.iterator(); it.hasNext();) {
Socket s = it.next();
try {
OutputStream os = s.getOutputStream();
os.write((content + "\n").getBytes("utf-8"));
} catch (Exception e) {
e.printStackTrace();
// 刪除該Socket
it.remove();
System.out.println(MyServer.socketList);
}
}
}
}
// 定義讀取客戶(hù)端數(shù)據(jù)的方法
private String readFromClient() {
try {
return br.readLine();
} catch (IOException e) { // 如果捕獲到異常,表明該Socket對(duì)應(yīng)的客戶(hù)端已經(jīng)關(guān)閉
e.printStackTrace();
// 刪除該Socket
MyServer.socketList.remove(s);
}
return null;
}
}
上面的服務(wù)器端線(xiàn)程類(lèi)不斷讀取客戶(hù)端數(shù)據(jù),程序使用readFromClient()方法來(lái)讀取客戶(hù)端數(shù)據(jù),如果在讀數(shù)據(jù)過(guò)程中捕獲到IOException異常,則表明該Socket對(duì)應(yīng)的客戶(hù)端Socket出現(xiàn)問(wèn)題,程序就將該Socket從socketList中刪除。
當(dāng)服務(wù)器線(xiàn)程讀到客戶(hù)端數(shù)據(jù)之后,程序遍歷socketList集合,并將該數(shù)據(jù)向socketList集合中的每個(gè)Socket發(fā)送一次——該服務(wù)器線(xiàn)程將把從Socket中讀到的數(shù)據(jù)向socketList中的每個(gè)Socket轉(zhuǎn)發(fā)一次。
先運(yùn)行上面程序的MyServer類(lèi),該類(lèi)運(yùn)行后只是作為服務(wù)器,看不到任何輸出。接著可以運(yùn)行Android客戶(hù)端——相當(dāng)于啟動(dòng)聊天界面登錄該服務(wù)器,接下來(lái)在任何一個(gè)Android客戶(hù)端輸入一些內(nèi)容后單擊“發(fā)送”按鈕,將可以看到所有客戶(hù)端(包含自己)都會(huì)收到剛剛輸入的內(nèi)容,這樣就簡(jiǎn)單實(shí)現(xiàn)了一個(gè)C/S結(jié)構(gòu)的聊天室的功能。
注意:由于該程序需要訪(fǎng)問(wèn)互聯(lián)網(wǎng),因此還需要在清單文件AndroidManifest.xml文件中授權(quán)訪(fǎng)問(wèn)互聯(lián)網(wǎng)的權(quán)限:
<!-- 授權(quán)訪(fǎng)問(wèn)互聯(lián)網(wǎng)--> <uses-permission android:name="android.permission.INTERNET" />
Demo程序運(yùn)行效果界面截圖如下:

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android實(shí)現(xiàn)簡(jiǎn)單C/S聊天室應(yīng)用
- Android實(shí)現(xiàn)C/S聊天室
- Android Socket通信實(shí)現(xiàn)簡(jiǎn)單聊天室
- Android使用Websocket實(shí)現(xiàn)聊天室
- android socket聊天室功能實(shí)現(xiàn)
- Android 基于Socket的聊天室實(shí)例
- Android編寫(xiě)簡(jiǎn)單的聊天室應(yīng)用
- Android中基于XMPP協(xié)議實(shí)現(xiàn)IM聊天程序與多人聊天室
- Android文本視圖TextView實(shí)現(xiàn)聊天室效果
相關(guān)文章
ScrollView與ListView合用(正確計(jì)算Listview的高度)的問(wèn)題解決
最近做項(xiàng)目中用到ScrollView和ListView一起使用的問(wèn)題,顯示的時(shí)候ListView不能完全正確的顯示,查了好多資料終于成功解決:2013-05-05
Android LocationManager獲取經(jīng)度與緯度等地理信息
這篇文章主要介紹了Android LocationManager獲取經(jīng)度與緯度等地理信息的相關(guān)資料,希望通過(guò)本站大家能掌握這樣的知識(shí),需要的朋友可以參考下2017-09-09
Android P實(shí)現(xiàn)靜默安裝的方法示例(官方Demo)
這篇文章主要介紹了Android P實(shí)現(xiàn)靜默安裝,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Android實(shí)現(xiàn)文字上下滾動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)文字上下滾動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
Android實(shí)現(xiàn)省市區(qū)三級(jí)聯(lián)動(dòng)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)省市區(qū)三級(jí)聯(lián)動(dòng),Spinner實(shí)現(xiàn)省市區(qū)的三級(jí)聯(lián)動(dòng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
Android onKeyDown監(jiān)聽(tīng)返回鍵無(wú)效的解決辦法
這篇文章主要介紹了 Android onKeyDown監(jiān)聽(tīng)返回鍵無(wú)效的解決辦法的相關(guān)資料,需要的朋友可以參考下2017-06-06
Android UI實(shí)現(xiàn)SlidingMenu側(cè)滑菜單效果
這篇文章主要為大家詳細(xì)介紹了Android UI實(shí)現(xiàn)SlidingMenu側(cè)滑菜單效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
Android觸摸及手勢(shì)操作GestureDetector
這篇文章主要a為大家詳細(xì)介紹了Android觸摸及手勢(shì)操作GestureDetector的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-07-07
View中如何進(jìn)行手勢(shì)識(shí)別onFling動(dòng)作實(shí)現(xiàn)介紹
下面我們就以實(shí)現(xiàn)手勢(shì)識(shí)別的onFling動(dòng)作,在CwjView中我們從View類(lèi)繼承,當(dāng)然大家可以從TextView等更高層的界面中實(shí)現(xiàn)觸控,感興趣的朋友可以了解下哈2013-06-06
Android Studio中CodeStyle模板的配置方式
這篇文章主要介紹了Android Studio中CodeStyle模板的配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03

