Android multi-process and cross-process communication methods

Preface

We often hear about threads and processes during development. Before talking about Android process multi-process, I plan to briefly sort out these two.

Understand what processes and threads are

  1. Process: An application that is running in the system. Once a program is run, it is a process, which is the smallest unit of resource allocation;
  2. Thread: The smallest unit of program execution, included in a process. A process can contain multiple threads.

Multiple processes in Android applications

1.dalivk virtual machine

The underlying task management and drivers of the Android system are based on the Linux system; an Android system is actually a Linux system. By entering the connected mobile phone through adb shell, you can see the file system of the Linux system.
Insert image description here

Like running a Java program, we know that the Linux system will start a Java virtual machine to run the Java program, and the Android system is a special Linux system. When an APP is started, the system will allocate a Linux process to the APP, and the There will be a dalivk virtual machine (also called DVM) instance in the process to run the APP, so the dalivk virtual machine is used to run the APP program

Different APPs run in different processes, corresponding to different dalivk virtual machines, and corresponding to different address spaces. Conversely, within an application, if a new process is opened, since the system will allocate an independent dalivk virtual machine when the new process is opened, the instances of different processes in the APP are independent of each other and do not affect each other.

  1. Each process has an independent dalivk virtual machine, corresponding to a separate memory space
  2. An application can have multiple processes, and there will be multiple dalivk virtual machines, corresponding to multiple memory spaces.
  3. A process can be accessed by multiple applications, and multiple applications can share the process

2.Linux system composition

In the Linux system, the virtual memory space (what I understand is the space for running software programs, which is a mapping of physical space) is only 4G; the highest 1GB (corresponding to virtual addresses 0xC0000000 to 0xFFFFFFFF) is called the kernel space, while the lower 3GB (corresponding to virtual addresses 0x00000000 to 0xBFFFFFFF) is called user space. Kernel space is the running space of the Linux kernel, and user space is the running space of applications. as the picture shows:
Insert image description here

  1. Kernel space: has access to protected memory space and has all permissions to access underlying hardware devices
  2. User space: the running space of the upper application and Native layer. User space cannot directly access it.
  3. Kernel space requires system calls to access the kernel space.

User space is not shared between different processes, but kernel space can be shared.

3.Three ways to define multi-process in Android

By default, when starting an APP, only one process is started, and the process name is the package name. So how to define multiple processes? Android provides a way to specify the process through "android:process" in the AndroidManifest file:

  1. Do not specify process: the default process, the process name is the package name
  2. Specify process, but start with ":": This process is a private process of the current APP and is not allowed to be accessed by other APPs.
  3. Specify process, but a string starting with a lowercase letter: The process is a global process, and other applications can set the same shareUID to share the process

4. Why introduce multi-process?

Why should an Android application introduce multiple processes? What are the application scenarios of multi-process?

Usually multiple processes need to be introduced in the following two situations:

  1. Since the Android system limits the maximum memory of each application, if an application needs more available memory, it needs to introduce multiple processes and let certain modules run in other processes to obtain more memory.
  2. Since different applications run in different processes, if data communication is required between two different applications

5. Cross-process communication

Since multi-process is introduced in Android, and the user space of the process is not shared, how do multiple processes communicate with each other?

This multi-process communication is also called IPC (Inter Process Communication)

IPC is not unique to Android systems. Cross-process communication exists in Linux systems. Common IPC methods in Linux systems are:

  1. Pipe: Create a shared file in memory and use the shared file to transfer information. The shared file is not a file system and only exists in memory; it can only flow in one direction.
  2. Signal: Asynchronous communication. Signals interact between user space and kernel space. The kernel can use signals to notify user space processes of what system events have occurred. Not suitable for signal exchange, suitable for process interrupt control;
  3. Semaphore: Controls access to shared resources by multiple processes. It is mainly a means of synchronization between processes and between different threads of the same process;
  4. Message Queue: Stored in memory and identified by a message pair identifier, allowing one or more processes to read and write messages to it. Information will be copied twice and is not suitable for frequent or large-volume communications.
  5. Shared Memory Shared Memory: Directly read and write a memory space of the kernel. No data copy required
  6. Socket: Inter-process communication between different machines.

Except for Socket, everything else is implemented based on the Binder mechanism.

6. Problems caused by multiple processes

It is also mentioned in the process structure that user space is not shared, and each process corresponds to a separate system stack area, static area, etc. So many processes also introduce some problems:

  • Each process maintains its own separate static member variables and singletons
  • Each process has a separate process lock
  • The reliability of SharedPreferences has decreased and concurrent writing is not supported.
  • For multiple processes of a single APP, multiple Applications will be created, and each process will have its own Application object.

Okay, now we have a basic understanding of processes and threads, and we also know the commonly used cross-process communication methods, including several IPC methods commonly used in Android. Next, we will make a list of several IPC methods commonly used in Android. Deep understanding.

Get to know IPC

InterProcess Communication (InterProcess Communication abbreviated as IPC) refers to the dissemination or exchange of information between different processes. A process is a running activity of a program with certain independent functions on a certain data collection. It is the basic unit of dynamic execution of the operating system.

IPC is not unique to Android, any operating system needs to have a corresponding IPC mechanism. In the Android system, a process corresponds to a virtual machine instance. Different virtual machines have different address spaces in memory allocation. Therefore, only in a multi-process environment do you need to consider using IPC for communication. An application in Android can be one process or can be configured as multiple processes. Each process runs in its own independent space.

There are two typical scenarios in Android: The first is that the application itself needs to open multiple processes, such as a multi-module application. Since the system sets a maximum memory limit for the application, different modules are placed in order to obtain more memory space. in different threads. Another situation is to obtain data from other applications. A typical case is to obtain address books and text messages.

Multi-process mode in Android

The Android default process runs in the process with the default package name. Unless otherwise specified, all components run in the default process. You can modify the Android default process name by modifying the AndroidManifest file and adding the android:process attribute under the application tag:

<application
    android:name=".MainApplication"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:process="com.test.process"
    android:theme="@style/Theme.Sunflower">
   
</application>

In Android, there is only one way to use multi-process by setting the android:process attribute to the four major components (Activity Service Broadcast ContentProvider).

<service
    android:name=".service.TestService"
    android:process=":services" />

The above code specifies the process attribute for TestService and adds a new process to the application. When TestService starts, the system creates a separate process for it. use

adb shell ps | grep [包名] 

View process information.

