content

IPC refers to communication across processes. IPC related contents mainly include:

  1. Common IPC communication methods;
  2. Binder;
  3. Two serialization methods and their comparison;

The problem

IPC

  • How does IPC work when communicating across processes on Android
  • Briefly describes the IPC?
  • Mechanisms for interprocess communication
  • AIDL mechanism
  • Bundle mechanism

IPC refers to the communication mechanism between processes. Starting an Activity/Service in the Android system involves cross-process invocation.

There are several ways to implement IPC in Android,

Bundle, used to transfer information between the four components, has the advantage of being simple to use, but has the disadvantage of being able to use only the data types it supports. Bundles inherit from BaseBundle, which stores data through an internally maintained ArrayMap

. We interact directly with them when we use the put() and get() family of methods. ArrayMap

is a key-value pair mapping similar to HashMap, but it is implemented in a similar way to SpareArray in that it is based on two arrays. The goal is also to improve the efficiency of Map. It uses binary lookup to find a hash value.
,>
,>

File sharing means that two processes exchange data by reading/writing the same file. Since the Android system is based on Linux, concurrent file reads and writes can be carried out without any restrictions, even two threads writing to the same file at the same time is allowed. If we read/write concurrently, the data we read may not be up to date. File sharing is suitable for communication between processes that do not require high data synchronization and should properly handle concurrent read/write problems.

SharedPreferences is also a file, but the system has a cache policy for its read/write, that is, there is a cache of SP file in memory. Therefore, in multi-process mode, the system becomes unreliable for its read/write, and there is a high probability of data loss in the face of high concurrent read/write access. SP is not recommended for interprocess communication.

Messenger is a lightweight IPC scheme with an underlying implementation of AIDL that can pass messages between different processes. It handles only one request at a time, and there is no need to worry about thread synchronization on the server side. There is no concurrency on the server side. In remote services, declare a Messenger, use a Handler to process incoming messages, and then return Messenger’s binder in the onBind() method. When a client binds to a Service, it can use the returned Binder to create Messenger and send services to the Service.

    // The code for the remote service
    private Messenger messenger = new Messenger(new MessengerHandler(this));

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        ToastUtils.makeToast("MessengerService bound!");
        return messenger.getBinder();
    }

    // The ServiceConnection used by the client bind service
    private ServiceConnection msgConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // Send a message to the remote Messenger
            boundServiceMessenger = new Messenger(service);
        }
        / /... .
    }

    // The client sends the message code
    Message message = Message.obtain(null./*what=*/ MessengerService.MSG_SAY_SOMETHING);
    message.replyTo = receiveMessenger; // Messenger used by the client to receive messages from the server
    Bundle bundle = new Bundle(); // Build the message
    bundle.putString(MessengerService.MSG_EXTRA_COMMAND, "11111");
    message.setData(bundle);
    boundServiceMessenger.send(message); // Send a message to the server
Copy the code

AIDL: Messenger processes messages from the client in a serial manner. If a large number of messages are sent to the server at the same time, the server can only process them one at a time. Therefore, a large number of concurrent requests are not suitable for Messenger. AIDL solves the problem of concurrent and cross-process method invocation.

AIDL stands for Android Interface definition Language. To use it, you simply create a file with the suffix.aidl. During compilation, the compiler automatically generates Java class files using aidl-exe.

The remote service only needs to implement Stub classes. The client needs to pass in a ServiceConnection at bindService() and convert the Binder to a local service in the connection callback method. You can then invoke the methods in the remote service locally.

    // The code for the remote service
    private Binder binder = new INoteManager.Stub() {
        @Override
        public Note getNote(long id) {
            / /... .}};// Bind the service
    public IBinder onBind(Intent intent) {
        return binder;
    }

    // Client code
    private INoteManager noteManager;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // Get the remote service, transform it, and then use it locally
            noteManager = INoteManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {}};// Server-side access control: Use Permission validation, declared in the manifest
    <permission android:name="com.jc.ipc.ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal"/>
    <uses-permission android:name="com.jc.ipc.ACCESS_BOOK_SERVICE"/>
    // Server onBinder method
    public IBinder onBind(Intent intent) {
        //Permission Permission authentication
        int check = checkCallingOrSelfPermission("com.jc.ipc.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) return null;
        return mBinder;
    }
Copy the code

Data types supported by AIDL include, 1). Basic data types; 2). The string and CharSequence; 3).List only supports ArrayList and its elements must be supported by AIDL; 4).Map only supports HashMap, and its elements must be supported by AIDL; 5). All objects that implement the Parcelable interface; 6).aiDL: All AIDL interfaces themselves can also be used in AIDL files.

