Rereading the Art of Android Development, this is a summary of chapter 2 in the book.

I. Introduction to IPC

IPC is short for Inter-process Communication. It is the Process of data exchange between two processes.

A process usually refers to an application, and a process can contain multiple threads.

Multi-process scenario in Android: an application is implemented in multi-process mode; Data sharing between two applications.

2. Enable the multi-process mode

The multi-process mode can be enabled by specifying the Android: Process attribute in androidmanifest.xml for the four major components. If the process attribute is not specified, it runs in the default process named package name. The code is as follows:

<activity
        android:name=".ThirdActivity"// Set the property value to:com.hyh.okhttpdemo.remote, process name:com.hyh.okhttpdemo.remote, this process is a global process, and other applications can passShareUIDThe way it runs is in the same processandroid:process="com.hyh.okhttpdemo.remote" 
        android:exported="false" >
</activity>
<activity
        android:name=".ChildActivity"// Set the property value to::remote, process name:com.hyh.okhttpdemo:remoteThis process belongs to the private process of the current application. Components of other applications cannot run in the same process with this processandroid:process=":remote"
        android:exported="false" />
<activity
        android:name=".MainActivity"
        android:configChanges="orientation|screenSize"
        android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
Copy the code

The format and difference of the naming method of process

Check the process information: adb shell ps | grep actual package names

Gets the current process name

private fun Context.currentProcessName(pid: Int): String {
    val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
    activityManager.runningAppProcesses?.forEach { info ->
        if (info.pid == pid) {
            return info.processName
        }
    }
    return ""
}

/ / call
val processName = currentProcessName(Process.myPid())
Copy the code

The rest of the chapter is summed up in mind mapping.

Next up is a Demo of different communication modes between processes, which has been submitted to GitHub

A simple useMessenagerAn example of interprocess communication

The service side

class MessengerService : Service() {

    companion object {
        const val TAG = "MessengerService"
    }

    class MessengerHandler(looper: Looper) : Handler(looper) {

        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_FROM_CLIENT -> {
                    The client message is received
                    Log.d(TAG, "receive msg from Client: ${msg.data.getString("msg")}")
                    // The server replies to the client message
                    val client = msg.replyTo
                    val replayMsg = Message.obtain(null, MSG_FROM_SERVICE)
                    val bundle = Bundle()
                    bundle.putString("reply"."already receive, replay soon.")
                    replayMsg.data = bundle
                    try {
                        client.send(replayMsg)
                    } catch (e: RemoteException) {
                        e.printStackTrace()
                    }
                }
                else- > {super.handleMessage(msg)
                }
            }
        }
    }

    private valmMessenger = Messenger(MessengerHandler(Looper.myLooper()!!) )override fun onBind(intent: Intent): IBinder {
        return mMessenger.binder
    }
}
Copy the code

The client

class MessengerActivity : AppCompatActivity() {

    companion object {
        const val TAG = "MessengerActivity"
    }

    // Receive the message
    private valmReceiveMessenger = Messenger(MessengerHandler(Looper.myLooper()!!) )class MessengerHandler(looper: Looper) : Handler(looper) {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                Constants.MSG_FROM_SERVICE -> {
                    // Receive a server message
                    Log.d(TAG, "receive msg from service: ${msg.data.getString("reply")}")}else- > {super.handleMessage(msg)
                }
            }
        }
    }

    // Send a message
    private lateinit var mService: Messenger

    private val mConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName? , service:IBinder?). {
            mService = Messenger(service)
            val msg = Message.obtain(null, Constants.MSG_FROM_CLIENT)
            val data = Bundle()
            data.putString("msg"."hello, this is client.")
            msg.data = data
            // Pass the object receiving the message from the server to the server
            msg.replyTo = mReceiveMessenger
            try {
                mService.send(msg)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }

        override fun onServiceDisconnected(name: ComponentName?).{}}override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_messenger)
        // Bind the server service
        val intent = Intent(this, MessengerService::class.java)
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
    }

    override fun onDestroy(a) {
        unbindService(mConnection)
        super.onDestroy()
    }
}
Copy the code

The statement

