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 useMessenager
An 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