Android夸進(jìn)程通信機(jī)制系列:
Android夸進(jìn)程通信機(jī)制一:多進(jìn)程簡(jiǎn)介
Android夸進(jìn)程通信機(jī)制二:Parcel 與 Parcelable
Android夸進(jìn)程通信機(jī)制三:Messenger與Message
Android夸進(jìn)程通信機(jī)制四:使用 Bundle進(jìn)行進(jìn)程間通信
Android夸進(jìn)程通信機(jī)制五:使用文件共享進(jìn)行進(jìn)程間通信
Android夸進(jìn)程通信機(jī)制六:使用ContentProvider進(jìn)行進(jìn)程間通信
Android夸進(jìn)程通信機(jī)制七:使用 Socket進(jìn)行進(jìn)程間通信
Android夸進(jìn)程通信機(jī)制八:使用 AIDL進(jìn)行進(jìn)程間通信
Android夸進(jìn)程通信機(jī)制九:AIDL深入了解
...
一、前言
我們知道,在Android中,實(shí)現(xiàn)進(jìn)程間的通信,歸納起來有共享文件、binder和Socket,其中Binder包含多種方式,如Messenger、bundle、contentProvider、AIDL。除了Binder,Android還支持Socket,通過Socket也可以實(shí)現(xiàn)任意兩個(gè)終端之間的通信,當(dāng)然一個(gè)設(shè)備上的兩個(gè)進(jìn)程之間通過Socket通信自然也是可以的。
前幾節(jié),我們一起學(xué)習(xí)了共享文件方式和binder方式,這一節(jié),我們來學(xué)習(xí)一下使用另一種方式進(jìn)行進(jìn)程間的通信-Socket。
二、什么是Socket?
Socket的英文原義是“孔”或“插座”。作為BSD UNIX的進(jìn)程通信機(jī)制,取后一種意思。通常也稱作"套接字",用于描述IP地址和端口,是一個(gè)通信鏈的句柄,可以用來實(shí)現(xiàn)不同虛擬機(jī)或不同計(jì)算機(jī)之間的通信。
網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)連接的一端稱為一個(gè)socket。
Socket 也稱為“套接字”,是網(wǎng)絡(luò)通信中的概念,它分為流式套接字和用戶數(shù)據(jù)報(bào)套接字兩種,分別對(duì)應(yīng)網(wǎng)絡(luò)的傳輸控制層中的 TCP 和 UDP 協(xié)議,TCP 協(xié)議是面向連接的協(xié)議,提供穩(wěn)定的雙向功能,TCP 鏈接的建立經(jīng)過“三次握手”才能完成,為了提供穩(wěn)定的數(shù)據(jù)傳輸功能,其本身提供了超時(shí)重傳機(jī)制,因此具有很高的穩(wěn)定性;而 UDP 是無連接的,提供不穩(wěn)定的單向連接功能,當(dāng)然 UDP 也可以實(shí)現(xiàn)雙向通信功能。在性能上,UDP 具有更好的效率,其缺點(diǎn)是不能保證數(shù)據(jù)一定能正確傳輸。尤其是在網(wǎng)絡(luò)擁堵的情況下。接下來我們演示一個(gè)跨進(jìn)程聊天程序,兩個(gè)進(jìn)程可以通過 Socket 來實(shí)現(xiàn)信息的傳輸,Socket 本身可以支持傳輸任意字節(jié)流的。
三、使用Socket進(jìn)行IPC
1、注意點(diǎn)
使用 Socket 來進(jìn)行通信,有兩點(diǎn)需要意點(diǎn)
- 需要聲明權(quán)限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- 2.不鞥在主線程中訪問網(wǎng)絡(luò)
如果鞥在主線程中訪問網(wǎng)絡(luò),會(huì)導(dǎo)致無法再 Android 4.0 及其以上的設(shè)備中運(yùn)行,會(huì)拋出如下異常:
android.os.NetwworkOnMainThreadException。
2. 舉例實(shí)現(xiàn)
我們通過一個(gè)簡(jiǎn)單的聊天程序,達(dá)到使用Socket方式進(jìn)行進(jìn)程間通信的效果。
目標(biāo)
下面開始我們的聊天程序,比較簡(jiǎn)單,首先在遠(yuǎn)程 Socket 建立一個(gè) TCP 服務(wù),然后在主界面中鏈接 TCP 服務(wù),連接上了以后,就可以給服務(wù)端發(fā)消息。
對(duì)于我們發(fā)送的每一條文本信息,服務(wù)端都會(huì)隨機(jī)的回應(yīng)我們一句話。為了更好的展示 Socket 的工作機(jī)制,在服務(wù)端我們需要處理下,使其能夠和多個(gè)客戶端同時(shí)建立鏈接并相應(yīng)。
服務(wù)端
先看一下服務(wù)端設(shè)計(jì),當(dāng) Service 啟動(dòng)時(shí),會(huì)在線程中建立 TCP 服務(wù),這里監(jiān)聽的是 8080 端口,然后就可以等待客戶端的連接請(qǐng)求。當(dāng)客戶端連接時(shí),就會(huì)生成一個(gè)新的 Socket,通過每次新創(chuàng)建的 Socket 就可以分別和不同的客戶端通信了。服務(wù)端每收到一次客戶端的信息就會(huì)包裝一下收到的信息再回復(fù)給客戶端。當(dāng)客戶端連接斷開時(shí),服務(wù)端也會(huì)相應(yīng)的關(guān)閉對(duì)應(yīng)的 Socket 并結(jié)束通話線程,服務(wù)端代碼如下:
/**
* Copyright (C), 2015-2019, 雨紛紛工作室
* FileName: IPCSocketService
* Author: yufenfen
* Email: ybyj1314@126.com
* Date: 2016/4/5 10:11 AM
* Description: IPC之使用Socket方式的服務(wù)端
*/
package yb.demo.myProcesses.useSocket;
import ...
/**
* @ClassName: IPCSocketService
* @Description: IPC之使用Socket方式的服務(wù)端
* @Author: yufenfen
* @Date: 2016/4/5 10:11 AM
*/
public class IPCSocketService extends Service {
//變量
...
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case Socket_Status_ing:
Toast.makeText(IPCSocketService.this.getApplicationContext(),
"建立TCP服務(wù)器……………………,端口:" + SERVERSOCKET_PORT,
Toast.LENGTH_LONG
).show();
break;
case Socket_Status_succee:
Toast.makeText(IPCSocketService.this.getApplicationContext(),
"成功連接服務(wù)器,端口:" + SERVERSOCKET_PORT,
Toast.LENGTH_LONG
).show();
break;
case Socket_Status_fail:
String error = msg.getData().getString("error");
Toast.makeText(IPCSocketService.this.getApplicationContext(),
"連接服務(wù)器失敗,端口:" + SERVERSOCKET_PORT+ ", " + error,
Toast.LENGTH_LONG
).show();
break;
default:
break;
}
}
};
@Override
public void onCreate() {
super.onCreate();
Toast.makeText(IPCSocketService.this.getApplicationContext(),
"建立TCP服務(wù)器,端口:" + SERVERSOCKET_PORT,
Toast.LENGTH_LONG
).show();
//注意在新線程中操作
new Thread(){
@Override
public void run() {
createSocket();
}
}.start();
}
····
public void createSocket() {
try {
mServer = new ServerSocket(SERVERSOCKET_PORT);
mExecutorService = Executors.newCachedThreadPool();
Socket client = null;
while (mIsServiceDestoryed) {
client = mServer.accept(); //每接受到一個(gè)新Socket連接請(qǐng)求,就會(huì)新建一個(gè)Thread去處理與其之間的通信
mHandler.sendEmptyMessage(Socket_Status_ing);
mList.add(client);
mExecutorService.execute(new Service(client));
}
} catch (Exception e) {
e.printStackTrace();
Message msg = mHandler.obtainMessage(Socket_Status_fail);
Bundle data = new Bundle();
data.putString("error", e.toString());
mHandler.sendMessage(msg);
}
}
class Service implements Runnable {
private Socket socket;
private BufferedReader in = null;
private PrintWriter printWriter=null;
private String receiveMsg;
private String sendMsg;
public Service(Socket socket) { //這段代碼對(duì)應(yīng)步驟三
this.socket = socket;
try {
printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter( socket.getOutputStream(), "UTF-8")), true);
in = new BufferedReader(new InputStreamReader(
socket.getInputStream(),"UTF-8"));
printWriter.println("成功連接服務(wù)器"+"(服務(wù)器發(fā)送)");
mHandler.sendEmptyMessage(Socket_Status_succee);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
while (true) { //循環(huán)接收、讀取 Client 端發(fā)送過來的信息
if ((receiveMsg = in.readLine())!=null) {
System.out.println("receiveMsg:"+receiveMsg);
if (receiveMsg.equals("0")) {
System.out.println("客戶端請(qǐng)求斷開連接");
printWriter.println("服務(wù)端斷開連接"+"(服務(wù)器發(fā)送)");
mList.remove(socket);
in.close();
socket.close(); //接受 Client 端的斷開連接請(qǐng)求,并關(guān)閉 Socket 連接
break;
} else {
sendMsg = "我已接收:" + receiveMsg + "(服務(wù)器發(fā)送)";
printWriter.println(sendMsg); //向 Client 端反饋、發(fā)送信息
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
服務(wù)端使用線程池實(shí)現(xiàn)多客戶端連接,server.accept() 表示等待客戶端連接,當(dāng)有客戶端連接時(shí)新建一個(gè)線程去處理。
客戶端
接著看一下客戶端,客戶端 Activity 啟動(dòng)時(shí),會(huì)在 onCreate 中開啟一個(gè)線程去鏈接服務(wù)端的 Socket,至于為什么用線程在前邊已經(jīng)做了介紹。
/**
* Copyright (C), 2015-2019, 雨紛紛工作室
* FileName: IPCSocketClientActivity
* Author: yufenfen
* Email: ybyj1314@126.com
* Date: 2016/4/5 5:01 PM
* Description: IPC之Socket方式,客戶端
*/
package yb.demo.myProcesses.useSocket;
import ...
/**
* @ClassName: IPCSocketClientActivity
* @Description: IPC之Socket方式,客戶端
* @Author: yufenfen
* @Date: 2016/4/5 5:01 PM
*/
public class IPCSocketClientActivity extends Activity {
private static final String TAG = "IPCSocketClientActivity";
private EditText mEditText;
private TextView mTextView;
private PrintWriter printWriter;
private BufferedReader in;
private ExecutorService mExecutorService = null;
private String receiveMsg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.ipcsocketclient_activity_layout);
mEditText = (EditText) findViewById(R.id.send_msg);
mTextView = (TextView) findViewById(R.id.socket_msg);
mExecutorService = Executors.newCachedThreadPool();
}
public void connect(View view) {
mExecutorService.execute(new connectService()); //在一個(gè)新的線程中請(qǐng)求 Socket 連接
}
public void send(View view) {
String sendMsg = mEditText.getText().toString();
mExecutorService.execute(new sendService(sendMsg));
}
public void disconnect(View view) {
mExecutorService.execute(new sendService("0"));
}
private class sendService implements Runnable {
private String msg;
sendService(String msg) {
this.msg = msg;
}
@Override
public void run() {
printWriter.println(this.msg);
}
}
private class connectService implements Runnable {
@Override
public void run() {
try {
Socket socket = new Socket(IPCSocketService.SERVERSOCKET_HOST, IPCSocketService.SERVERSOCKET_PORT);
socket.setSoTimeout(60000);
printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream(), "UTF-8")), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
receiveMsg();
} catch (Exception e) {
Log.e(TAG, ("connectService:" + e.getMessage())); //如果Socket對(duì)象獲取失敗,即連接建立失敗,會(huì)走到這段邏輯
e.printStackTrace();
}
}
}
private void receiveMsg() {
try {
while (true) {
if ((receiveMsg = in.readLine()) != null) {
Log.d(TAG, "receiveMsg:" + receiveMsg);
runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText(receiveMsg + "\n\n" + mTextView.getText());
}
});
}
}
} catch (IOException e) {
Log.e(TAG, "receiveMsg: ");
e.printStackTrace();
}
}
}
服務(wù)端連接成功后,就可以和服務(wù)端進(jìn)行通信了。
接著和發(fā)送消息的過程,這個(gè)就簡(jiǎn)單了,這里不再詳細(xì)說明.
四、結(jié)語
上述就是通過 Socket 來進(jìn)行進(jìn)程間通信的示例,這里采用 TCP 套接字實(shí)現(xiàn)利用Sock在進(jìn)程間的通信,還可以采用 UDP 套接字,這里就不做詳細(xì)舉例了。
另外,我們知道兩個(gè)進(jìn)程如果要進(jìn)行通訊最基本的一個(gè)前提就是能夠唯一的標(biāo)識(shí)一個(gè)進(jìn)程,在本地進(jìn)程通訊中我們可以使用 PID 來唯一標(biāo)識(shí)一個(gè)進(jìn)程,但 PID 只在本地是唯一的,網(wǎng)絡(luò)中兩個(gè)進(jìn)程 PID 沖突幾率很大,這時(shí)我們就需要通過其他手段來唯一標(biāo)識(shí)網(wǎng)絡(luò)中的進(jìn)程了,我們知道 IP 層的 ip 地址可以唯一標(biāo)示主機(jī),而 TCP 層協(xié)議和端口號(hào)結(jié)合就可以唯一標(biāo)示主機(jī)的一個(gè)進(jìn)程了。
我們示例的IP用的本地主機(jī)
public static final String SERVERSOCKET_HOST = "localhost";
你可以替換成其他可見的IP地址。