<service
    android:name=".messenger.MessengerService"
    android:process=":remote" />

<activity
    android:name=".messenger.MessengerActivity"
    android:configChanges="orientation|screenSize">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
Copy the code

A simple example of using AIDL communication

Book. The aidl document

package com.hyh.ipc.aidl;

parcelable Book;
Copy the code

IBookManager aidl document

package com.hyh.ipc.aidl;

import com.hyh.ipc.aidl.Book;
import com.hyh.ipc.aidl.IOnNewBookArriveListener;

interface IBookManager {

    List<Book> getBookList();

    void addBook(in Book book);

    void registerListener(IOnNewBookArriveListener listener);

    void unregisterListener(IOnNewBookArriveListener listener);
}
Copy the code

IOnNewBookArriveListener aidl document

package com.hyh.ipc.aidl;
import com.hyh.ipc.aidl.Book;

interface IOnNewBookArriveListener {
    void onNewBookArrived(in Book newBook);
}
Copy the code

Book. Kt file

package com.hyh.ipc.aidl

import android.os.Parcel
import android.os.Parcelable

class Book() : Parcelable {

    var bookId: Int = 0
    var bookName: String = ""

    constructor(id: Int, name: String) : this() {
        this.bookId = id
        this.bookName = name
    }

    constructor(parcel: Parcel) : this() { bookId = parcel.readInt() bookName = parcel.readString() ? :""
    }

    override fun describeContents(a): Int {
        return 0
    }

    override fun writeToParcel(dest: Parcel? , flags:Int){ dest? .apply { writeInt(bookId) writeString(bookName) } }companion object CREATOR : Parcelable.Creator<Book> {
        override fun createFromParcel(parcel: Parcel): Book {
            return Book(parcel)
        }

        override fun newArray(size: Int): Array<Book? > {return arrayOfNulls(size)
        }
    }

    override fun toString(a): String {
        return "[bookId: $bookId, bookName: $bookName]. ""}}Copy the code

The client

package com.hyh.ipc.aidl

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.*
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import com.hyh.ipc.R

class BookManagerActivity : AppCompatActivity() {

    companion object {
        const val TAG = "BookManagerActivity"
        const val MESSAGE_NEW_BOOK_ARRIVED = 1
    }

    private var mRemoteBookManager: IBookManager? = null

    private val mHandler = object : Handler(Looper.myLooper()!!) {
        override fun handleMessage(msg: Message) {
            when(msg.what) {
                MESSAGE_NEW_BOOK_ARRIVED -> {
                    Log.d(TAG, "receive new book : ${msg.obj}")}else- > {super.handleMessage(msg)
                }
            }
        }
    }


    private val mConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName? , service:IBinder?). {
            val bookManager = IBookManager.Stub.asInterface(service)
            try {
                / / assignment
                mRemoteBookManager = bookManager
                val list = bookManager.bookList
                Log.d(TAG, "list type : ${list.javaClass.canonicalName}")
                Log.d(TAG, "query book list: $list")
                bookManager.addBook(Book(3."C"))
                val newList = bookManager.bookList
                Log.d(TAG, "query book newList: $newList")
                // Register events
                bookManager.registerListener(mIOnNewBookArriveListener)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }

        override fun onServiceDisconnected(name: ComponentName?). {
            mRemoteBookManager = null}}private val mIOnNewBookArriveListener = object : IOnNewBookArriveListener.Stub() {
        override fun onNewBookArrived(newBook: Book?). {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget()
        }

    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_book_manager)
        val intent = Intent(this, BookManagerService::class.java)
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
    }

    override fun onDestroy(a) {
        Log.d(TAG, "BookManagerActivity onDestroy()") mRemoteBookManager? .takeIf { it.asBinder().isBinderAlive }? .let {try {
                Log.d(TAG, "unregister listener $mIOnNewBookArriveListener")
                // Unbind events
                it.unregisterListener(mIOnNewBookArriveListener)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }
        unbindService(mConnection)
        super.onDestroy()
    }
}
Copy the code

The service side

package com.hyh.ipc.aidl

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.os.RemoteCallbackList
import android.os.RemoteException
import android.util.Log
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicBoolean

class BookManagerService : Service() {

