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 如下所示:

<?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>

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

图片

点击“连接服务器”按钮后,再连续点击“发送一条消息”按钮,界面改变如下所示:

图片

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

图片

阅读 181

活到老,学到老。

1 声望
1 粉丝
0 条评论
你知道吗?

活到老,学到老。

1 声望
1 粉丝
宣传栏