Pay attention to the XML code above. There are two ways to set the process attribute. One is as shown above, starting with ":" followed by the process name, and the other is the complete process name, such as android:process="com.packages. name.services". The former will add the package name ("com.packages.name:services") before the process name, and stipulates that the process is a private process of the current application, and components of other applications cannot use the same process with it. In the latter, whatever you write will be the process name, and it is a global process. Other applications can also use this process.

Although the method of starting multiple processes is very simple, this seemingly simple operation will cause huge problems if it is not done deeply:

  1. Problem 1: Multiple processes will cause the Application to be created multiple times: when a component needs to run in a new process, the actual creation process is equivalent to restarting the application again. When the application starts, the Application will inevitably be created. Components running in different processes not only belong to different virtual machines, but also have different applications.

  2. Problem 2: Multiple processes will cause static members and singletons to be completely invalid: because different processes are assigned independent and different virtual machines, they have different address spaces in memory allocation. This will result in multiple copies of a class, and their modifications will not affect each other.

  3. Question 3: In multi-process mode, the thread synchronization mechanism will also fail: because the threads of different processes do not belong to the same memory, then whether it is an object lock or a class lock, the lock itself is not the same.

  4. Question 4: The risk of SharedPreferences will increase in multi-process mode: the bottom layer of SharedPreferences is implemented through file reading and writing, and concurrent operations may cause problems.

In short: if components in different processes use memory to communicate, there will be hidden dangers or direct failure. This is the main impact of multi-processing.

Inter-process communication (IPC) in Android

Next, we will explain how to communicate between multiple processes.

1: Use Bundle

In Android development, when we start Activity, Service and Receiver through Intent, we can pass parameters through Bundle. It implements the Parcelable interface and saves data in the form of key-value pairs. It can be thought of as a container that supports basic data types (string, int, boolean, byte, float, long, double) and their corresponding data. When an object or array of objects needs to be passed, the object being passed must implement the Serialiable or Parcelable interface.

Next, let's look at how to use Bundle to achieve cross-process communication through an Activity and a Service.

First, let both run in different processes:

<activity 
    android:name=".process.TestActivity"/>
<service
    android:name=".process.MessengerService"
    android:process=":services" />

Just specify the running thread of the Service in the configuration file.

When binding services to TestActivity through bindService(Intent, ServiceConnection,int), add bundle for intent:

val intent = Intent(this, MessengerService::class.java)
val bundle = Bundle()
bundle.putString("name", "Butler")
bundle.putInt("age", 28)
intent.putExtra("message", bundle)
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)

The onBind method in service accepts data:

override fun onBind(intent: Intent?): IBinder? {
    
    
    intent?.getBundleExtra("message")?.apply {
    
    
        Log.e("Name is:", "${
      
      this.getString("name")}")
        Log.e("age is:", "${
      
      this.getInt("age") }")
    }
    return mMessenger.binder
}

The running results are as follows:
Insert image description here
The method of use is very simple, no different from how we usually use Bundle.

2: Use file sharing

By sharing files, different processes can exchange data by reading and writing files. In Android, there are no special requirements for concurrent reading and writing of files. Although this may cause problems, it can still help us pass data between different processes. We create a new Activity (FileActivity) and specify its process android:process=":file"

Write files in TestActivity and implement jumps:

findViewById<Button>(R.id.file).setOnClickListener {
    
    
    val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    coroutineScope.launch {
    
    
        val message = "name:Butler,age:28"
        try {
    
    
            val fileOutputStream = openFileOutput(FileName, MODE_PRIVATE)
            fileOutputStream.write(message.toByteArray())
            fileOutputStream.close()
        } catch (e: Exception) {
    
    
            print(e.message)
        } finally {
    
    
            startActivity(Intent(this@TestActivity, FileActivity::class.java))
        }
    }
}

Read file content in FileActivity:

coroutineScope.launch {
    
    
    try {
    
    
        val fileInputStream = openFileInput(FileName)
        var n = 0
        val sBuffer = StringBuffer()
        while (n != -1) {
    
    
            n = fileInputStream.read()
            val by = n.toChar()
            sBuffer.append(by)
        }
        Log.e("message:","$sBuffer")
    } catch (e:Exception){
    
    
        print(e.message)
    } finally {
    
    

    }
}

The running results are as follows:
Insert image description here

Note⚠️: Do not use SharedPreferences for cross-process communication. In principle, it does not support multi-process. Although it is essentially a file, it will load the cache in the memory when the application is running. However, memory cannot be shared between processes. The SharedPreferences operated by each process will be a separate instance, which will lead to security issues. problem, this problem can only be solved through other communication methods between multiple processes or by using SharedPreferences while ensuring that SharedPreferences data will not be operated at the same time.

3:Use ContentProvider

ContentProvider provides an application with the ability to manage data stored by itself and other applications, and provides a way to share this data with other applications. It encapsulates data and provides mechanisms for defining data security. Regardless of whether you need to share data with other applications, you can use the ContentProvider to access this data, although it is not necessary to use it when there is no need to share data with other applications. The system is preset with many ContentProviders, such as call records, address books, messages, etc. You only need to get this information through ContentProvider. ContentProvider presents data to external applications in the form of one or more tables, which are similar to tables in relational databases. A row represents an instance of a type of data collected by the provider, and each column in the row represents a single piece of data collected for an instance.

There are usually two typical usage scenarios: one is to access existing ContentProvider in other applications by implementing code to obtain data; the other is to create a new ContentProvider to share data with other applications.

Below we use the simplest example to demonstrate the use of it for cross-process communication. For the convenience of demonstration, we do it within the same application:

First create a ContentProvider. The code is very simple, it is a data persistence using Room, and only performs the logical processing of insert and query.

class UserProvider : ContentProvider() {
    
    

    private lateinit var appDatabase: AppDatabase
    private var userDao: UserDao? = null

    override fun onCreate(): Boolean {
    
    
        appDatabase =
            Room.databaseBuilder(context!!, AppDatabase::class.java, "database-provider").build()
        userDao = appDatabase.userDao()
        return true
    }

    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
    
    

        return userDao?.getAll()
    }

    override fun getType(uri: Uri): String? {
    
    

        return ""

    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
    
    
        val user = User(
            System.currentTimeMillis().toInt(),
            values?.getAsString("name"),
            values?.getAsInteger("age")
        )
        Log.e("-------${
      
      user.firstName}", "------------${
      
      user.age}")
        userDao?.insertAll(user)
        return uri
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
    
    
        //不展示
        return 0
    }

    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<out String>?
    ): Int {
    
    
        //不展示
        return 0
    }
}

