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

Author Avatar
wshunli 6月 08, 2018
  • 在其它设备中阅读本文章

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

如果本文对您有所帮助,且您手头还很宽裕,欢迎打赏赞助我,以支付网站服务器和域名费用。 https://paypal.me/wshunli 您的鼓励与支持是我更新的最大动力,我会铭记于心,倾于博客。
本文链接:https://www.wshunli.com/posts/c9a2416c.html