    companion object {
        const val TAG = "BMS"
    }

    private var mIsServiceDestroyed = AtomicBoolean(false)

    val mBookList = CopyOnWriteArrayList<Book>()

    private val mListenerList = RemoteCallbackList<IOnNewBookArriveListener>()

    private val mBinder = object : IBookManager.Stub() {
        override fun getBookList(a): MutableList<Book> {
            return mBookList
        }

        override fun addBook(book: Book?). {
            mBookList.add(book)
        }

        override fun registerListener(listener: IOnNewBookArriveListener?). {
            mListenerList.register(listener)
            val n = mListenerList.beginBroadcast()
            Log.d(TAG, "registerListener  $n")
            mListenerList.finishBroadcast()
        }

        override fun unregisterListener(listener: IOnNewBookArriveListener?). {
            val success = mListenerList.unregister(listener)
            if (success) {
                Log.d(TAG, "unregister success.")}else {
                Log.d(TAG, "not found, can not unregister.")}// beginBroadcast() and finishBroadcast() need to be paired
            val N = mListenerList.beginBroadcast()
            mListenerList.finishBroadcast()
            Log.d(TAG, "unregisterListener, current size:$N")}}override fun onCreate(a) {
        super.onCreate()
        mBookList.add(Book(1."A"))
        mBookList.add(Book(1."B"))
        Thread(ServiceWorker()).start()
    }

    override fun onDestroy(a) {
        super.onDestroy()
        mIsServiceDestroyed.set(true)}override fun onBind(intent: Intent?).: IBinder? {
        return mBinder
    }

    private fun onNewBookArrived(book: Book) {
        mBookList.add(book)
        val N = mListenerList.beginBroadcast()
        for (i in 0 until N) {
            vall = mListenerList.getBroadcastItem(i) l? .let { l.onNewBookArrived(book) } } mListenerList.finishBroadcast() }inner class ServiceWorker : Runnable {

        override fun run(a) {
            while(! mIsServiceDestroyed.get()) {
                try {
                    Thread.sleep(5000)}catch (e: InterruptedException) {
                    e.printStackTrace()
                }
                val bookId = mBookList.size + 1
                val newBook = Book(bookId, "new book#$bookId")
                try {
                    onNewBookArrived(newBook)
                } catch (e: RemoteException) {
                    e.printStackTrace()
                }
            }
        }

    }
}
Copy the code

Formation Statement

<activity
    android:name=".aidl.BookManagerActivity"
    android:exported="false" />
<service
    android:name=".aidl.BookManagerService"
    android:process=":remote" />
Copy the code

A simple example of using ContentProvider to communicate

This example stores data in SQLite, so DbOpenHelper is used

class DbOpenHelper @JvmOverloads constructor(
    val context: Context,
    name: String = DB_NAME,
    factory: SQLiteDatabase.CursorFactory? = null,
    version: Int = DB_VERSION,
    errorHandler: DatabaseErrorHandler? = null,
) : SQLiteOpenHelper(context, name, factory, version, errorHandler) {

    companion object {
        const val DB_NAME = "book_provider.db"
        const val BOOK_TABLE_NAME = "book"
        const val USER_TABLE_NAME = "user"
        const val DB_VERSION = 1

        const val CREATE_BOOK_TABLE =
            "CREATE TABLE IF NOT EXISTS $BOOK_TABLE_NAME (_id INTEGER PRIMARY KEY, name TEXT)"

        const val CREATE_USER_TABLE =
            "CREATE TABLE IF NOT EXISTS $USER_TABLE_NAME (_id INTEGER PRIMARY KEY, name TEXT)"
    }

    override fun onCreate(db: SQLiteDatabase?).{ db? .execSQL(CREATE_BOOK_TABLE) db? .execSQL(CREATE_USER_TABLE) }override fun onUpgrade(db: SQLiteDatabase? , oldVersion:Int, newVersion: Int){}}Copy the code

The Provider of

class BookProvider : ContentProvider() {