Next, configure it in the configuration file and specify the process in which it is located.

<provider
    android:name=".provider.UserProvider"
    android:authorities="com.hirzy.test.provider"
    android:permission="com.karl.PROVIDER"
    android:process=":provider" />

Then create Activity (ProviderActivity) and let it run in the default process of the application

class ProviderActivity : AppCompatActivity() {
    
    
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_provider)
        val textName = findViewById<EditText>(R.id.et_name)
        val textAge = findViewById<EditText>(R.id.et_age)
        val textView = findViewById<TextView>(R.id.tv)
        findViewById<Button>(R.id.btn_save).setOnClickListener(){
    
    
            if (textName.text != null && textAge.text != null) {
    
    
                val uri = Uri.parse("content://com.hirzy.test.provider")
                val contentValue = ContentValues()
                contentValue.put("name", "${
      
      textName.text}")
                contentValue.put("age", textAge.text.toString().toInt())
                contentResolver.insert(uri, contentValue)
            }
        }
        findViewById<Button>(R.id.btn_find).setOnClickListener(){
    
    
            textView.text = ""
            val uri = Uri.parse("content://com.hirzy.test.provider")

            val query = contentResolver.query(uri, null, null, null)
            var text = ""
            while (query?.moveToNext()!!){
    
    
                text += "姓名:${
      
      query.getString(1)}  年龄:${
      
      query.getInt(2)}\n"
            }
            textView.text = text

        }
    }
}

The running effect is as follows:
Insert image description here

It can be seen that the Activity and Provider in two different processes have successfully implemented data communication. However, due to design reasons, ContentProvider only supports addition, deletion, modification and query, and is more like a cross-process database.

4: Use Messenger

Messenger is a cross-process communication class through which messages can be delivered in different processes. The bottom layer is implemented through AIDL, which can be understood as a simple solution provided by the official to prevent us from using AIDL too cumbersomely.

It is very simple to use. First create the server Service (MessengerService) and set the process in which it is located.

android:process=":services":
class MessengerService : Service() {
    
    

   private val mHandlerThread: HandlerThread = HandlerThread("服务端")
   private lateinit var mMessenger: Messenger
   private lateinit var mHandler: Handler

   override fun onCreate() {
    
    
       super.onCreate()
       mHandlerThread.start()
       mHandler = object : Handler(mHandlerThread.looper) {
    
    
           override fun handleMessage(msg: Message) {
    
    
               if (msg.what == 0) {
    
    
                   val obtain = Message.obtain(msg)
                   obtain.what  = 1
                   obtain.arg2  = 2*obtain.arg1
                   Thread.sleep(2000L)
                   obtain.replyTo.send(obtain)
                   return
               }
               super.handleMessage(msg)
           }
       }
       mMessenger = Messenger(mHandler)
   }
}

Accepts the Int value from the client, delays it for two seconds and returns its multiple.

Next, implement the client Activity (MessengerActivity) in the application default process:

class MessengerActivity : AppCompatActivity() {
    
    
   

    private lateinit var mServiceMessenger: Messenger
    private val mServiceConnection = object : ServiceConnection {
    
    
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
    
    
            mServiceMessenger = Messenger(service)
        }

        override fun onServiceDisconnected(name: ComponentName?) {
    
    

        }
    }

    private val handler: Handler = object : Handler(Looper.getMainLooper()) {
    
    
        override fun handleMessage(msg: Message) {
    
    
            if (msg.what == 1) {
    
    
                Log.e("${
      
      currentTime()}--客户端收到的消息:", "${
      
      msg.arg2}")
            }
            super.handleMessage(msg)
        }
    }

    private val mClientMessenger: Messenger = Messenger(handler)

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        val intent = Intent(this, MessengerService::class.java)
        val bundle = Bundle()
        bundle.putString("name", "Butler")
        bundle.putInt("age", 28)
        intent.putExtra("message", bundle)
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
        setContentView(R.layout.activity_main_messenger)
        findViewById<Button>(R.id.Messenger).setOnClickListener {
    
    
            val nextInt = Random().nextInt(100)
            Log.e("${
      
      currentTime()}--客户端发送的消息:", "$nextInt")
            val message = Message.obtain(handler, 0, nextInt, 0)
            message.replyTo = mClientMessenger
            mServiceMessenger.send(message)
        }

        findViewById<Button>(R.id.file).setOnClickListener {
    
    
            val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
            coroutineScope.launch {
    
    
                val message = "name:Butler,age:28"
                try {
    
    
                    val fileOutputStream = openFileOutput(FileName, MODE_PRIVATE)
                    fileOutputStream.write(message.toByteArray())
                    fileOutputStream.close()
                } catch (e: Exception) {
    
    
                    print(e.message)
                } finally {
    
    
                    startActivity(Intent(this@MainMessengerActivity, FileActivity::class.java))
                }
            }
        }
    }


    override fun onDestroy() {
    
    
        unbindService(mServiceConnection)
        super.onDestroy()
    }

    fun currentTime(): String {
    
    
        val formatter = SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss")
        return formatter.format(Date(System.currentTimeMillis()))
    }
}

Activity is bound to MessengerService when it starts. Click the button to send numbers to services in different processes and accept new return values.

Messenger sends a Message to another process through a Handler, realizing inter-process communication. The bottom layer still uses the Binder mechanism, which is essentially based on AIDL.

The content of Messenger’s AIDL file (IMessenger) is as follows:

/* //device/java/android/android/app/IActivityPendingResult.aidl
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/

package android.os;

import android.os.Message;

/** @hide */
oneway interface IMessenger {
    void send(in Message msg);
}

It is not difficult to find that its function is to send Messages across processes. Its general process is as follows:
Insert image description here

However, Messenger also has obvious shortcomings: First, the server processes messages in a serial manner, which is not suitable for a large number of concurrent requests. Secondly, its main function is to pass messages and is not suitable for calling methods across processes.

5: Use AIDL

From the above, we know that Messenger implements cross-process communication, which is essentially based on AIDL. In this section, we use AIDL to implement a Messenger ourselves to demonstrate the use of AIDL and to allow users to learn the working principle of Messenger in order to simplify the code. We do not use Message directly as the data we pass, but use a Bean as the data we pass. Its core code is as follows:

package com.hirzy.test.aidl

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

class Bean: Parcelable {
    
    
    var name: String? = null
    var age = 0

    constructor(parcel: Parcel)  {
    
    
        name = parcel.readString()
        age = parcel.readInt()
    }

}

