Introduction:

Deep understanding of IPC mechanism and multi-process development model in Android.

The main content

  • Introduction of Android IPC
  • Multi-process mode in Android
  • IPC basic concepts
  • IPC mode in Android
  • Binder connection pool
  • Choose the appropriate IPC method

The specific content

Introduction of Android IPC

IPC refers to inter-process Communication or inter-process Communication. It refers to the Process of data exchange between two processes. Thread is the smallest unit of CPU scheduling and a limited system resource. A process generally refers to a unit of execution, and on PCS and mobile devices it refers to a program or application. Processes and threads are contained and contained. A process can contain multiple threads. The simplest case is one thread per process, the main thread (for example, the Android UI thread). Any operating system needs an IPC mechanism. Like clipboards, pipes, and mail slots on Windows; Named pipes, shared content, semaphores, and more on Linux. The most distinctive form of interprocess communication in Android is binder, which also supports sockets. ContentProvider is the interprocess communication implemented at the bottom of Android.

In Android, IPC can be used in the following scenarios:

  • Some modules need to run in a separate process for special reasons.
  • Access to multiple memory Spaces through multiple processes.
  • The current application needs to obtain data from other applications.

Multi-process mode in Android

Enable the multi-process mode

There is only one way to use multithreading in Android: assign the Android: Process attribute to the four major components in their Manifest. The value of this property is the process name. This means that the process in which a thread resides cannot be specified at run time.

<activity android:name=".MainActivity"
            android:process=":remote"/>
Copy the code
<activity android:name=".MainActivity"
            android:process="com.example.c2.remote"/>
Copy the code

The difference between the two process naming methods:

  • “: remote”

The: indicates that the package name is appended to the current process name. The complete process name is com.example.c2.remote. This process belongs to the private process of the current application. Components of other applications cannot run in the same process with this process.

  • “Com. Example. C2. The remote”

That’s a whole way of naming it. This process is a global process and other applications can run in the same process with it through ShareUID.

Tips: use adb shell ps or adb shell ps | grep package name View the current process of information.

Multithreaded mode of operation mechanism

Android allocates an independent VM for each process. Different VMS have different address Spaces for memory allocation. As a result, multiple copies of objects of the same class are generated when different VMS access the same object. For example, changes to static variables made by activities of different processes have no effect on other processes. All four components running in different processes fail to share data between them via memory. Data cannot be shared between the four components without a middle tier.

Multiple processes cause the following problems:

  • Static members and singletons are completely invalidated.
  • The thread synchronization lock mechanism has completely failed.

Both of these are because different processes are not in the same memory space, and the lock object is not the same object.

  • The reliability of SharedPreferences decreases.

The SharedPreferences layer is implemented by reading/writing XML files, and concurrent reading/writing can result in certain data loss.

  • Application is created multiple times.

Since the system creates new processes and assigns independent virtual machines, this is actually the process of starting an application. In multi-process mode, components of different processes have independent virtual machines, applications, and memory space.

Multi-process is equivalent to two different applications using the SharedUID pattern.

There are many ways to implement cross-process:

  • Intent passes data.
  • Share files and SharedPreferences.
  • Binder-based Messenger and AIDL.
  • The Socket and so on.

IPC basic concepts

Serializable, Parcelable and Binder are introduced. The Serializable and Parcelable interfaces are used to serialize objects, and are used to transfer data through IntEnts and binders. Sometimes we need to persist objects to storage devices or transfer them over the network to other clients. We also need to Serializable for object persistence.

The Serializable interface

Serializable is a serialization interface (empty interface) provided by Java that provides standard serialization and deserialization operations for objects. Serialization requires only a class that implements the Serializable interface and declares a serialVersionUID.

private static final long serialVersionUID = 8711368828010083044L
Copy the code

SerialVersionUID can also be undeclared. If you do not manually specify the value of serialVersionUID, the system recalculates the hash value of the current class and updates the serialVersionUID if the current class changes during deserialization (for example, if some member variables are added or deleted). At this time, the serialVersionUID of the current class is inconsistent with the serialVersionUID of the serialized data, resulting in deserialization failure and crash of the program.

Static member variables belong to classes, not objects, and do not participate in the serialization process. Secondly, transient keyword marked member variables do not participate in the serialization process.

You can override the writeObject and readObject methods to change the system’s default serialization process.

Parcelable interface

Parcel internally wraps serializable data that can be transferred freely in Binder. The serialization process needs to realize the functions of serialization, deserialization and content description.

Serialization is done by the writeToParcel method and ultimately through the Parcel’s series of writer methods.

    @Override
    public void writeToParcel(Parcel out, int flags) {
    out.writeInt(code);
    out.writeString(name);
    }
