详解 Android IPC 机制(八)使用 Socket 实现进程间通信

Socket 也称为 “嵌套字”,是计算机网络中的概念,分为流式嵌套字(TCP)和用户数据报嵌套字(UDP)。

不同用户进程通过 Socket 进行通信也是一种 IPC 方式。

在使用 Socket 通信前应在 AndroidManifest 中声明权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

1、服务端

我们需要一个 Service 作为服务端,声明如下:

<service
android:name=".socket.SocketService"
android:enabled="true"
android:exported="true"
android:process=":remote" />

Socket 服务端在 Service 启动时,会建立 TCP 连接并监听 8688 端口。

public class SocketService extends Service {

private static final String TAG = "SocketService";

private boolean isDestroyed = false;
private String[] messages = new String[]{
"你好啊,哈哈",
"请问你叫什么名字呀?",
"今天北京天气不错啊",
"你知道吗?我可是可以和多个人同时聊天的哦",
"给你讲个笑话吧:据说爱笑的人运气不会太差,不知道真假。"
};

public SocketService() {
}

@Override
public void onCreate() {
super.onCreate();
new Thread(new TCPServer()).start();
}

@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onDestroy() {
isDestroyed = true;
super.onDestroy();
}
private class TCPServer implements Runnable {
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
e.printStackTrace();
}
while (!isDestroyed) {
try {
final Socket client = serverSocket.accept();
Log.d(TAG, "accept");
new Thread() {
@Override
public void run() {
responseClient(client);
}
}.start();

} catch (IOException e) {
e.printStackTrace();
}
}
}

private void responseClient(Socket client) {
try {
// 接收客户端消息
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
// 响应客户端消息
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
Log.d(TAG, "欢迎来到聊天室!");
out.println("欢迎来到聊天室!");
while (!isDestroyed) {
String line = in.readLine();
Log.d(TAG, "message from Client: " + line);
if (line == null) break;
int i = new Random().nextInt(messages.length);
String message = messages[i];
out.println(message);
Log.d(TAG, "response to Client: " + message);
}
out.close();
in.close();

client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

当与客户端建立连接后,新建 Socket 客户端,接收消息并作出响应。

2、客户端

客户端部分首先启动 Socket 服务,并且在连接失败后会不断重新尝试连接。

public class SocketActivity extends AppCompatActivity {
private static final String TAG = "SocketActivity";

private Button bt_send;
private EditText et_receive;
private TextView tv_message;

private PrintWriter mPrintWriter;

private Socket mClientSocket;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_socket);

initView();

Intent service = new Intent(this, SocketService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectSocketServer();
}
}.start();
}

private void initView() {
et_receive = findViewById(R.id.et_receive);
bt_send = findViewById(R.id.bt_send);
tv_message = findViewById(R.id.tv_message);
bt_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String msg = et_receive.getText().toString();
//向服务器发送信息
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
Log.d(TAG, "onClick: " + msg);
new Thread(new Runnable() {
@Override
public void run() {
mPrintWriter.println(msg);
}
}).start();
tv_message.setText(tv_message.getText() + "\n" + "客户端:" + msg);
et_receive.setText("");
}
}
});
}

private void connectSocketServer() {
Socket socket = null;
while (socket == null) {
try {
//选择和服务器相同的端口8688
socket = new Socket("localhost", 8688);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
} catch (IOException e) {
SystemClock.sleep(1000);
}
}
try {
// 接收服务器端的消息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (!isFinishing()) {
final String msg = br.readLine();
if (msg != null) {
runOnUiThread(
new Runnable() {
@Override
public void run() {
tv_message.setText(tv_message.getText() + "\n" + "服务端:" + msg);
}
}
);
}
}
mPrintWriter.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
protected void onDestroy() {
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
}

主要使用 socket.getOutputStream() 和 socket.getInputStream() 方法分别发送、接收服务端消息。

打印日志如下:

8.Socket日志

最终效果如下:

8.Socket进程间通信

到这里把 Android IPC 通信的几种实现方式基本看了一遍,但是在 Binder 机制原理方面还有欠缺,后面再深入学习。

参考资料
1、《Android开发艺术探索》 – 2.4.6 使用 Socket
2、Android IPC机制(五)用Socket实现跨进程聊天程序 | 刘望舒的博客
http://liuwangshu.cn/application/ipc/5-socket.html