Note⚠️: In AIDL, not all data types can be used. It supports the following data types:

  • Basic data types
  • ArrayList, and the elements inside must be data types supported by AIDL
  • HashMap, and the elements inside must be data types supported by AIDL
  • An object that implements the Parcelable interface
  • AIDL interface itself

So our Bean class must implement the Parcelable interface. In the same way, the Message passed in Messenger in the previous section also implements the Parcelable interface, and the source code is as follows:

/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.os;

import android.compat.annotation.UnsupportedAppUsage;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.VisibleForTesting;

/**
 *
 * Defines a message containing a description and arbitrary data object that can be
 * sent to a {@link Handler}.  This object contains two extra int fields and an
 * extra object field that allow you to not do allocations in many cases.
 *
 * <p class="note">While the constructor of Message is public, the best way to get
 * one of these is to call {@link #obtain Message.obtain()} or one of the
 * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
 * them from a pool of recycled objects.</p>
 */
public final class Message implements Parcelable {
    
    
    /**
     * User-defined message code so that the recipient can identify
     * what this message is about. Each {@link Handler} has its own name-space
     * for message codes, so you do not need to worry about yours conflicting
     * with other handlers.
     */
    public int what;

    /**
     * arg1 and arg2 are lower-cost alternatives to using
     * {@link #setData(Bundle) setData()} if you only need to store a
     * few integer values.
     */
    public int arg1;

    /**
     * arg1 and arg2 are lower-cost alternatives to using
     * {@link #setData(Bundle) setData()} if you only need to store a
     * few integer values.
     */
    public int arg2;

    /**
     * An arbitrary object to send to the recipient.  When using
     * {@link Messenger} to send the message across processes this can only
     * be non-null if it contains a Parcelable of a framework class (not one
     * implemented by the application).   For other data transfer use
     * {@link #setData}.
     *
     * <p>Note that Parcelable objects here are not supported prior to
     * the {@link android.os.Build.VERSION_CODES#FROYO} release.
     */
    public Object obj;

    /**
     * Optional Messenger where replies to this message can be sent.  The
     * semantics of exactly how this is used are up to the sender and
     * receiver.
     */
    public Messenger replyTo;

    /**
     * Indicates that the uid is not set;
     *
     * @hide Only for use within the system server.
     */
    public static final int UID_NONE = -1;

    /**
     * Optional field indicating the uid that sent the message.  This is
     * only valid for messages posted by a {@link Messenger}; otherwise,
     * it will be -1.
     */
    public int sendingUid = UID_NONE;

    /**
     * Optional field indicating the uid that caused this message to be enqueued.
     *
     * @hide Only for use within the system server.
     */
    public int workSourceUid = UID_NONE;

    /** If set message is in use.
     * This flag is set when the message is enqueued and remains set while it
     * is delivered and afterwards when it is recycled.  The flag is only cleared
     * when a new message is created or obtained since that is the only time that
     * applications are allowed to modify the contents of the message.
     *
     * It is an error to attempt to enqueue or recycle a message that is already in use.
     */
    /*package*/ static final int FLAG_IN_USE = 1 << 0;

    /** If set message is asynchronous */
    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;

    /** Flags to clear in the copyFrom method */
    /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;

    @UnsupportedAppUsage
    /*package*/ int flags;

    /**
     * The targeted delivery time of this message. The time-base is
     * {@link SystemClock#uptimeMillis}.
     * @hide Only for use within the tests.
     */
    @UnsupportedAppUsage
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public long when;

    /*package*/ Bundle data;

    @UnsupportedAppUsage
    /*package*/ Handler target;

    @UnsupportedAppUsage
    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    @UnsupportedAppUsage
    /*package*/ Message next;


    /** @hide */
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;

    private static final int MAX_POOL_SIZE = 50;

