本文系转载文章,浏览原文可获取源码,文章开端有原文链接

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 = falseprivate 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? = nullvar mTvMessage: TextView? = nullvar mBtnSend: Button? = nullvar mH: Handler? = nullvar mReceiveThread: Thread? = nullvar mPrintWriter: PrintWriter? = nullvar mClientSocket: Socket? = nullvar isThreadActive: Boolean = truevar 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 如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout

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"><Button    android:id="@+id/btn_connect"    android:layout_width="match_parent"    android:text="连贯服务器"    android:onClick="onClick"    android:layout_height="wrap_content" /><Button    android:id="@+id/btn_send"    android:layout_width="match_parent"    android:text="发送一条音讯"    android:onClick="onClick"    android:layout_height="wrap_content" /><TextView    android:id="@+id/tv_message"    android:layout_width="match_parent"    android:layout_height="wrap_content" />

</LinearLayout>
首先客户端先开启 MyService 同时也开启了一个过程,通过点击“连贯服务器”的按钮开启一个子线程 ReceiveThread,该子线程次要的事件是通过端口号 8083 创立一个 Socket 对象,通过 Socket 对象获取一个输出流 BufferedReader 和一个输入流 PrintWriter,而后通过 BufferedReader 进行期待接收数据,接管到的数据切换到主线程,并用 TextView 进行显示;点击“发送一条音讯”的按钮,次要的事件是开启一个子线程用 PrintWriter 将数据发送进来。

(3)对 AndroidManifest.xml 文件进行相应的配置:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.xe.ipcdemo5"><uses-permission android:name="android.permission.INTERNET"></uses-permission><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission><application    android:allowBackup="true"    android:icon="@mipmap/ic_launcher"    android:label="@string/app_name"    android:roundIcon="@mipmap/ic_launcher_round"    android:supportsRtl="true"    android:theme="@style/AppTheme">    <activity android:name=".ClientActivity">        <intent-filter>            <action android:name="android.intent.action.MAIN" />            <category android:name="android.intent.category.LAUNCHER" />        </intent-filter>    </activity>    <service android:name="com.xe.ipcservice.MyService"        android:process=":remote">    </service></application>

</manifest>

程序一开始运行的界面如下所示:

图片

点击“连贯服务器”按钮后,再间断点击“发送一条音讯”按钮,界面扭转如下所示:

图片

控制台的日志打印如下所示:

图片