    companion object {
        const val TAG = "BookProvider"

        const val AUTHORITY = "com.hyh.book.provider"

        val BOOK_CONTENT_URI = Uri.parse("content://$AUTHORITY/book")
        val USER_CONTENT_URI = Uri.parse("content://$AUTHORITY/user")

        const val BOOK_URI_CODE = 0
        const val USER_URI_CODE = 1

        val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH)
    }

    init {
        // Associate code with uri
        sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE)
        sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE)
    }


    private lateinit var mDb: SQLiteDatabase

    fun getTableName(uri: Uri): String {
        return when (sUriMatcher.match(uri)) {
            BOOK_URI_CODE -> {
                DbOpenHelper.BOOK_TABLE_NAME
            }
            USER_URI_CODE -> {
                DbOpenHelper.USER_TABLE_NAME
            }
            else- > {""}}}override fun onCreate(a): Boolean {
        Log.d(TAG, "onCreate current Thread ${Thread.currentThread().name}") context? .let { mDb = DbOpenHelper(it).writableDatabasereturn true
        }
        return false
    }

    override fun query(
        uri: Uri,
        projection: Array<out String>? , selection:String? , selectionArgs:Array<out String>? , sortOrder:String?,
    ): Cursor? {
        Log.d(TAG, "query current Thread ${Thread.currentThread().name}")
        valtable = getTableName(uri) table? .let {return mDb.query(table, projection, selection, selectionArgs, null.null, sortOrder, null)}return null
    }

    override fun getType(uri: Uri): String? {
        Log.d(TAG, "getType current Thread ${Thread.currentThread().name}")
        return null
    }

    override fun insert(uri: Uri, values: ContentValues?).: Uri? {
        Log.d(TAG, "insert current Thread ${Thread.currentThread().name}")
        val table =  getTableName(uri)
        mDb.insert(table, null, values)
        // Notification updatecontext? .contentResolver? .notifyChange(uri,null)
        return null
    }

    override fun delete(uri: Uri, selection: String? , selectionArgs:Array<out String>?: Int {
        Log.d(TAG, "delete current Thread ${Thread.currentThread().name}")
        getTableName(uri).let { 
            val count = mDb.delete(it, selection, selectionArgs)
            if (count > 0) { context? .contentResolver? .notifyChange(uri,null)}return count
        }
    }

    override fun update(
        uri: Uri,
        values: ContentValues? , selection:String? , selectionArgs:Array<out String>?,
    ): Int {
        Log.d(TAG, "update current Thread ${Thread.currentThread().name}")
        getTableName(uri).let { 
            val row = mDb.update(it, values, selection, selectionArgs)
            if (row > 0) { context? .contentResolver? .notifyChange(uri,null)}return row
        }
    }
}
Copy the code

Class call part

class ProviderActivity : AppCompatActivity() {

    companion object {
        const val TAG = "ProviderActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_provider)

        // The value is the value of authorities
        val bookUri = BookProvider.BOOK_CONTENT_URI
        val values = ContentValues()
        values.put("name"."AAA-BB")
        contentResolver.insert(bookUri, values)
        contentResolver.query(bookUri, arrayOf("_id"."name"), null.null.null)? .let { cursor ->while (cursor.moveToNext()) {
                val book = Book(cursor.getInt(0), cursor.getString(1))
                Log.d(TAG, "book = $book")
            }
            cursor.close()
        }
    }
}
Copy the code

Formation Statement

<activity android:name=".contentprovider.ProviderActivity" android:exported="false" /> <! - authorities unique identifier -- > < provider android: name = ". The contentprovider. BookProvider" android:authorities="com.hyh.book.provider" android:permission="com.hyh.PROVIDER" android:process=":provider" />Copy the code

A simple practical Socket for communication example client part

Interface needs:

  • A TextView: displays the message
  • An EditText: Enter the message you want to send
  • A Button: click and send a message
class TCPClientActivity : AppCompatActivity() {

    companion object {
        const val TAG = "TCPClientActivity"
        const val MSG_RECEIVE_NEW_MSG = 1
        const val MSG_SOCKET_CONNECTION = 2
    }

    private lateinit var mPrintWriter: PrintWriter
    private lateinit var mClientSocket: Socket