Attention! Here we use a custom Parcelable object: the Note class, but AIDL does not know this class, so we will create an AIDL file with the same name as the Note class: note.aidl. And the class must be consistent with the package structure of the AIDL file.

ContentProvider is used to provide database sharing. The disadvantage is that the CURN operation mainly provides the data source.

Socket: The Socket is used for data exchange on the network. On Android, Zygote starts a ServerSocket. When we need to create an application process, we will communicate with it through Socket, which is also the application of Socket.

In addition, when using Looper to start MQ, a Looper is started in the Native layer. The Native layer communicates with the Java layer Looper using epoll, which is the pipe communication mechanism.

  • Why is IPC necessary? What might go wrong with multi-process communication?

By default, an Android application has only one process. Each process has its own independent resources and memory space. Other processes cannot access the memory and resources of the current process, and the memory allocated to each process is limited. If a process, a memory than the memory limit will be submitted to the OOM issues, many involves frequent operation of the big picture or need to read a large section of the data in memory when using, it is easy to quote OOM issues, in order to solve the problems of the application memory, Android introduced the concept of multiple processes, it allows within the same application, To share the load of the main process, a separate process is created for some pages that occupy memory, such as Flash, video playing pages, and frequently drawn pages.

This is done simply by using the process attribute to specify a process when registering an Activity or other Activity in the Manifest. Process is a private process and a global process. A process starting with a: (:) is a private process. Other application components cannot run in the same process. A process that does not start with a: is a global process. Other applications can run in the same process with it by using ShareUID. There is also a special way to fork a new process in the native layer via JNI.

However, the multi-process mode presents the following problems:

  1. Static members and singletons are completely invalid because they are not stored in the same space;
  2. The thread synchronization mechanism completely fails because threads are in different processes;
  3. SharedPreferences are less reliable because the system has a cache policy for its read/write, i.e. a cache of SP files in memory;
  4. Application is created multiple times.

These problems can be solved by relying on the process communication mechanism in Android, known as IPC, to pick up the above problems.

  • Binder?

Why Binder, Binder model, efficient

Binder is an Interprocess communication mechanism designed by Android. Linux itself has many ways of communicating across processes, such as pipes, Signal and Trace, sockets, Message queues, shared Memory, and Semaphore. Binder was designed because these communication mechanisms do not meet the efficiency, stability, and security requirements of Android.

Efficiency: As a universal interface, Socket has low transmission efficiency and high cost. It is mainly used for inter-process communication across the network and low-speed communication between processes on the local machine. Message queues and pipes adopt store-and-forward mode, that is, data is copied from the sender cache to the cache created by the kernel, and then copied from the kernel cache to the receiver cache at least twice. Although shared memory does not require copying, it is difficult to control and use. Binders require only one copy of data and are second only to shared memory in performance.

Stability: Binder based on C | S architecture, the Client (Client) what are the requirements to the Server (Server) to complete, clear architecture, strong responsibilities and are independent of each other, natural stability is better. Shared memory does not require copying, but is controlled and difficult to use. Binder mechanisms are superior to memory sharing from a stability perspective.

Security: Binder by adding the status symbol for the client within the kernel layer UID | PID, as a symbol of identity, to safeguard the security of communication. Traditional IPC access points are open and private channels cannot be established. For example, the name of the named pipe, the SystemV key, the Socket IP address, or the file name are all open. As long as a program knows these access points, it can establish a connection with the peer end.

There are four main roles in the Binder model: Client, Server, drive and ServiceManager Binder. The overall structure of the Binder is based on the structure of the C | S, we begin the process of the Activity, for example, each application will interact with the AMS, When they have access to AMS’s Binder, it’s like they have access to the network interface. A Binder is a Server, a Client is a Client terminal, a ServiceManager is a DOMAIN name Server (DNS), and a driver is a router.

  1. Client, Server, and Service Manager are implemented in user space, Binder drivers are implemented in kernel space;
  2. Binder drivers and ServiceManager have been implemented in the Android platform, developers only need to implement their own Client and Server in user space;
  3. Binder drivers provide device files/dev/binderInteracting with user-space, Client, Server, and ServiceManager communicate with Binder drivers through open and IOCtl file manipulation functions;
  4. Interprocess communication between Client and Server is realized indirectly by Binder drivers.
  5. The ServiceManager is a daemon process that manages servers and provides clients with the capability to query Server interfaces.

