Talk about the big data communication mechanism between Android processes: LocalSocket

insert image description here

foreword

Speaking of Android inter-communication, everyone will immediately think of AIDL, but due to the limitation of the Binder mechanism, AIDL cannot transmit large data.

For example, in our previous article "How to process video data in WebRtc?" " It is mentioned that we can get the video data of WebRtc. If we have a demand at this time, what if we want to transfer these video data to another APP? This amount of data will be huge.

So how do we transfer large data between processes?

Android provides us with another mechanism: LocalSocket

It will create a socket channel locally for data transmission.

So how is it used?

First we need two applications: client and server

Initialize LocalSocket

server initialization

override fun run() {
    
    
    server = LocalServerSocket("xxxx")
    remoteSocket = server?.accept()
    ...
}

First create a LocalServerSocket service. The parameter is the service name. Note that the service name needs to be unique, which is the basis for the connection at both ends.

Then call the accept function to wait for the client to connect. This function is a block thread, so another thread is started in the example.

When the client initiates the connection, accept will return the LocalSocket object, and then the data can be transmitted.

Client initialization

var localSocket = LocalSocket()
localSocket.connect(LocalSocketAddress("xxxx"))

First create a LocalSocket object

Then create a LocalSocketAddress object, the parameter is the service name

Then call the connect function to connect to the service. You can use this socket to transmit data.

data transmission

The socket object at both ends is a class, so the sending and receiving codes at both ends are logically consistent.

The input and output stream can be obtained through localSocket.inputStream and localSocket.outputStream , and data transmission is performed by reading and writing the stream.

Note that when reading and writing streams, a new thread must be opened for processing.

Because the socket is bidirectional, both ends can send and receive, that is, read and write

send data

code show as below:

var pool = Executors.newSingleThreadExecutor()
var runnable = Runnable {
    
    
	try {
    
    
		var out = xxxxSocket.outputStream
		out.write(data)
		out.flush()
    } catch (e: Throwable) {
    
    
        Log.e("xxx", "xxx", e)
    }
}
pool.execute(runnable)

Sending data is an active action, each sending requires another thread, so if it is multiple times, we need to use a thread pool for management

If you need to send data multiple times, you can encapsulate it into a function

Receive data

Receiving data is actually a while loop to read data in a loop. It is best to start after the connection is successful. For example, the client code is as follows:

localSocket.connect(LocalSocketAddress("xxx"))
var runnable = Runnable {
    
    
    while (localSocket.isConnected){
    
    
		var input = localSocket.inputStream
		input.read(data)
        ...
    }
}
Thread(runnable).start()

Receiving data is actually a while loop that reads non-stop. If the data is not read, the loop continues, and when the data is read, it is processed and recycled. Therefore, only another thread can be opened here, and a thread pool is not required.

transfer complex data

The above is just a simple example, and complex data cannot be transmitted. If you want to transmit complex data, you need to use DataInputStream and DataOutputStream .

First, a set of protocols needs to be defined.

For example, define a simple protocol: the transmitted data is divided into two parts, the first part is an int value, indicating the length of the subsequent byte data; the second part is the byte data. so you know how to read and write

write complex data

The complex data writing code is as follows:

var pool = Executors.newSingleThreadExecutor()
var out = DataOutputStream(xxxSocket.outputStream)
var runnable = Runnable {
    
    
	try {
    
    
		out.writeInt(data.size)
		out.write(data)
		out.flush()
    } catch (e: Throwable) {
    
    
        Log.e("xxx", "xxx", e)
    }
}
pool.execute(runnable)

Write the size of the data block first, and then write the data.

complex data read

The code to read the above data is as follows:

var runnable = Runnable {
    
    
var input = DataInputStream(xxxSocket.inputStream)
var outArray = ByteArrayOutputStream()
    while (true) {
    
    
        outArray.reset()
		var length = input.readInt()
		if(length > 0) {
    
    
			var buffer = ByteArray(length)
			input.read(buffer)
            ...
        }
    }

}
Thread(runnable).start()

First read an int, which is the size of the next data block, and then directly read a piece of data of this length, so that this part of the data will be completely read out. In this way, it is just the beginning when the next part of data is read.

In this way, complex data can be transmitted without causing data confusion.

Transfer very large data

Although complex data can be transmitted above, problems will also occur when our data is too large.

For example, to transmit pictures or videos, assuming that the byte data length reaches 1228800, then we pass

var buffer = ByteArray(1228800)
input.read(buffer)

Not all data can be read, only part of it can be read. And it will cause confusion in the following data, because the reading position is misplaced.

The read length is about 65535 bytes. This is because TCP is wrapped by IP packets, and there is also a packet size limit of 65535.

But watch out! When writing data, if the data is too large, it will be automatically divided into packets, but when reading data, if it seems that one read cannot span packets, this leads to the above result, only one packet can be read, and the subsequent ones will be messed up.

So how to transmit this kind of super-large data, we use a loop to write it a little bit, and read it a little bit, and constantly correct the offset according to the result. code:

Large data write

The code for writing very large data is as follows:

var pool = Executors.newSingleThreadExecutor()
var out = DataOutputStream(xxxSocket.outputStream)
var runnable = Runnable {
    
    
	try {
    
    
		out.writeInt(data.size)
		var offset = 0
		while ((offset + 1024) <= data.size) {
    
    
		 	out.write(data, offset, 1024)
	        offset += 1024
	    }
		out.write(data, offset, data.size - offset)
		out.flush()
    } catch (e: Throwable) {
    
    
        Log.e("xxxx", "xxxx", e)
    }

}

pool.execute(runnable)

Also write the data size first, then write 1024 data in a loop, record the offset, and finally write the insufficient part at one time.

Very large data read

The above data reading code is as follows:

var input = DataInputStream(xxxSocket.inputStream)
var runnable = Runnable {
    
    
var outArray = ByteArrayOutputStream()
    while (true) {
    
    
        outArray.reset()
		var length = input.readInt()
		if(length > 0) {
    
    
			var buffer = ByteArray(1024)
			var total = 0
            while (total + 1024 <= length) {
    
    
				var count = input.read(buffer)
                outArray.write(buffer, 0, count)
                total += count
            }
			var buffer2 = ByteArray(length - total)
			input.read(buffer2)
            outArray.write(buffer2)
			var result = outArray.toByteArray()
            ...
        }
    }
}
Thread(runnable).start()

Also read an int first, which is the data size. Then read the data of 1024 size in a loop, and read the remaining part together until it is insufficient. In this way, this part of the data is completely read out, and then the next part of the data is read at the beginning.

This can avoid the problem of mismatching the length of the read due to subpackaging

Summarize

LocalSocket is based on the underlying Linux IPC communication mechanism (UNIX-domain socket), so sockets used for network communication do not need to go through the network protocol stack, pack and unpack, and calculate and verify. So the efficiency of LocalSocket is very high.

Someone on the Internet has conducted a test, and the results are as follows:
insert image description here
It can be seen that the transmission of LocalSocket is close to AIDL, so it is still very efficient.

Guess you like

Origin blog.csdn.net/chzphoenix/article/details/122706571