    private lateinit var mEtInput: EditText
    private lateinit var mTvShow: TextView
    private lateinit var mBtnSend: Button

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_tcpclient)

        mEtInput = findViewById(R.id.msg)
        mTvShow = findViewById(R.id.msg_container)

        // Start the service
        val intent = Intent(this, TCPServerService::class.java)
        startService(intent)
        
        GlobalScope.launch(Dispatchers.IO) {
            // Delay for a while, otherwise the first link failure will occur
            Thread.sleep(1000)
            connectTCPServer()
        }

        mBtnSend = findViewById(R.id.send)

        mBtnSend.setOnClickListener {
            GlobalScope.launch(Dispatchers.IO) {
                val msg = mEtInput.text.toString().trim()
                mPrintWriter.println(msg)
                withContext(Dispatchers.Main) {
                    mTvShow.text = "${mTvShow.text}\n client: $msg"
                }
                mEtInput.setText("")}}}private suspend fun connectTCPServer(a) {
        var socket: Socket? = null
        while (null == socket) {
            try {
                socket = Socket("127.0.0.1".8688)
                // This can also be done
// socket = Socket("localhost", 8688)
                mClientSocket = socket
                mPrintWriter = PrintWriter(BufferedWriter(OutputStreamWriter(mClientSocket.getOutputStream())),
                        true)
                withContext(Dispatchers.Main) {
                    mBtnSend.isEnabled = true
                }
                Log.d(TAG, "connected")}catch (e: Exception) {
                Thread.sleep(1000)
                e.printStackTrace()
            }
        }

        try {
            val br = BufferedReader(InputStreamReader(socket.getInputStream()))
            while(! isFinishing) {val msg = br.readLine()
                Log.d(TAG, "receive $msg")
                withContext(Dispatchers.Main) {
                    mTvShow.text = "${mTvShow.text}\nserver: $msg"
                }
            }
            Log.d(TAG, "quit...")
            mPrintWriter.close()
            br.close()
            socket.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun onDestroy(a) {
        if(! mClientSocket.isClosed) { mClientSocket.shutdownInput() mClientSocket.close() }super.onDestroy()
    }
}
Copy the code

Server part

class TCPServerService : Service() {

    companion object {
        const val TAG = "TCPServerService"
    }

    private var mIsServiceDestroy = false

    private val mDefinedMessage =
        arrayListOf("hello"."What's your name?"."hi"."nice to meet you")

    override fun onCreate(a) {
        Thread(TcpServer()).start()
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onDestroy(a) {
        mIsServiceDestroy = true
        super.onDestroy()
    }

    inner class TcpServer : Runnable {
        override fun run(a) {
            var serverSocket: ServerSocket? = null
            try {
                // Listen on port 8688
                serverSocket = ServerSocket(8688)
                Log.d(TAG, "power")}catch (e: IOException) {
                Log.d(TAG, "establish tcp server failed, port: 8688")
                e.printStackTrace()
                return
            }

            while(! mIsServiceDestroy) {try {
                    // Receive client requests
                    val client = serverSocket.accept()
                    Log.d(TAG, "accept")
                    Thread {
                        responseClient(client)
                    }.start()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }
    }

    private fun responseClient(client: Socket) {
        // Receive client information
        val ir = BufferedReader(InputStreamReader(client.getInputStream()))
        // Send messages to the client
        val out = PrintWriter(BufferedWriter(OutputStreamWriter(client.getOutputStream())), true)
        out.println("welcome~")
        while(! mIsServiceDestroy) {val str = ir.readLine()
            Log.d(TAG, "msg from client $str")
            if (null == str) {
                // Disconnect the client
                break
            }
            val i = Random.nextInt(0, mDefinedMessage.size - 1)
            val msg = mDefinedMessage[i]
            out.println(msg)
            Log.d(TAG, "msg send to client $msg")
        }
        Log.d(TAG, "client quit")
        out.close()
        ir.close()
        client.close()
    }
}
Copy the code

A statement

<activity
    android:name=".socket.TCPClientActivity"
    android:exported="false" />
<service
    android:name=".socket.TCPServerService"
    android:process=":socket" />
Copy the code