The system-started init process creates the ServiceManager by parsing the init.rc file. The Binder driver is opened, the ServiceManager is registered as the context, and the Binder loop is started. When a service, such as AMS, is used, it is first fetched from the buffer based on its string name, and if not, it is fetched remotely. The ServiceManager here is also a service.

  1. The client first gets the proxy object on the server side. A proxy object is essentially a “reference” to a server on the client side that has the functionality of the server, enabling it to access the server’s methods from the client as if they were accessing local methods.
  2. The client sends the request to the server by calling the server proxy object.
  3. Proxy objects send user requests through Binder drivers to server processes.
  4. Server processes process user requests and, through Binder drivers, return the processing results to the client’s server proxy objects.

Binder drivers create two mappings between processes when communication is required: between the kernel cache and the kernel receive cache, and between the kernel receive cache and the user space address of the receiving process. In this way, when data is copied from one user space to the kernel buffer, it is copied to another user space. In this way, only one copy is required, eliminating the step of temporary storage in the kernel and doubling the performance. Memory mapping is achieved by the above mmap() function.

(For knowledge about Binder, please refer to my article:Android system source code -2: Binder Communication mechanisms)

serialization

  • The role of serialization, and the differences between Android and serialization
  • Serialization: Why did Android introduce Parcelable
  • Have you tried to simplify the use of Parcelable

There are two main methods of serialization in Android.

The first is Serializable. It is the serialization method provided by Java that allows classes implementing the Serializable interface to be serialized. The disadvantage of this serialization method is that it has low serialization efficiency and is more suitable for serialization of information on the network and disk. It is not suitable for application scenarios with limited memory such as Android. The advantage is that it is easy to use, and only one interface needs to be implemented.

This serialization ObjectOutputStream/ObjectInputStream classes can be used for reading and writing. Such serialized objects can provide a field called serialVersionUID, which identifies the version number of the class, for example, and will not be deserialized if the deconstruction of the class changes.

In addition,

  1. Static member variables do not belong to objects and do not participate in the serialization process
  2. Member variables marked with the TRANSIENT keyword do not participate in the serialization process.

The second method is Parcelable. It is a new serialization method provided by Android. It is mainly used for in-memory serialization, not network and disk serialization. The downside is that it can be cumbersome to use, requiring two methods and a static inner class.

Serializable uses reflection, serialization and deserialization processes require a lot of I/O operations and generate a lot of temporary variables at serialization time, leading to frequent GC. Parcelable itself implements marshalling and unmarshalling without reflection and data is stored in Native memory, which is much faster.

I have tried some solutions to simplify the use of Parcelable myself. There are usually two solutions: one is to use IDE plug-ins to help generate Parcelable related code (plug-in address); The second option is to use reflection, calling the wirte() and read() methods based on the type of the field (low performance); The third approach is based on annotation processing, generating the proxy class at compile time and then calling the methods of the generated proxy class in the methods that need to be overridden.

Processes and threads

  • What are the differences and relationships between processes and threads?

A process is a unit of execution, a program or application on PCS and mobile devices. In Android, an application has only one process by default. Each process has its own independent resources and memory space. Other processes cannot access the memory and resources of the current process, and the system has a limit on the memory allocated to each process. This is done simply by using the process attribute to specify a process when registering an Activity or other Activity in the Manifest. Process is a private process and a global process. A process starting with a: (:) is a private process. Other application components cannot run in the same process. A process that does not start with a: is a global process. Other applications can run in the same process with it by using ShareUID

Android starts with Zygote. When we need to create an application process, we communicate with it through sockets. Zygote forks itself to create an application process.

It should not just cover the differences between the two, but also cover system process creation, application process creation, and how to use multiple processes in your application.

Threads are the smallest unit of CPU scheduling, and a process can contain more than one thread. Java thread implementation is based on the one-to-one thread model, that is, through the language level program to indirectly call the system kernel thread. The kernel thread is supported by the operating system kernel, and the operating system kernel completes the thread switch. The kernel schedules the thread by operating the scheduler, and maps the task of the thread to each processor. Since we write multithreaded programs at the language level, the program does not call the kernel thread directly. Instead, it is a Light Weight Process, which is also a thread in the general sense. Since each lightweight process maps to a kernel thread, we can call the kernel thread through the lightweight process and have the operating system kernel map tasks to individual processors. This one-to-one relationship between lightweight processes and kernel threads is called the one-to-one threading model.

For more information about the Android startup process and the VIRTUAL machine memory model (JMM), please refer to my article:Android system source code -1: Android system start process source analysis 和 JVM Literacy 3: The Virtual Machine memory model and efficient concurrency)