A simple example of Android Kotlin using Binder, pipes, sockets, file sharing and semaphores for interprocess communication

In most cases, what we come into contact with are some conceptual articles. I hope this article can bring you a different experience, so that you can better digest the knowledge related to inter-process communication.

tips: In actual development, if inter-process communication is required, it is recommended to use Binder, Broadcast, ContentProvider, etc. These mechanisms are more efficient and safer.

1. Binder

Use AIDL
Reference: https://blog.csdn.net/qq_42751010/article/details/132182022

2. Pipeline

Server code:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedWriter
import java.io.OutputStreamWriter
import java.io. PipedInputStream
import java.io. PipedOutputStream

class ServerMainActivity : AppCompatActivity() {<!-- -->

    private lateinit var outputStream: PipedOutputStream
    private lateinit var inputStream: PipedInputStream

    override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
        super.onCreate(savedInstanceState)
        setContentView(R. layout. activity_main)

        outputStream = PipedOutputStream()
        inputStream = PipedInputStream(outputStream)

        lifecycleScope.launch(Dispatchers.IO) {<!-- -->
            startServer()
        }
    }

    private suspend fun startServer() {<!-- -->
        val writer = BufferedWriter(OutputStreamWriter(outputStream))

        while (true) {<!-- -->
            // read the request content from the pipe
            val request = withContext(Dispatchers.IO) {<!-- -->
                readRequestFromPipe()
            }

            val response = if (request == "open_camera") {<!-- -->
                openCamera()
                "Camera opened"
            } else {<!-- -->
                "Invalid request"
            }

            // write the response content to the pipe
            withContext(Dispatchers.IO) {<!-- -->
                writer. write(response)
                writer. newLine()
                writer. flush()
            }
        }
    }

    private suspend fun readRequestFromPipe(): String {<!-- -->
        val reader = BufferedReader(InputStreamReader(inputStream))
        return withContext(Dispatchers.IO) {<!-- -->
            reader. readLine()
        }
    }

    private fun openCamera() {<!-- -->
        // Open the operation of the camera
    }
}

Client code:

import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io. PipedInputStream
import java.io. PipedOutputStream

class ClientMainActivity : AppCompatActivity() {<!-- -->

    private lateinit var connectButton: Button
    private lateinit var outputStream: PipedOutputStream
    private lateinit var inputStream: PipedInputStream

    override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
        super.onCreate(savedInstanceState)
        setContentView(R. layout. activity_main)

        connectButton = findViewById(R.id.connectButton)
        connectButton.setOnClickListener {<!-- -->
            lifecycleScope.launch(Dispatchers.IO) {<!-- -->
                sendRequestToServer()
            }
        }

        outputStream = PipedOutputStream()
        inputStream = PipedInputStream(outputStream)
    }

    private suspend fun sendRequestToServer() {<!-- -->
        try {<!-- -->
            // write the request content to the pipe
            outputStream.write("open_camera".toByteArray())

            // wait for server response
            val response = withContext(Dispatchers.IO) {<!-- -->
                readResponseFromPipe()
            }

            Log.d("ClientMainActivity", "Received response: $response")
        } catch (e: Exception) {<!-- -->
            e. printStackTrace()
        }
    }

    private suspend fun readResponseFromPipe(): String {<!-- -->
        val reader = BufferedReader(InputStreamReader(inputStream))
        return withContext(Dispatchers.IO) {<!-- -->
            reader. readLine()
        }
    }
}

3. Socket

Server code:

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.PrintWriter
import java.net.ServerSocket

class ServerActivity : AppCompatActivity() {<!-- -->

    override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
        super.onCreate(savedInstanceState)
        setContentView(R. layout. activity_server)

        // Start the coroutine to execute server-side Socket communication
        lifecycleScope. launch {<!-- -->
            startServer()
        }
    }

    private suspend fun startServer() {<!-- -->
        val serverSocket = ServerSocket(8080) // Create a ServerSocket object and listen to the specified port

        while (true) {<!-- -->
            val clientSocket = serverSocket.accept() // wait for client connection

            // handle client request
            launch(Dispatchers.IO) {<!-- -->
                val reader = BufferedReader(InputStreamReader(clientSocket. getInputStream()))
                val writer = PrintWriter(clientSocket. getOutputStream(), true)

                val request = reader.readLine() // read the request sent by the client
                Log.d("ServerActivity", "Received request: $request")

                // process the request and return the response
                val response = "Hello, client!"
                writer. println(response)
                Log.d("ServerActivity", "Sent response: $response")

                if (request == "open_camera") {<!-- -->
                    // After receiving the request to open the camera, call the code of the server to open the camera
                    openCamera()
                }

                writer. close()
                reader. close()
                clientSocket. close()
            }
        }
    }

    private fun openCamera() {<!-- -->
        Log.d("ServerActivity", "Opening camera...")
        //...
    }
}