    private static boolean gCheckRecycle = true;

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
    
    
        synchronized (sPoolSync) {
    
    
            if (sPool != null) {
    
    
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

    /**
     * Same as {@link #obtain()}, but copies the values of an existing
     * message (including its target) into the new one.
     * @param orig Original message to copy.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Message orig) {
    
    
        Message m = obtain();
        m.what = orig.what;
        m.arg1 = orig.arg1;
        m.arg2 = orig.arg2;
        m.obj = orig.obj;
        m.replyTo = orig.replyTo;
        m.sendingUid = orig.sendingUid;
        m.workSourceUid = orig.workSourceUid;
        if (orig.data != null) {
    
    
            m.data = new Bundle(orig.data);
        }
        m.target = orig.target;
        m.callback = orig.callback;

        return m;
    }

    /**
     * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h) {
    
    
        Message m = obtain();
        m.target = h;

        return m;
    }

    /**
     * Same as {@link #obtain(Handler)}, but assigns a callback Runnable on
     * the Message that is returned.
     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
     * @param callback Runnable that will execute when the message is handled.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h, Runnable callback) {
    
    
        Message m = obtain();
        m.target = h;
        m.callback = callback;

        return m;
    }

    /**
     * Same as {@link #obtain()}, but sets the values for both <em>target</em> and
     * <em>what</em> members on the Message.
     * @param h  Value to assign to the <em>target</em> member.
     * @param what  Value to assign to the <em>what</em> member.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what) {
    
    
        Message m = obtain();
        m.target = h;
        m.what = what;

        return m;
    }

    /**
     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, and <em>obj</em>
     * members.
     * @param h  The <em>target</em> value to set.
     * @param what  The <em>what</em> value to set.
     * @param obj  The <em>object</em> method to set.
     * @return  A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what, Object obj) {
    
    
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.obj = obj;

        return m;
    }

    /**
     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,
     * <em>arg1</em>, and <em>arg2</em> members.
     *
     * @param h  The <em>target</em> value to set.
     * @param what  The <em>what</em> value to set.
     * @param arg1  The <em>arg1</em> value to set.
     * @param arg2  The <em>arg2</em> value to set.
     * @return  A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what, int arg1, int arg2) {
    
    
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;

        return m;
    }

    /**
     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,
     * <em>arg1</em>, <em>arg2</em>, and <em>obj</em> members.
     *
     * @param h  The <em>target</em> value to set.
     * @param what  The <em>what</em> value to set.
     * @param arg1  The <em>arg1</em> value to set.
     * @param arg2  The <em>arg2</em> value to set.
     * @param obj  The <em>obj</em> value to set.
     * @return  A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what,
            int arg1, int arg2, Object obj) {
    
    
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
        m.obj = obj;

        return m;
    }

    /** @hide */
    public static void updateCheckRecycle(int targetSdkVersion) {
    
    
        if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
    
    
            gCheckRecycle = false;
        }
    }

    /**
     * Return a Message instance to the global pool.
     * <p>
     * You MUST NOT touch the Message after calling this function because it has
     * effectively been freed.  It is an error to recycle a message that is currently
     * enqueued or that is in the process of being delivered to a Handler.
     * </p>
     */
    public void recycle() {
    
    
        if (isInUse()) {
    
    
            if (gCheckRecycle) {
    
    
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    @UnsupportedAppUsage
    void recycleUnchecked() {
    
    
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
    
    
            if (sPoolSize < MAX_POOL_SIZE) {
    
    
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

    /**
     * Make this message like o.  Performs a shallow copy of the data field.
     * Does not copy the linked list fields, nor the timestamp or
     * target/callback of the original message.
     */
    public void copyFrom(Message o) {
    
    
        this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;
        this.what = o.what;
        this.arg1 = o.arg1;
        this.arg2 = o.arg2;
        this.obj = o.obj;
        this.replyTo = o.replyTo;
        this.sendingUid = o.sendingUid;
        this.workSourceUid = o.workSourceUid;

        if (o.data != null) {
    
    
            this.data = (Bundle) o.data.clone();
        } else {
    
    
            this.data = null;
        }
    }

    /**
     * Return the targeted delivery time of this message, in milliseconds.
     */
    public long getWhen() {
    
    
        return when;
    }

    public void setTarget(Handler target) {
    
    
        this.target = target;
    }

    /**
     * Retrieve the {@link android.os.Handler Handler} implementation that
     * will receive this message. The object must implement
     * {@link android.os.Handler#handleMessage(android.os.Message)
     * Handler.handleMessage()}. Each Handler has its own name-space for
     * message codes, so you do not need to
     * worry about yours conflicting with other handlers.
     */
    public Handler getTarget() {
    
    
        return target;
    }

    /**
     * Retrieve callback object that will execute when this message is handled.
     * This object must implement Runnable. This is called by
     * the <em>target</em> {@link Handler} that is receiving this Message to
     * dispatch it.  If
     * not set, the message will be dispatched to the receiving Handler's
     * {@link Handler#handleMessage(Message)}.
     */
    public Runnable getCallback() {
    
    
        return callback;
    }

    /** @hide */
    @UnsupportedAppUsage
    public Message setCallback(Runnable r) {
    
    
        callback = r;
        return this;
    }

    /**
     * Obtains a Bundle of arbitrary data associated with this
     * event, lazily creating it if necessary. Set this value by calling
     * {@link #setData(Bundle)}.  Note that when transferring data across
     * processes via {@link Messenger}, you will need to set your ClassLoader
     * on the Bundle via {@link Bundle#setClassLoader(ClassLoader)
     * Bundle.setClassLoader()} so that it can instantiate your objects when
     * you retrieve them.
     * @see #peekData()
     * @see #setData(Bundle)
     */
    public Bundle getData() {
    
    
        if (data == null) {
    
    
            data = new Bundle();
        }

        return data;
    }

    /**
     * Like getData(), but does not lazily create the Bundle.  A null
     * is returned if the Bundle does not already exist.  See
     * {@link #getData} for further information on this.
     * @see #getData()
     * @see #setData(Bundle)
     */
    public Bundle peekData() {
    
    
        return data;
    }

    /**
     * Sets a Bundle of arbitrary data values. Use arg1 and arg2 members
     * as a lower cost way to send a few simple integer values, if you can.
     * @see #getData()
     * @see #peekData()
     */
    public void setData(Bundle data) {
    
    
        this.data = data;
    }

    /**
     * Chainable setter for {@link #what}
     *
     * @hide
     */
    public Message setWhat(int what) {
    
    
        this.what = what;
        return this;
    }

    /**
     * Sends this Message to the Handler specified by {@link #getTarget}.
     * Throws a null pointer exception if this field has not been set.
     */
    public void sendToTarget() {
    
    
        target.sendMessage(this);
    }

    /**
     * Returns true if the message is asynchronous, meaning that it is not
     * subject to {@link Looper} synchronization barriers.
     *
     * @return True if the message is asynchronous.
     *
     * @see #setAsynchronous(boolean)
     */
    public boolean isAsynchronous() {
    
    
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }

    /**
     * Sets whether the message is asynchronous, meaning that it is not
     * subject to {@link Looper} synchronization barriers.
     * <p>
     * Certain operations, such as view invalidation, may introduce synchronization
     * barriers into the {@link Looper}'s message queue to prevent subsequent messages
     * from being delivered until some condition is met.  In the case of view invalidation,
     * messages which are posted after a call to {@link android.view.View#invalidate}
     * are suspended by means of a synchronization barrier until the next frame is
     * ready to be drawn.  The synchronization barrier ensures that the invalidation
     * request is completely handled before resuming.
     * </p><p>
     * Asynchronous messages are exempt from synchronization barriers.  They typically
     * represent interrupts, input events, and other signals that must be handled independently
     * even while other work has been suspended.
     * </p><p>
     * Note that asynchronous messages may be delivered out of order with respect to
     * synchronous messages although they are always delivered in order among themselves.
     * If the relative order of these messages matters then they probably should not be
     * asynchronous in the first place.  Use with caution.
     * </p>
     *
     * @param async True if the message is asynchronous.
     *
     * @see #isAsynchronous()
     */
    public void setAsynchronous(boolean async) {
    
    
        if (async) {
    
    
            flags |= FLAG_ASYNCHRONOUS;
        } else {
    
    
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

    /*package*/ boolean isInUse() {
    
    
        return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
    }

    @UnsupportedAppUsage
    /*package*/ void markInUse() {
    
    
        flags |= FLAG_IN_USE;
    }

    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    
    
    }

    @Override
    public String toString() {
    
    
        return toString(SystemClock.uptimeMillis());
    }

    @UnsupportedAppUsage
    String toString(long now) {
    
    
        StringBuilder b = new StringBuilder();
        b.append("{ when=");
        TimeUtils.formatDuration(when - now, b);

        if (target != null) {
    
    
            if (callback != null) {
    
    
                b.append(" callback=");
                b.append(callback.getClass().getName());
            } else {
    
    
                b.append(" what=");
                b.append(what);
            }

            if (arg1 != 0) {
    
    
                b.append(" arg1=");
                b.append(arg1);
            }

            if (arg2 != 0) {
    
    
                b.append(" arg2=");
                b.append(arg2);
            }

            if (obj != null) {
    
    
                b.append(" obj=");
                b.append(obj);
            }

            b.append(" target=");
            b.append(target.getClass().getName());
        } else {
    
    
            b.append(" barrier=");
            b.append(arg1);
        }

        b.append(" }");
        return b.toString();
    }

    void dumpDebug(ProtoOutputStream proto, long fieldId) {
    
    
        final long messageToken = proto.start(fieldId);
        proto.write(MessageProto.WHEN, when);

        if (target != null) {
    
    
            if (callback != null) {
    
    
                proto.write(MessageProto.CALLBACK, callback.getClass().getName());
            } else {
    
    
                proto.write(MessageProto.WHAT, what);
            }

            if (arg1 != 0) {
    
    
                proto.write(MessageProto.ARG1, arg1);
            }

            if (arg2 != 0) {
    
    
                proto.write(MessageProto.ARG2, arg2);
            }

            if (obj != null) {
    
    
                proto.write(MessageProto.OBJ, obj.toString());
            }

            proto.write(MessageProto.TARGET, target.getClass().getName());
        } else {
    
    
            proto.write(MessageProto.BARRIER, arg1);
        }

        proto.end(messageToken);
    }

    public static final @android.annotation.NonNull Parcelable.Creator<Message> CREATOR
            = new Parcelable.Creator<Message>() {
    
    
        public Message createFromParcel(Parcel source) {
    
    
            Message msg = Message.obtain();
            msg.readFromParcel(source);
            return msg;
        }

        public Message[] newArray(int size) {
    
    
            return new Message[size];
        }
    };

    public int describeContents() {
    
    
        return 0;
    }

    public void writeToParcel(Parcel dest, int flags) {
    
    
        if (callback != null) {
    
    
            throw new RuntimeException(
                "Can't marshal callbacks across processes.");
        }
        dest.writeInt(what);
        dest.writeInt(arg1);
        dest.writeInt(arg2);
        if (obj != null) {
    
    
            try {
    
    
                Parcelable p = (Parcelable)obj;
                dest.writeInt(1);
                dest.writeParcelable(p, flags);
            } catch (ClassCastException e) {
    
    
                throw new RuntimeException(
                    "Can't marshal non-Parcelable objects across processes.");
            }
        } else {
    
    
            dest.writeInt(0);
        }
        dest.writeLong(when);
        dest.writeBundle(data);
        Messenger.writeMessengerOrNullToParcel(replyTo, dest);
        dest.writeInt(sendingUid);
        dest.writeInt(workSourceUid);
    }

    private void readFromParcel(Parcel source) {
    
    
        what = source.readInt();
        arg1 = source.readInt();
        arg2 = source.readInt();
        if (source.readInt() != 0) {
    
    
            obj = source.readParcelable(getClass().getClassLoader(), java.lang.Object.class);
        }
        when = source.readLong();
        data = source.readBundle();
        replyTo = Messenger.readMessengerOrNullFromParcel(source);
        sendingUid = source.readInt();
        workSourceUid = source.readInt();
    }
}

Next create the AIDL file BeanAidl.aidl:

package com.hirezy.test.aidl;

parcelable Bean;
interface BeanAidl {
    Bean send(in Bean bean);
}

Note⚠️: Custom objects must be imported. In addition, in addition to basic data types in AIDL, other types of parameters must be marked with directions: in, out or inout:

  • in: Variables in the object can be passed from the client to the server, and modifications to the object by the server will not affect the client.
  • out: The variables in the object cannot be passed from the client to the server. What the server receives is a variable whose internal variables are all initial values, but the server's modification of the object can affect the client.
  • inout: Variables in the object can be passed to the server, and modifications by the service team can also affect the client.

Next create the server code AIdlService:

class AIdlService : Service() {
    
    

    override fun onBind(intent: Intent?): IBinder? {
    
    
        return iService.asBinder()
    }

    private val iService = object : BeanAidl.Stub() {
    
    
        override fun send(bean: Bean?): Bean {
    
    
            Thread.sleep(5000L)
            return bean
        }
    }
}

What we need to do is create an instance of BeanAidl.Stub (automatically generated code, explained later) and return it as IBinder. In the specific implementation of BeanAidl.Stub, we implement the send method in BeanAidl.aidl. When the client calls the send method through AIDL, the server's send method will eventually be called. and returns a bean object.

Next let’s take a look at the corresponding implementation in IMessenger. First of all, the send method defined in the aidl file has a specific implementation inHandler:

 private final class MessengerImpl extends IMessenger.Stub {
    
    
        public void send(Message msg) {
    
    
            msg.sendingUid = Binder.getCallingUid();
            Handler.this.sendMessage(msg);
        }
    }

It can be seen that it sends the client's message again using Handler. This is what we need to create above, and use handleMessage to process the messages sent by the client. So where does Handler come from, and how is it related to Messenger? Let’s look at the code of MessengerService.kt:

private val mHandlerThread: HandlerThread = HandlerThread("服务端")
private lateinit var mMessenger: Messenger
private lateinit var mHandler: Handler

override fun onCreate() {
    
    
    super.onCreate()
    mHandlerThread.start()
    mHandler = object : Handler(mHandlerThread.looper) {
    
    
        override fun handleMessage(msg: Message) {
    
    
            if (msg.what == 0) {
    
    
                val obtain = Message.obtain(msg)
                obtain.what  = 1
                obtain.arg2  = 2*obtain.arg1
                Thread.sleep(2000L)
                obtain.replyTo.send(obtain)
                return
            }
            super.handleMessage(msg)
        }
    }
    mMessenger = Messenger(mHandler)
}

It is not difficult to see that when creating a Messenger instance, a Handler object must be passed in. Because it has only one constructor:

private final IMessenger mTarget;

public Messenger(Handler target) {
    
    
    mTarget = target.getIMessenger();
}

The Handler's getIMessenger method is implemented as follows:

@UnsupportedAppUsage
final IMessenger getIMessenger() {
    
    
    synchronized (mQueue) {
    
    
        if (mMessenger != null) {
    
    
            return mMessenger;
        }
        mMessenger = new MessengerImpl();
        return mMessenger;
    }
}

Note that the MessengerImpl inside is exactly where the AIDL send method is implemented above. It's the inner class in the Handler. And in the onBinder method of services

public IBinder getBinder() {
    
    
    return mTarget.asBinder();
}

You can clearly see that the asBinder method is called.

Note that in the above, our server code will delay returning information to the client for five seconds. At this time, we will find that the application page is blocked and cannot respond to any events. Moreover, ANR exceptions are sometimes triggered. This is because: the method called by the client runs in the Binder thread pool of the server. At this time, the client thread will hang. If the server method is time-consuming to execute, it will cause the client thread to be blocked. If it is a UI If it is a thread, an ANR exception will be triggered. Therefore, if you clearly know that the server will perform time-consuming operations, you must access the server code in a child thread. However, the server is different. It itself runs in the Binder thread pool, so there is no need to start a new sub-thread. The only thing that needs attention is that thread safety needs to be ensured when multiple clients communicate with the server.

AIDL also supports interface callback monitoring. First create a new AILD (ChangeListener)

package com.hirezy.test.aidl;

parcelable Bean;

interface ChangeListener {
    
    
    void onChangeListener(in Bean bean);
}

Then add the following method in BeanAidl:

void sendBean(in Bean bean);
void setListener(in ChangeListener listener);
void removeListener(in ChangeListener listener);

Modify AIdlService:

private var listener: ChangeListener? = null
private val iService = object : BeanAidl.Stub() {
    
    
    override fun send(bean: Bean?): Bean {
    
    
        bean!!.age = 2 * bean!!.age
        Thread.sleep(5000L)

        listener?.onChangeListener(bean)

        return bean
    }

    override fun sendBean(bean: Bean?) {
    
    
        listener?.onChangeListener(bean)
    }

    override fun setListener(on: ChangeListener?) {
    
    
        listener = on
    }

    override fun removeListener(on: ChangeListener?) {
    
    
        listener = null
    }

}

Add the following code to the client AidlActivity to implement monitoring:

private val listener = object : ChangeListener.Stub() {
    
    
    override fun onChangeListener(bean: Bean) {
    
    
        textView.text = bean.toString()
        Log.i("bean","bean info is:${
      
      bean.toString()}")
    }
}

How AIDL works

Android Interface Definition Language is a tool that users can use to abstract IPC. Binder is still used for specific communication. Taking the example of an interface specified in an .aidl file, various build systems use the aidl binary to construct C++ or Java bindings for using the interface across processes.

AIDL can be used between any process in Android: between platform components or between applications. However, AIDL should never be used as an application's API.

AIDL is called using the Binder kernel driver. When a call is made, the system packs the method identifier and all objects into some buffer and copies it to the remote process, where there is a Binder thread waiting to read the data. After the Binder thread receives the data for a transaction, the thread looks for the native stub object in the local process, which then decompresses the data and calls the local interface object. This local interface object is exactly what the server process created and registered. When the call is made in the same process and the same backend, there is no proxy object, so the call can be made directly without performing any packaging or unpacking operations.

Binder is a very deep topic because it is very complex. Here we will not discuss its underlying principles too much. In Android development, Binder is mainly used in Service, and Binder in ordinary Service does not involve inter-process communication. The main thing here is to talk about cross-process. Among AIDL and Messenger, the bottom layer of Messenger is actually AIDL, so use AIDL to explore the working mechanism of Binder.

Let’s create a new simplest AIDL (TestAidl) file

package com.hirezy.aidl;

interface TestAidl {
    
    
   void fun(int val);
   void func(int val);
}

At this time, the SDK will automatically generate the Binder class generated by AIDL for us, which is located in the build/generated directory:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.hirezy.aidl;
public interface TestAidl extends android.os.IInterface
{
    
    
  /** Default implementation for TestAidl. */
  public static class Default implements com.hirezy.aidl.TestAidl
  {
    
    
    @Override public void fun(int val) throws android.os.RemoteException
    {
    
    
    }
    @Override public void func(int val) throws android.os.RemoteException
    {
    
    
    }
    @Override
    public android.os.IBinder asBinder() {
    
    
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.hirezy.aidl.TestAidl
  {
    
    
    private static final java.lang.String DESCRIPTOR = "com.hirezy.aidl.TestAidl";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
    
    
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.hirezy.aidl.TestAidl interface,
     * generating a proxy if needed.
     */
    public static com.hirezy.aidl.TestAidl asInterface(android.os.IBinder obj)
    {
    
    
      if ((obj==null)) {
    
    
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.hirezy.aidl.TestAidl))) {
    
    
        return ((com.hirezy.aidl.TestAidl)iin);
      }
      return new com.hirezy.aidl.TestAidl.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
    
    
      return this;
    }
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
    
    
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
    
    
        case INTERFACE_TRANSACTION:
        {
    
    
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_fun:
        {
    
    
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          this.fun(_arg0);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_func:
        {
    
    
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          this.func(_arg0);
          reply.writeNoException();
          return true;
        }
        default:
        {
    
    
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.hirezy.aidl.TestAidl
    {
    
    
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
    
    
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
    
    
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
    
    
        return DESCRIPTOR;
      }
      @Override public void fun(int val) throws android.os.RemoteException
      {
    
    
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
    
    
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(val);
          boolean _status = mRemote.transact(Stub.TRANSACTION_fun, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
    
    
            getDefaultImpl().fun(val);
            return;
          }
          _reply.readException();
        }
        finally {
    
    
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public void func(int val) throws android.os.RemoteException
      {
    
    
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
    
    
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(val);
          boolean _status = mRemote.transact(Stub.TRANSACTION_func, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
    
    
            getDefaultImpl().func(val);
            return;
          }
          _reply.readException();
        }
        finally {
    
    
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.hirezy.aidl.TestAidl sDefaultImpl;
    }
    static final int TRANSACTION_fun = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_func = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.hirezy.aidl.TestAidl impl) {
    
    
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
    
    
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.hirezy.aidl.TestAidl getDefaultImpl() {
    
    
      return Stub.Proxy.sDefaultImpl;
    }
  }
  public void fun(int val) throws android.os.RemoteException;
  public void func(int val) throws android.os.RemoteException;
}

The above code is generated by the system based on DemoAidl.aidl. It is an interface that implements the IInterface interface. If you want to use the Binder mechanism to achieve cross-process communication, you must implement this interface. The main structure of this class is as follows:

First, declare two methods fun(int val) and func(int val), which are the two methods we defined in the AIDL file. At the same time, two static constants are declared:

static final int TRANSACTION_fun = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_func = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

Identify these two methods. The purpose is to identify which method is called in onTransact.

Next, an internal class Stub is defined, which inherits Binder and implements the DemoAidl interface. Obviously it is Binder. At the same time, there is also a proxy class Proxy in this internal class. When the client and server are in the same process, method calls will not go through the Transact process across processes. When not within a process, it will be completed through the internal proxy class. So how does it judge?

Let’s take a look at its asInterface code

public static com.hirezy.test.aidl.BeanAidl asInterface(android.os.IBinder obj)
{
    
    
  if ((obj==null)) {
    
    
    return null;
  }
  android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  if (((iin!=null)&&(iin instanceof com.karl.butler.aidl.BeanAidl))) {
    
    
    return ((com.hirezy.test.aidl.BeanAidl)iin);
  }
  return new com.hirezy.test.aidl.BeanAidl.Stub.Proxy(obj);
}

As you can see, it first determines whether there is a corresponding Binder object locally through queryLocalInterface. That is, whether there is one in the current thread. If there is, it means that the client and the server are in the same process. Then this method returns the Stub object itself of the server. Otherwise, it is in a different process and the Stub.proxy object is returned.

Next, let’s take a look at the onTransact method. It runs in the Binder thread pool on the server side. When the client initiates a cross-process request, the remote request will be encapsulated by the bottom layer of the system and handed over to this method for processing.

Finally, let's take a look at the fun and func methods in Proxy: The method runs on the client. When the method is called, the data is first prepared, then a remote request is initiated and the current thread is suspended. The unprovoked onTransact method is called through the underlying driver. After returning the result, the current thread continues execution.

The process is roughly as follows:
Insert image description here
In fact, AIDL can be understood as a specification stipulated by Binder. It is a tool used to automatically generate code to facilitate our development. We can write the code ourselves without using the AIDL file to complete the Binder.

Binder implementation principle

Binder is an inter-process communication mechanism based on the open source OpenBinder implementation.

  • Binder is an inter-process communication mechanism (mechanism) of CS architecture
  • A virtual physical device (driver)
  • At the application layer is a Java class that can initiate communication.

Binder is required when multi-process communication is used in the application. Since the running memory allocated by the virtual machine to each process is limited, LMK will also give priority to recycling processes that occupy more resources. So we can use multiple processes:

  • Circumvent memory limitations, such as memory-intensive galleries
  • Improve stability and use independent processes to maintain long connections
  • Avoid memory leaks, independent webView process
  • Avoid risks and use independent processes to run high-risk functions to avoid affecting the main business.

At the same time, Android applications are composed of four major components (Activity, Service, Broadcast Receiver and Content Provider). Sometimes these components run in the same process, sometimes in different processes. Communication between these processes relies on the Binder IPC mechanism. At the same time, AMS and PMS in android are implemented based on the Binder mechanism. Therefore, the component startup process, communication mechanism and AIDL are all inseparable from Binder.

First understand the memory division in the system. The operating system divides the memory into two blocks:

  • User space: where user program code runs, exclusive to the application
  • Kernel space: the space where kernel code runs, shared by all processes

To ensure security, they are isolated. Moreover, different processes are also isolated, memory is not shared, and data between different processes cannot be directly accessed.

Different processes rely on the kernel space as a bridge to communicate: by copying the data in the A process to the kernel space (copy from user), and then copying the data from the kernel space to the B process (copy to user), the A process and the B process are realized communication between. Essentially: data transfer between processes is completed through kernel memory space and copy assignment.

Insert image description here
Linux provides pipes (Handler uses the pipe mechanism + epoll), semaphores, message queues, shared memory (Fresco) and Socket Wait for the IPC mechanism. Compared with them, Binder has the following advantages:

  • Binder only requires one data copy, which is second only to shared memory in performance, while others require two copies.
  • Binder is based on C/S architecture. The memory sharing mechanism needs to manage and control concurrency and synchronization by itself. The stability and convenience are not as good as Binder, and all processes can access it, so the security is low.
  • Binder adds an identity by obtaining the PID in the kernel instead of generating it by the program itself, which is more secure.
  • Binder is a one-to-many relationship. A server can have multiple clients communicating with it, while pipelines only support one-to-one communication.

In Linux, the virtual memory addressing mode used is:

  1. The virtual memory address of user space is mapped to physical memory.
  2. Reading and writing virtual memory is actually reading and writing physical memory, and memory mapping is implemented through the system call mmap().

Binder uses the memory mapping method to create a layer of memory mapping between the kernel space and the data buffer area of ​​the receiver user space, which is equivalent to directly copying to the data buffer area of ​​the receiver user space, thus reducing one data copy. .

Insert image description here
Since I don’t know much about Binder, here is only a brief description of its basic concepts. If you want to learn more about the Binder mechanism, it is recommended to read:Android Binder Design and Implementation - Design Chapter, the introduction is very detailed.

Summarize

I learned the difference between processes and threads

  1. Process: An application that is running in the system. Once a program is run, it is a process, which is the smallest unit of resource allocation;
  2. Thread: The smallest unit of program execution, included in a process. A process can contain multiple threads.

Also know the commonly used Android IPC communication method

  1. Bundle: implements the Parcelable interface, commonly used in Activity, Service,
  2. Communication between BroadcastReceive
  3. File sharing: often used for exchanging data without concurrency and low real-time performance
  4. Messenger: low-concurrency one-to-many instant messaging. Messages sent by the Client are processed in a serial manner. Only data can be transmitted, but method calls cannot be made (RPC).
  5. ContentProvider: Stores and obtains data, and shares data between different programs. One-to-many data sharing
  6. AIDL
  7. Socket: Network data exchange

Note⚠️: Except for Socket, everything else is implemented based on the Binder mechanism.

Common multi-process communication IPC (Inter Process Communication) includes the following types:

  1. Pipe: Create a shared file in memory and use the shared file to transfer information. The shared file is not a file system and only exists in memory; it can only flow in one direction.
  2. Signal: Asynchronous communication. Signals interact between user space and kernel space. The kernel can use signals to notify user space processes of what system events have occurred. Not suitable for signal exchange, suitable for process interrupt control
  3. Semaphore: Controls access to shared resources by multiple processes. It is mainly a means of synchronization between processes and between different threads of the same process.
  4. Message Queue: Stored in memory and identified by a message pair identifier, allowing one or more processes to read and write messages to it. Information will be copied twice and is not suitable for frequent or large-volume communications.
  5. Shared Memory Shared Memory: Directly read and write a memory space of the kernel. No data copy required
  6. Socket: Inter-process communication between different machines.

The purpose of introducing multi-process in Android

  1. Since the Android system limits the maximum memory of each application, if an application needs more available memory, it needs to introduce multiple processes and let certain modules run in other processes to obtain more memory.
  2. Since different applications run in different processes, if data communication is required between two different applications

Guess you like

Origin blog.csdn.net/ljx1400052550/article/details/134967225