Android中的IPC进程通信方式第五篇

本文系转载文章,阅读原文可获取源码,文章末尾有原文链接
【Android中的IPC进程通信方式第五篇】ps:本文的讲的是使用 Socket 进行进程间通信,demo 是用 Kotlin 语言写的
1、使用 Socket
Socket 的中文名字称为“套接字”,是应用层 与 TCP/IP 协议族通信的中间软件抽象层,表现为一个封装了 TCP / IP 协议族 的编程接口(API);
它分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中的TCP和 UDP协议。
TCP协议是面向连接的协议,提供稳定的双向通信功能,连接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能;TCP 为了保证数据包传输的可靠行,会给每个包一个序号,同时此序号也保证了发送到接收端主机能够按序接收,然后接收端主机对成功接收到的数据包发回一个相应的确认字符,如果发送端主机在合理的往返时延内未收到确认字符,那么对应的数据包就被认为丢失并将被重传;UDP是一种无连接的协议,不保证可靠性,UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,即无法得知其是否安全完整到达,但在性能上,UDP具有更好的效率。
我们用 Socket 进行 IPC 通信时,它也属于网络操作,很有可能是耗时的,所以在接收或者发送数据的时候尽量要用子线程来操作,因为放在主线程中会影响程序的响应效率,从性能方面也不应该在主线程中访问网络;下面我们来写一个 demo;
(1)服务器端,新建一个 kt 类 MyService(包名com.xe.ipcservice) 并继承 Service:
class MyService: Service() {

private var mIsServiceDestoryed = false private val TAG = "MyService" override fun onBind(intent: Intent?): IBinder { return null!! }override fun onDestroy() { super.onDestroy() mIsServiceDestoryed = true }override fun onCreate() { super.onCreate() Thread(TcpServer()).start() }private fun recevi(client: Socket) { var inB: BufferedReader? = null var out: PrintWriter? = null try { inB = BufferedReader(InputStreamReader(client.getInputStream())) out = PrintWriter(BufferedWriter(OutputStreamWriter(client.getOutputStream())), true) var msg: String = "" while (!mIsServiceDestoryed) { Thread.sleep(50) msg = inB!!.readLine() if (msg != null) { var s: String = "服务器端收到消息,正准备发送回去------" + msg Log.d(TAG, s) out.println(s) } else { Log.d(TAG, "msg == null") } } } catch (e: IOException) { e.printStackTrace() } catch (e: InterruptedException) { e.printStackTrace() } finally { if (out != null) { out.close() } if (inB != null) { try { inB.close() } catch (e: IOException) { e.printStackTrace() }} } }internal inner class TcpServer : Runnable { override fun run() { var serverSocket: ServerSocket? = null try { serverSocket = ServerSocket(8083) } catch (e: IOException) { e.printStackTrace() }while (!mIsServiceDestoryed) { try { val client = serverSocket!!.accept() recevi(client) } catch (e: IOException) { e.printStackTrace() }} } }

}
这里我们的服务器端用的是8083端口号,一开始的时候开启一个子线程,创建一个 ServerSocket 对象,并等待客户端的连接,当客户端连接成功后,通过 ServerSocket 对象获取到输入流 BufferedReader 和输出流 PrintWriter;通过 BufferedReader 接收到客户端发送过来的数据经过修饰内容之后再用 PrintWriter 发送给客户端。
(2)客户端,创建一个 kt 类型的 Activity,它的名字为 ClientActivity(包名com.xe.ipcdemo5):
class ClientActivity : AppCompatActivity() {
var mBtnConnect: Button? = null var mTvMessage: TextView? = null var mBtnSend: Button? = null var mH: Handler? = null var mReceiveThread: Thread? = null var mPrintWriter: PrintWriter? = null var mClientSocket: Socket? = null var isThreadActive: Boolean = true var mDefinedMessages = arrayOf("你好!", "请问你叫什么名字", "今天天气不错", "给你讲个笑话吧", "这个可以多人聊天") companion object { var MESSAGE_SOCKET_CONNECTED: Int = 1 var UPDATE_VIEW: Int = 2 var TAG: String = "ClientActivity" }override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_client) init(); startMyService() }fun startMyService() { startService(Intent(this, MyService::class.java)) }fun init() { mBtnConnect = findViewById(R.id.btn_connect) mTvMessage = findViewById(R.id.tv_message) mBtnSend = findViewById(R.id.btn_send); mH = MyHandler(); mReceiveThread = ReceiveThread(); }fun onClick(v: View) { if (v.id == R.id.btn_connect) { connect(v) } else if (v.id == R.id.btn_send) { sendMessage(v) } }fun sendMessage(v: View) { mBtnSend!!.isEnabled = false var t: Thread = SendThread() t.start()}inner class SendThread : Thread() { override fun run() { super.run() try { var index: Int = Random().nextInt(mDefinedMessages.size) if (mPrintWriter != null) { mPrintWriter!!.println(mDefinedMessages[index]) } else { Log.d(TAG,"mPrintWriter == null") } } catch (e: Exception) {} finally { mH!!.sendEmptyMessage(UPDATE_VIEW) } } }fun connect(v: View) { mReceiveThread!!.start() v.isEnabled = false }inner class MyHandler : Handler() { override fun handleMessage(msg: Message?) { super.handleMessage(msg) if (msg!!.what == MESSAGE_SOCKET_CONNECTED) { var message: String = msg!!.obj as String var mTvContent: String = mTvMessage!!.text.toString() mTvContent = mTvContent + "\n" + message mTvMessage!!.setText(mTvContent) } else if (msg!!.what == UPDATE_VIEW){ mBtnSend!!.isEnabled = true } } }inner class ReceiveThread : Thread() { override fun run() { super.run() var socket: Socket? = null while (socket == null) { try { socket = Socket("127.0.0.1", 8083); mClientSocket = socket; mPrintWriter = PrintWriter(BufferedWriter(OutputStreamWriter(socket.getOutputStream())), true); } catch (e: IOException) { e.printStackTrace(); } } var br: BufferedReader? = null try { br = BufferedReader(InputStreamReader(socket.getInputStream())); while (isThreadActive) { var msg = br!!.readLine() Thread.sleep(500); if (msg != null) { var message: Message = Message.obtain(); message.what = MESSAGE_SOCKET_CONNECTED message.obj = msg mH!!.sendMessage(message); } }} catch (e: IOException) { e.printStackTrace(); } catch (e: InterruptedException) { e.printStackTrace(); } finally { if (br != null) { try { br.close(); } catch (e: IOException) { e.printStackTrace(); } } } try { socket.close(); } catch (e: IOException) { e.printStackTrace(); } } }override fun onDestroy() { super.onDestroy() isThreadActive = false; mReceiveThread = null if (mPrintWriter != null) { mPrintWriter!!.close() mPrintWriter = null } if (mClientSocket != null) { try { mClientSocket!!.shutdownInput() mClientSocket!!.close() } catch (e: IOException) { e.printStackTrace() }} }

}
ClientActivity 对应的布局文件 activity_client 如下所示:

xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.xe.ipcdemo5.ClientActivity">


首先客户端先开启 MyService 同时也开启了一个进程,通过点击“连接服务器”的按钮开启一个子线程 ReceiveThread,该子线程主要的事情是通过端口号 8083 创建一个 Socket 对象,通过 Socket 对象获取一个输入流 BufferedReader 和一个输出流 PrintWriter,然后通过 BufferedReader 进行等待接收数据,接收到的数据切换到主线程,并用 TextView 进行显示;点击“发送一条消息”的按钮,主要的事情是开启一个子线程用 PrintWriter 将数据发送出去。
(3)对 AndroidManifest.xml 文件进行相应的配置:

package="com.xe.ipcdemo5">


程序一开始运行的界面如下所示:
图片
点击“连接服务器”按钮后,再连续点击“发送一条消息”按钮,界面改变如下所示:
图片
控制台的日志打印如下所示:
图片

    推荐阅读