Copy the code

The deserialization function is done by CREATOR, whose internals show how to create serialized objects and arrays through the Parcel’s series of read methods.

public static final Creator<Book> CREATOR = new Creator<Book>() {
    @Override
    public Book createFromParcel(Parcel in) {
        return new Book(in);
    } 
    @Override
    public Book[] newArray(int size) {
        return newBook[size]; }};protected Book(Parcel in) {
    code = in.readInt();
    name = in.readString();
}
Copy the code

In the Book(Parcel in) method, if one of the member variables is another serializable object, the context classloader of the current thread needs to be passed during deserialization otherwise an error will be reported that the class could not be found.

 book = in.readParcelable(Thread.currentThread().getContextClassLoader());
Copy the code

The description function is done by the describeContents method, which should return 0 in almost all cases and 1 only if the file descriptor is present in the current object.

public int describeContents(a) {
    return 0;
}
Copy the code

Serializable is a Java serialization interface that is simple to use but expensive. Serialization and deserialization processes require a lot of I/O operations. And Parcelable is the serialization method in Android, suitable for the Android platform, high efficiency but troublesome to use. Parcelable is mainly for memory serialization. Parcelable can also serialize objects to storage devices or transfer objects over the network. However, Serializable is recommended.

Binder

Binder is a class in Android that implements the IBinder interface. Binder is a cross-process communication method for Android from an IPC perspective. Binder can also be understood as a virtual physical device driven by /dev/binder. From the perspective of the Android Framework, Binder serves as a bridge between ServiceManager managers (ActivityManager, WindowManager) and corresponding ManagerServices. From the Android application layer, Binder is the communication medium between the client and the server. When bindService is performed, the server returns a Binder object containing the server’s business call. The client can obtain the services or data provided by the server (both normal services and AIDL-based services).

Binder communication uses the C/S architecture and consists of Client, Server, ServiceManager, and Binder drivers from a component perspective. The ServiceManager manages various services in the system.

The interactions among Client,Server and Service Manager in the figure are all dotted lines, because they do not directly interact with each other, but with Binder drivers to realize IPC communication. Binder drivers are located in kernel space, and Client,Server, and Service Manager are located in user space. Binder drivers and Service Manager can be regarded as the infrastructure of Android platform, while Client and Server are the application layer of Android. Developers only need to customize the implementation of Client and Server. The basic Android platform architecture enables direct IPC communication.

Binder is primarily used in Android for Services, including AIDL and Messenger. Binders are not involved in interprocess communication with services. The underlying layer of Messenger is AIDL, so here’s how Binder works.

The system automatically generates a. Java file based on the AIDL file
  1. Book.java

Represents the book information entity class, implements the Parcelable interface. 2. book. aidl Declaration of the Book class in aiDL. 3. Ibookmanager.aidl defines an interface to manage the Book entity, including getBookList and addBook methods. Although the Book class and IBookManager are in the same package, you still import the Book class in IBookManager. 4. Ibookmanager. Java system for iBookManager. aidl Binder class, in the gen directory.

IBookManager inherits the IInterface interface, which is required for all interfaces transmitted in Binder. The structure is as follows:

  • The getBookList and addBook methods are declared, and two integer ids are declared to identify the two methods, which are requested by the client during transact.
  • An inner class Stub is declared, which is a Binder class that allows method calls that do not cross transact processes when clients and servers are in the same process. When they are in different processes, method calls need to go through a Transact procedure. This logic is done by Stub’s internal Proxy class.
  • The core implementation of this interface is its inner class Stub and its inner Proxy class Proxy.
Internal methods and definitions of Stub and Proxy classes

  1. DESCRIPTOR

A unique identifier for a Binder, usually with a Binder class name. AsInterface (Android.os.ibinder obj) converts Binder objects on the server to objects of the AIDL interface type required by the client. If C/S is in the same process, this method returns the Stub object itself on the server. Otherwise, the stub.proxy object encapsulated by the system is returned. 3. AsBinder returns the current Binder object. 4. OnTransact is a method that runs in the Binder thread pool on the server side. When a client makes a cross-process request, the remote request is processed by this method after the low-level encapsulation of the system. The prototype of this method is:

 java public Boolean onTransact(int code,Parcelable data,Parcelable reply,int flags)
Copy the code
  • The server uses code to determine what the target method of the client request is,

The parameters required by the target method are then fetched from Data and executed.

  • Write the return value (if any) to Reply after execution.
  • If this method returns false, the server will fail.
  1. Proxy# getBookList and Proxy# same