Client code:

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.PrintWriter
import java.net.Socket

class ClientActivity : AppCompatActivity() {<!-- -->

    private lateinit var connectButton: Button
    private var clientSocket: Socket? = null
    private var writer: PrintWriter? = null
    private var reader: BufferedReader? = null

    override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
        super.onCreate(savedInstanceState)
        setContentView(R. layout. activity_client)

        connectButton = findViewById(R.id.connectButton)
        connectButton.setOnClickListener {<!-- -->
            // send request to server
            lifecycleScope.launch(Dispatchers.IO) {<!-- -->
                sendRequestToServer("open_camera")
            }
        }

        // connect to the server on creation
        lifecycleScope.launch(Dispatchers.IO) {<!-- -->
            connectToServer()
        }
    }

    private suspend fun connectToServer() {<!-- -->
        val serverAddress = "localhost" // server address
        val serverPort = 8080 // server port

        clientSocket = Socket(serverAddress, serverPort) // Create a Socket to connect to the server
        reader = BufferedReader(InputStreamReader(clientSocket?.getInputStream()))
        writer = PrintWriter(clientSocket?.getOutputStream(), true)
        Log.d("ClientActivity", "Connected to server")
    }

    private suspend fun sendRequestToServer(request: String) {<!-- -->
        writer?.println(request) // Send the request to the server
        Log.d("ClientActivity", "Sent request: $request")

        val response = reader?.readLine() // read the response from the server
        Log.d("ClientActivity", "Received response: $response")
    }

    override fun onDestroy() {<!-- -->
        super. onDestroy()
        try {<!-- -->
            // close connection and resource
            writer?. close()
            reader?. close()
            clientSocket?. close()
        } catch (e: Exception) {<!-- -->
            e. printStackTrace()
        }
    }
}

4. File sharing and semaphores

Server code:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.util.concurrent.Semaphore

class ServerMainActivity : AppCompatActivity() {<!-- -->

    private lateinit var requestFile: File
    private lateinit var responseFile: File
    private lateinit var semaphore: Semaphore

    override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
        super.onCreate(savedInstanceState)
        setContentView(R. layout. activity_main)

        requestFile = File(filesDir, "camera_request.txt")
        responseFile = File(filesDir, "camera_response.txt")
        semaphore = Semaphore(0)

        lifecycleScope.launch(Dispatchers.IO) {<!-- -->
            startServer()
        }
    }

    private suspend fun startServer() {<!-- -->
        while (true) {<!-- -->
            // Wait for the semaphore, indicating that there is a new request
            semaphore. acquire()

            if (requestFile. exists()) {<!-- -->
                // read request file
                val request = withContext(Dispatchers.IO) {<!-- -->
                    requestFile. readText()
                }

                val response = if (request == "open_camera") {<!-- -->
                    openCamera()
                    "Camera opened"
                } else {<!-- -->
                    "Invalid request"
                }

                // write to response file
                withContext(Dispatchers.IO) {<!-- -->
                    responseFile.writeText(response)
                }

                // Release the semaphore and notify the client that there is a response to read
                semaphore. release()
            }
        }
    }

    private fun openCamera() {<!-- -->
        // Open the operation of the camera
    }
}

Client code:

import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.util.concurrent.Semaphore

class ClientMainActivity : AppCompatActivity() {<!-- -->

    private lateinit var connectButton: Button
    private lateinit var requestFile: File
    private lateinit var responseFile: File
    private lateinit var semaphore: Semaphore

    override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
        super.onCreate(savedInstanceState)
        setContentView(R. layout. activity_main)

        connectButton = findViewById(R.id.connectButton)
        connectButton.setOnClickListener {<!-- -->
            lifecycleScope.launch(Dispatchers.IO) {<!-- -->
                sendRequestToServer()
            }
        }

        requestFile = File(filesDir, "camera_request.txt")
        responseFile = File(filesDir, "camera_response.txt")
        semaphore = Semaphore(0)
    }

    private suspend fun sendRequestToServer() {<!-- -->
        try {<!-- -->
            // Create a request file and write the request content
            requestFile.writeText("open_camera")

            // Release the semaphore to notify the server of a new request
            semaphore. release()

            // wait for server response
            semaphore. acquire()

            val response = withContext(Dispatchers.IO) {<!-- -->
                responseFile. readText()
            }

            Log.d("ClientMainActivity", "Received response: $response")
        } catch (e: Exception) {<!-- -->
            e. printStackTrace()
        }
    }
}