These two methods run on the client side and are implemented internally as follows:

  • First create the input Parcel object _data, the output Parcel object _reply, and the return value object List needed for the method.
  • The method’s parameter information is then written to _data, if any.
  • The Transact method is then called to initiate an RPC (remote procedure call) while the current thread is suspended.
  • The onTransact method on the server is then called until the RPC procedure returns, and the current thread continues to execute, fetching the return result of the RPC procedure from _REPLY, and finally returning the data from _reply.

An AIDL file is not required and is provided so that the system can generate iBookManager.java for us, but we can write one ourselves.

LinkToDeath and unlinkToDeath

If the server process terminates abnormally, our Binder connection to the server breaks. However, if we do not know that the Binder connection has broken, the client function will be affected. With linkTODeath we could give Binder a death proxy, and we would be notified when Binder died.

  1. Declare a DeathRecipient object. DeathRecipient is an interface with only one method, binderDied. When Binder dies, the binderDied method is called back and we can rebind the remote service.
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
    @Override
     public void binderDied(a){
        if(mBookManager == null) {return;
        } 
        mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
        mBookManager = null;
        // TODO: rebind the remote Service}};Copy the code
  1. Set death proxy to binder after client bind remote service successfully:
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
Copy the code
  1. In addition, Binder’s isBinderAlive can be used to determine if Binder is dead.

IPC mode in Android

There are mainly the following ways:

  • Extras are appended to the Intent
  • The Shared file
  • Binder
  • ContentProvider
  • Socket
Use the Bundle

Three of the four major components (Activity, Service, and Receiver) support Bundle data passing in intEnts. The Bundle implements the Parcelable interface, so it can be easily transferred between different processes. When we start an Activity, Service, or Receiver for another process in one process, we can attach messages to the Bundle that we want to transmit to the remote process and send them out with intEnts. The data being transferred must be serialized.

Using File Sharing

We can serialize an object to the file system and recover the object from another process.

  1. Serialize an object to a file via ObjectOutputStream/ObjectInputStream, or unsequence the object from the file in another process. Note: The deserialized object is just the same in content as the object before serialization; it is essentially two objects.
  2. Concurrent file reads and writes result in reading objects that may not be up to date, especially if they are written concurrently. Therefore, file sharing is suitable for communication between processes that do not have high requirements on data synchronization and should properly handle concurrent read and write problems.
  3. The underlying implementation of SharedPreferences uses XML files to store key-value pairs. The system has a certain cache policy for its read/write, that is, there is a cache of SharedPreferences file in memory. Therefore, in multi-process mode, the system becomes unreliable for its read/write, and SharedPreferences have a high probability of data loss in the face of high concurrent read/write. Therefore, it is not recommended to use SharedPreferences in IPC.
Use the Messenger

Messenger can pass Message objects between different processes. Is a lightweight IPC solution with the underlying implementation being AIDL. It encapsulates AIDL, making it easier for us to conduct IPC.

In specific use, it can be divided into server and client:

  1. Server: Create a Service to handle client requests, and create a Handler through which to create one

Messenger, and then return to the Binder underlying the Messenger object in onBind of the Service.

 private final Messenger mMessenger = new Messenger (new xxxHandler());
Copy the code
  1. Client: Bind the Sevice of the server and use the IBinder object returned by the server to create a Messenger. Through this Messenger, messages can be sent to the server. The Message type is Message. If a server response is required, a Handler needs to be created and used to create a Messenger (just like the server), which is passed to the server via the replyTo parameter of Message. The server responds to the client using the replyTo parameter of the Message.

In short, the client and server get each other’s Messenger to send messages. Clients use bindService and servers use message.replyTo to get each other’s Messenger.

There is a Hanlder in Messenger that processes messages in the queue in a serial manner. There is no concurrent execution, so we don’t have to worry about thread synchronization.

Use AIDL

If you have a large number of concurrent requests, using Messenger is not a good idea, and if you need to call server-side methods across processes, Messenger can’t do it. At this point we can use AIDL.

The process is as follows:

  1. The server needs to create a Service to listen for client requests, then create an AIDL file, declare the interface exposed to the client in the AIDL file, and implement the AIDL interface in the Service.
  2. The client first binds the Service to the server, converts the Binder object returned by the server to the type of the AIDL interface, and then invokes AIDL methods.

Data types supported by AIDL:

  • Basic data types, String, CharSequence
  • List: Only ArrayList is supported, and every element in it must be supported by AIDL
  • Map: Only HashMap is supported, and every element in it must be supported by AIDL
  • Parcelable
  • All AIDL interfaces themselves can also be used in AIDL files

Custom Parcelable objects and AIDL objects must be explicitly imported, regardless of whether they are in the same package as the current AIDL file.

If a custom Parcelable object is used in an AIDL file, you must create a new AIDL file with the same name and declare it as a Parcelable type.

package com.ryg.chapter_2.aidl;
parcelable Book;
Copy the code

Parameters in the AIDL interface must indicate directions in/out except for basic types. AIDL interface files support only methods, not static constants. It is recommended that all aiDL-related classes and files be placed in the same package for easy management.

void addBook(in Book book);
Copy the code

The AIDL method is executed in a Binder thread pool on the server side, so when multiple clients are connected at the same time, the collection of administrative data directly uses CopyOnWriteArrayList for automatic thread synchronization. Similarly, ConcurrentHashMap.

RemoteCallbackList is an interface provided by the system to remove cross-process Listeners because the client listener and the server listener are not the same object. RemoteCallbackList supports the management of any AIDL interface because all AIDL interfaces inherit from the I interface.

public class RemoteCallbackList<E extends IInterface>
Copy the code

Internally, it holds all AIDL callbacks through a Map interface whose key is of type IBinder and value is of type Callback. When the client is unregistered, go through all server listeners, find the server listenr with the same Binder object as the client Listener and delete it.

The thread is suspended during client RPC, and since the called method runs in the server’s Binder thread pool, it may be time-consuming to call the server’s methods in the main thread.

By default, anyone can connect to our remote service. We must add permission authentication. If permission authentication fails, methods in the service cannot be called. There are two common methods of verification:

  • Validation in onBind does not return NULL.

Authentication methods such as permission authentication are declared in AndroidManifest:

<permission
android:name="com.rgy.chapter_2.permisson.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
Copy the code

Android custom permissions and use permissions:

public IBinder onBind(Intent intent){
    int check = checkCallingOrSelefPermission("com.ryq.chapter_2.permission.ACCESS_BOOK_SERVICE");
    if(check == PackageManager.PERMISSION_DENIED){
        return null;
    }
    return mBinder;
}
Copy the code

This approach also works for Messager.

  • Verify in onTransact, return false if the verification fails.

Permission validation is possible, as well as Uid and Pid validation.

The use of ContentProvider

ContentProvider is one of the four components that are designed to communicate between processes. As with Messenger, the underlying implementation is with Binder.

The system preset a number of contentProviders, such as contacts, calendar, etc. To access this information, RPC simply uses the Query, UPDATE, INSERT, and delete methods of the ContentResolver.

To create a custom ContentProvider, simply inherit the ContentProvider class and implement the six abstract methods onCreate, Query, update, INSERT, and getType. GetType is used to return the MIME type of a Uri request, with the remaining four methods corresponding to CRUD operations. All six methods run in the ContentProvider process. With the exception of onCreate, which is called back by the system and runs in the main thread, the other five methods are called outside and run in the Binder thread pool.

A ContentProvider is a Uri that distinguishes the set of data to be accessed by the outside world. For example, we need to define separate URIs and URI_codes for the tables in the ContentProvider. Based on Uri_Code, we know which table to access.

Query, Update, INSERT, and DELETE methods have concurrent access from multiple threads, so thread synchronization must be done within the methods. If SQLite is used and there is only one SQLiteDatabase, synchronization has already been done within SQLiteDatabase. If you have multiple SQliteDatabases or a List as the underlying data set, you must do thread synchronization.

Use the Socket

Sockets, also called sockets, are classified into streaming sockets and user datagram sockets, corresponding to TCP and UDP protocols respectively. Socket can realize the communication between two processes in the computer network, of course, can also realize the communication between processes in the local. We demonstrate this with a cross-process chat program.

Establish a TCP Service in the remote Service, and then connect to the TCP Service in the main screen. The server listens to the local port. The client connects to the specified port. After the connection is successfully established, the client can send messages to the server or receive messages sent by the server after receiving the Socket object.

In addition to using TCP sockets, UDP sockets can also be used. In fact, sockets can not only realize communication between processes, but also between devices (as long as the IP addresses between devices are visible to each other).

Binder connection pool

The AIDL process mentioned earlier is: First create a service and an AIDL interface. Then create a class that inherits from the Stub class of the AIDL interface and implements the abstract methods in the Stub class. The client takes the object of the class in the service’s onBind method and binds the service. After the connection is established, RPC can be carried out through this Stub object.

If the project is large and multiple business modules need to use AIDL for IPC, as the number of AIDL increases, we cannot add services without limit, we need to manage all AIDL in the same Service.

  • There is only one Service on the server side. Put all AIDL in one Service. There is no coupling between different business modules.
  • The server provides a queryBinder interface that returns Binder objects to the client based on the characteristics of the business module.
  • Different business modules get the required Binder objects to conduct RPC.

Choose the appropriate IPC method