Everything you need to know about Binder as an application developer

Everything you need to know about Binder as an application developer

Why understand Binder?

Generally, inter-process communication (IPC) is rarely used directly in Android application development, but if you want to know:

  • How is the App started and initialized?
  • What is the process of launching an Activity?
  • How do processes communicate with each other?
  • What is the specific principle of AIDL?
  • The design principles of many plug-in frameworks, etc.

You must have some understanding of Binder, whether it is the four major components or various system Services, such as ActivityManagerService and PackageManagerService, their implementation depends on Binder's communication mechanism. It can be seen that Binder is important in the Android system. It can be said that Binder is the first step towards becoming a senior engineer.

[[245146]]

The Binder mechanism is very complex and it is difficult to fully understand it. In addition to understanding various knowledge in the operating system, you also need to understand the code implementation of the Binder driver layer. I have read a lot of articles about Binder recently, most of which are too abstract or too in-depth in source code details, and there are few articles that are really easy to understand.

This article mainly explains the various concepts and basic communication processes in Binder from a macro perspective, focusing only on the implementation of the Java layer, and not introducing the underlying implementation. For application developers, understanding the basic design principles and communication processes of Binder is enough. If you want to understand Binder in depth, you need to read the source code yourself.

This article mainly analyzes from three aspects:

1. Why Binder?

  • Disadvantages of traditional Linux IPC mechanism
  • Some basic knowledge of Linux
  • Communication principle of traditional Linux IPC mechanism

2. The basic principle of Binder

  • The underlying principle of Binder
  • Binder communication model
  • Binder's proxy mechanism
  • Re-understanding of the Binder concept

3. Understanding Binder through code

  • Learn how to use Binder through AIDL examples
  • Implementing ActivityManagerService by manual coding

1. Why Binder?

1.1 Disadvantages of Traditional IPC Mechanism

As we all know, the Android system is implemented based on the Linux kernel. Linux has provided a variety of inter-process communication mechanisms, such as pipes, message queues, shared memory, and sockets. Why do we need to implement another IPC mechanism? There are two main reasons:

1.1.1 Performance perspective

Pipes, message queues, and sockets all require two memory copies to achieve one process communication, which is too inefficient; although shared memory does not require memory copying, it is complex to manage; Binder only requires one memory copy, which is lower than shared memory and better than other methods from a performance perspective.

1.1.2 Security Considerations

The traditional IPC mechanism has no security measures. The receiver cannot obtain the reliable process ID or user ID of the other party. It is completely protected by the upper-layer protocol. For example, the IP address of Socket communication is filled in by the client and is likely to be tampered by malicious programs. As an open source platform for end users, Android has a large number of applications for users to choose from in the application market, so security is extremely important. The Android system assigns a user ID (UID) to each installed App. The UID is an important identifier for identifying the identity of the process. A series of permission checks can be performed through the UID. On the other hand, the access point of the traditional IPC is open, and any program can access it according to the protocol. It is impossible to prevent malicious programs from accessing it. Android needs an IPC mechanism based on the C/S architecture. The server side needs to be able to verify the identity of the client's request to ensure data security.

1.2 Some basic knowledge of Linux

To understand how Binder implements cross-process communication with only one memory copy, we first need to understand why the traditional IPC mechanism requires two memory copies. This requires understanding some basic knowledge of operating systems.

1.2.1 Process Isolation

Let's first look at Wikipedia's definition of "process isolation":

Process isolation is a set of different hardware and software technologies designed to protect processes in the operating system from interfering with each other. This technology is to prevent process A from writing to process B. Process isolation is implemented using virtual address space. The virtual address of process A is different from the virtual address of process B, which prevents process A from writing data information to process B.

In other words, data between processes is not shared, and process A cannot directly access the data of process B, thereby ensuring data security. In an operating system with process isolation, the interaction between processes must be through the IPC mechanism.

The implementation of process isolation uses virtual address space. What is virtual address space? First of all, you need to understand the concept of virtual memory in the operating system. It is a technology that improves programming efficiency and physical memory utilization efficiency. In simple terms, the application sees a continuous and complete memory address space, but in fact these spaces are mapped to fragmented physical memory. This mapping process is transparent to the application. This concept is very important. For a deeper understanding of virtual memory, you can refer to this article: Understanding Linux Virtual Memory and Physical Memory

1.2.2 Process space: user space/kernel space

Today's operating systems all use virtual memory. For a 32-bit operating system, the addressing space is 2 to the power of 32, or 4G. The core of the operating system is the kernel, which has all access rights to the underlying devices, so it needs to be independent of ordinary applications, and user processes cannot directly access kernel processes. The operating system logically divides the virtual address space into user space and kernel space. In a 32-bit Linux operating system, the high 1GB bytes are used by the kernel, called the kernel space; the remaining 3GB bytes are used by user processes, called the user space.

1.2.3 System calls: user mode/kernel mode

Because the permissions of user space are lower than those of kernel space, it is inevitable that user space needs to access the resources of kernel space, such as reading and writing files and network access. How to achieve this? The only way is through the system call interface provided by the operating system. Through the system call interface, user programs can achieve limited access to kernel resources under the control of the kernel, which can not only meet the resource requests of applications, but also ensure system security and stability.

When a user process executes its own code, the process is currently in the user running state (user state). At this time, the processor executes user code with lower permissions. When a user process executes kernel code through a system call, the process temporarily enters the kernel running state (kernel state). At this time, the processor has maximum permissions and can execute privileged instructions.

1.2.4 Kernel modules/drivers

As mentioned earlier, user space can access kernel space through system calls, so how do user spaces (processes) communicate? Traditional IPC mechanisms are supported by the kernel, and the same is true for Binder. There is a Binder driver running in the kernel that is responsible for Binder communication between processes.

Driver generally refers to device driver, which is a special program that enables computers and devices to communicate. It is equivalent to the interface of hardware, and the operating system can only use this interface.

The Binder driver is a virtual character device registered in /dev/binder. It defines a set of Binder communication protocols, is responsible for establishing Binder communication between processes, and provides a series of underlying support for data packets to be transmitted between processes. Application processes also access the Binder driver through system calls.

1.3 Communication Principles of Traditional IPC Mechanism

After understanding the basics above, let's take a look at how the traditional IPC mechanism is implemented, which is usually the following two steps (except for the shared memory mechanism):

  1. The sending process copies the data to be sent to the kernel buffer through a system call (copy_from_user).
  2. The receiver opens up a memory space, and the kernel copies the data in the kernel buffer to the receiver's memory buffer through a system call (copy_to_user).

This traditional IPC mechanism has two problems:

  1. Data needs to be copied twice, the first from the sender's user space to the kernel buffer, and the second from the kernel buffer to the receiver's user space.
  2. The receiving process does not know how much space to allocate in advance to receive the data, so there may be a waste of space.

2. The basic principle of Binder

2.1 Binder underlying principle

The traditional IPC mechanism needs to copy the memory twice. How does Binder realize inter-process communication with only one memory copy? We have learned that Linux uses virtual memory addressing. The virtual memory address of the user space is mapped to the physical memory. The reading and writing of the virtual memory is actually the reading and writing of the physical memory. This process is memory mapping, and this memory mapping process is implemented through the system call mmap().

Binder uses memory mapping to create a memory mapping layer between the kernel space and the data buffer of the receiver's user space. In this way, the data copied from the sender's user space to the kernel space buffer is equivalent to being directly copied to the receiver's user space data buffer, thus reducing one data copy.

2.2 Binder Communication Model

Binder is based on the C/S architecture. For both communicating parties, the process that initiates the request belongs to the Client, and the process that receives the request belongs to the Server. Due to process isolation, the two parties cannot communicate directly. How is Binder implemented?

The network communication example in the Binder principle analysis written for Android application engineers is very appropriate. The communication process of Binder is similar to that of network requests. The network communication process can be simplified into four roles: Client, Server, DNS server and router. The general process of a complete network communication is as follows:

a. Client enters the domain name of the Server

b. DNS domain name resolution

The corresponding server cannot be found directly through the domain name. The server's domain name must first be converted into a specific IP address through a DNS server.

c. Send the request to the Server through the router

After the client resolves the server's IP address through the DNS server, it cannot directly initiate a request to the server. It needs to go through layers of routers before reaching the server.

d. Server returns data

After the server receives and processes the request, it returns the data to the client through the router.

In the Binder mechanism, four roles are also defined: Client, Server, Binder driver and ServiceManager.

Binder driver: Similar to a router in network communication, it is responsible for forwarding the client's request to a specific server for execution, and transmitting the data returned by the server back to the client.

ServiceManager: Similar to the DNS server in network communication, it is responsible for converting the Binder descriptor requested by the Client into a specific Server address so that the Binder driver can forward it to the specific Server. If the Server needs to provide Binder services, it needs to register with the ServiceManager.

The specific communication process is as follows:

a. Server registers with ServiceManager

The server registers with the ServiceManager through the Binder driver, declaring that it can provide services to the outside world. The ServiceManager will keep a mapping table: the Binder reference corresponding to the server named zhangsan is 0x12345.

b. Client requests the Server's Binder reference from ServiceManager

When the Client wants to request data from the Server, it needs to first request the Server's Binder reference from the ServiceManager through the Binder driver: I want to communicate with the Server named zhangsan, please tell me the Server's Binder reference.

c. Send a request to a specific Server

After the Client obtains the Binder reference, it can communicate with the Server through the Binder driver.

d. Server returns the result

After the server responds to the request, it needs to return the result to the client again through the Binder driver.

As you can see, the communication between Client, Server and ServiceManager is all done through Binder driver as a bridge, which shows the importance of Binder driver. Maybe you still have some doubts, ServiceManager and Binder driver belong to two different processes, they are for the inter-process communication between Client and Server, that is to say, the inter-process communication between Client and Server depends on the inter-process communication between ServiceManager and Binder driver, which is like: "Eggs lay chickens, chickens lay eggs, but the first egg must be hatched by a chicken". How does the Binder mechanism create the first chicken to lay eggs?

  1. When the Android system is started, a process named servicemanager is created. This process registers with the Binder driver through an agreed command BINDERSETCONTEXT_MGR and applies to become a ServiceManager. The Binder driver automatically creates a Binder entity for the ServiceManager (*** a laying hen);
  2. And the reference of this Binder entity is 0 in all clients, which means that each client can communicate with the ServiceManager through this reference 0. The server registers with the ServiceManager through reference 0, and the client can obtain the Binder reference of the server to communicate through reference 0.

Android Binder Design and Implementation - The design chapter provides a more detailed introduction to Client, Server, Binder driver and ServiceManager.

2.3 Binder's Proxy Mechanism

Through the above analysis, we have learned the basic communication process of Binder: Client obtains the Binder reference of Server from ServiceManager, and Client initiates specific requests to Server through Binder reference. How does Client call Server method through this Binder reference?

For example, if a Server provides an add method, the process for a Client to actually request add is as follows: the Client first obtains the Server's Binder reference from the ServiceManager through the Binder driver. This reference is a Java Object, and this Object has an add method. After obtaining this Object, the Client can directly request the add method.

In fact, the Object obtained by the Client is not the real Binder entity of the Server. The Binder driver performs an object conversion and packages the Object into a proxy object ProxyObject. The ProxyObject has the same method signature as the real Binder entity. When the Client requests the add method through the ProxyObject, the Binder driver will automatically forward the request to the specific Binder entity for execution. This is the Binder proxy mechanism. Since the ProxyObject has the same method signature as the real Binder entity, the Client does not need to care whether it is a ProxyObject or a real Object.

For the convenience of description, the real Binder entity of the Server is called the Binder local object; the Binder reference in the Client, that is, ProxyObject, is called the Binder proxy object.

2.4 Re-understanding of the Binder Concept

After the above analysis, we have roughly understood the basic communication principle of the Binder mechanism. Now let's go back and re-sort out our understanding of the Binder mechanism:

Generally speaking, Binder is an object-oriented IPC mechanism based on C/S structure. It includes four major components: Client, Server, Binder driver and ServiceManager. The meaning of Binder in each component is different:

  • For Client

Binder is a reference to the local object of the Server. This reference is actually a proxy object. The Client indirectly accesses the local object of the Server through this proxy object.

  • For Server

Binder is a local object that provides a specific implementation and needs to be registered with the ServiceManager;

  • For Binder Driver

It is the bridge connecting the Client to the Server, responsible for converting the proxy object into a local object and returning the execution result of the Server to the Client.

  • For ServiceManager

It saves the mapping between the Server Binder character name and the Binder reference, and the Client uses it to find the Server's Binder reference.

The Binder driver retains the specific structure of the Binder proxy object and the Binder local object. Since we are only concerned with the basic communication mechanism of Binder, we will not introduce the underlying implementation in detail. Students who want to know more can refer to Android Binder Design and Implementation - Design.

3. Understanding Binder through code

The above introduction is relatively abstract. Now let's understand Binder through specific examples.

  • Learn how to use Binder through AIDL examples
  • Implementing ActivityManagerService by manual coding

3.1 Understand the usage of Binder through AIDL examples

The most common way to implement Binder communication is through aidl. The aidl interface defines the interface for communication between Client and Server. If you are not familiar with aidl, please refer to the official document Android Interface Definition Language (AIDL).

3.1.1 Responsibilities of Several Classes Related to Binder

Before the specific analysis, we need to understand the responsibilities of several classes related to Binder:

  • IBinder

The Base interface for cross-process communication declares a series of abstract methods that need to be implemented for cross-process communication. Implementing this interface means that cross-process communication is possible. Both the Client and the Server must implement this interface.

  • IInterface

This is also a Base interface, which is used to indicate what capabilities the Server provides and is the protocol for communication between the Client and the Server.

  • Binder

The base class for local objects that provide Binder services. It implements the IBinder interface. All local objects must inherit this class.

  • BinderProxy

In the Binder.java file, a BinderProxy class is also defined. This class represents the Binder proxy object. It also implements the IBinder interface, but many of its implementations are handled by the native layer. What the Client actually gets is this proxy object.

  • Stub

This class is automatically generated after compiling the aidl file. It inherits from Binder, indicating that it is a Binder local object; it is an abstract class that implements the IInterface interface, indicating that its subclasses need to implement the specific capabilities that the Server will provide (that is, the methods declared in the aidl file).

  • Proxy

It implements the IInterface interface, which means it is part of the Binder communication process; it implements the methods declared in aidl, but is ultimately handled by the mRemote member, which means it is a proxy object. The mRemote member is actually BinderProxy.

3.1.2 AIDL Examples

First, define an aidl file. This interface declares a getPid method:

  1. //IRemoteService.aidl
  2. package com.rush.demo.aidltest;
  3.  
  4. interface IRemoteService {
  5. int getPid();
  6. }

The following is the Java class generated after compiling IRemoteService.aild:

  1. // IRemoteService.java
  2. package com.rush.demo.aidltest;
  3.  
  4. public interface IRemoteService extends android.os.IInterface {
  5. public   static abstract class Stub extends android.os.Binder implements com.rush.demo.aidltest.IRemoteService {
  6. //Binder descriptor
  7. private static final java.lang.String DESCRIPTOR = "com.rush.demo.aidltest.IRemoteService" ;
  8.  
  9. public Stub() {
  10. this.attachInterface(this, DESCRIPTOR);
  11. }
  12.  
  13. public   static com.rush.demo.aidltest.IRemoteService asInterface(android.os.IBinder obj) {
  14. if ((obj == null )) {
  15. return   null ;
  16. }
  17. android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  18. if (((iin != null ) && (iin instanceof com.rush.demo.aidltest.IRemoteService))) {
  19. return ((com.rush.demo.aidltest.IRemoteService) iin);
  20. }
  21. return new com.rush.demo.aidltest.IRemoteService.Stub.Proxy(obj);
  22. }
  23.  
  24. @Override
  25. public android.os.IBinder asBinder() {
  26. return this;
  27. }
  28.  
  29. @Override
  30. public boolean onTransact( int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
  31. switch (code) {
  32. case INTERFACE_TRANSACTION: {
  33. reply.writeString(DESCRIPTOR);
  34. return   true ;
  35. }
  36. case TRANSACTION_getPid: {
  37. data.enforceInterface(DESCRIPTOR);
  38. java.lang.String _arg0;
  39. _arg0 = data.readString();
  40. int _result = this.getPid(_arg0);
  41. reply.writeNoException();
  42. reply.writeInt(_result);
  43. return   true ;
  44. }
  45. }
  46. return super.onTransact(code, data, reply, flags);
  47. }
  48.  
  49. private static class Proxy implements com.rush.demo.aidltest.IRemoteService {
  50. private android.os.IBinder mRemote;
  51.  
  52. Proxy(android.os.IBinder remote) {
  53. mRemote = remote;
  54. }
  55.  
  56. @Override
  57. public android.os.IBinder asBinder() {
  58. return mRemote;
  59. }
  60.  
  61. public java.lang.String getInterfaceDescriptor() {
  62. return DESCRIPTOR;
  63. }
  64.  
  65. @Override
  66. public   int getPid(java.lang.String name ) throws android.os.RemoteException {
  67. android.os.Parcel _data = android.os.Parcel.obtain();
  68. android.os.Parcel _reply = android.os.Parcel.obtain();
  69. int _result;
  70. try {
  71. _data.writeInterfaceToken(DESCRIPTOR);
  72. _data.writeString( name );
  73. mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);
  74. _reply.readException();
  75. _result = _reply.readInt();
  76. finally
  77. _reply.recycle();
  78. _data.recycle();
  79. }
  80. return _result;
  81. }
  82.  
  83. static final int TRANSACTION_getPid = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
  84. }
  85.  
  86. public   int getPid(java.lang.String name ) throws android.os.RemoteException;
  87. }

There are 3 classes in this file:

a. IRemoteService

Inherited from IInterface interface, declared the getPid method declared in IRemoteService.aidl, which is the interface for communication between Client and Service.

b. IRemoteService.Stub

The static abstract inner class of IRemoteService inherits from Binder. Its subclass needs to implement the IRemoteService interface, indicating that it is the Binder local object of the Server and needs to implement the getPid interface.

c. IRemoteService.Stub.Proxy

The static inner class of IRemoteService.Stub does not inherit from Binder, but contains an IBinder object, which is actually BinderProxy, indicating that it is the local proxy object of Server in Client. Proxy implements the getPid interface, serializes the parameters and passes them to mRemote (BinderProxy) for processing, which is actually to pass it to the Binder driver to complete the communication with the remote Stub.

Let's first look at the asInterface method in Stub. This method is usually called by the Client after bindService succeeds. Its function is to convert the IBinder object returned after the binding is successful into a specific IInterface interface. After the Client obtains this IInterface interface, it can freely call the methods provided by the Server.

  1. public   static com.rush.demo.aidltest.IRemoteService asInterface(android.os.IBinder obj) {
  2. if ((obj == null )) {
  3. return   null ;
  4. }
  5. android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  6. if (((iin != null ) && (iin instanceof com.rush.demo.aidltest.IRemoteService))) {
  7. return ((com.rush.demo.aidltest.IRemoteService) iin);
  8. }
  9. return new com.rush.demo.aidltest.IRemoteService.Stub.Proxy(obj);
  10. }

The asInterface method may return the IRemoteService object of the Stub itself, or create a Proxy object. Why is this? Because although Binder is a cross-process communication mechanism, it can also serve the process itself, that is, the Client and Server may be in the same process. In the same process, there is no need to transfer through the Binder driver, and direct access is sufficient; if the Client and Server are in different processes, they need to be transferred through the Binder proxy object. In other words:

  1. The Client and Server are in the same process, obj is a Binder local object (a subclass of Stub), and the asInterface method returns the Binder local object;
  2. Client and Server are in different processes, obj is actually a Binder proxy object, and asInterface returns a Proxy object.
  1. //Binder.java
  2. /**
  3. * How does obj.quecalInterface find out whether there is a local IInterface? From the Binder code, we can see that it simply compares the Binder descriptor and the descriptor to be found to see if they match. If they match, it directly returns mOwner. This mOwner is the this parameter passed in when calling the attachInterface method in the Stub construction method.
  4. */
  5. public IInterface queryLocalInterface(String descriptor) {
  6. if (mDescriptor.equals(descriptor)) {
  7. return mOwner;
  8. }
  9. return   null ;
  10. }
  11.  
  12. final class BinderProxy implements IBinder {
  13. public IInterface queryLocalInterface(String descriptor) {
  14. return   null ;
  15. }
  16. }
  17.  
  18. public   static abstract class Stub extends android.os.Binder implements com.rush.demo.aidltest.IRemoteService {
  19. // Binder descriptor, the value is the full name of the interface class
  20. private static final java.lang.String DESCRIPTOR = "com.rush.demo.aidltest.IRemoteService" ;
  21.  
  22. public Stub() {
  23. //Bind owner and descriptor to Binder
  24. this.attachInterface(this, DESCRIPTOR);
  25. }
  26. }

How does obj.queryLocalInterface find out whether there is a local IInterface? From the Binder code, we can see that it simply compares the Binder descriptor with the descriptor to be searched. If they match, it directly returns mOwner, which is the this parameter passed in when calling the attachInterface method in the Stub construction method. The queryLocalInterface method of BinderProxy directly returns null.

There are two scenarios in which the Client calls the Server method through Binder:

1. Client and Server are in the same process

The Stub.asInterface method returns a Stub object, which is a Binder local object. This means that it has nothing to do with Binder cross-process communication and can be called directly. At this time, the Client caller and the Server responder are in the same thread.

2. Client and Server are in different processes

The Stub.asInterface method returns a Binder proxy object, which needs to be driven by the Binder to complete cross-process communication. In this scenario, the Client caller thread will be suspended (Binder also provides an asynchronous method, which is not discussed here) and wait for the Server to respond before returning data. It should be noted that the Server's response is processed in the Binder thread pool of the Server process, not the main thread.

Next, we analyze the specific process of Client calling the getPid method in the cross-process scenario:

1. Client calls the Binder proxy object, and the Client thread hangs

The IRemoteService reference obtained by the Client is actually a Proxy. Calling the getPid method actually calls the getPid method of the Proxy. This method simply serializes the parameters and calls the transact method of the mRemote member. The Stub class defines a method number for each method in IRemoteService, and the number of the getPid method is passed into the transact method. At this time, the Client caller thread is suspended, waiting for the Server to respond with data.

  1. // Stub.Proxy
  2. public   int getPid(java.lang.String name ) throws android.os.RemoteException {
  3. ...
  4. _data.writeInterfaceToken(DESCRIPTOR);
  5. _data.writeString( name );
  6. mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);
  7. _reply.readException();
  8. _result = _reply.readInt();
  9. ...
  10. return _result;
  11. }

2. The Binder proxy object dispatches the request to the Binder driver

The mRemote member in Proxy is actually BinderProxy, and the transact method in BinderProxy is ultimately called into the transactNative method, which means that the Client's request is dispatched to the Binder driver for processing.

3. Binder driver dispatches the request to Server

After a series of processing, the Binder driver dispatches the request to the Server, that is, calling the onTransact method of the local Binder object (Stub) of the Server, and finally completing the specific call of the getPid method in this method. In the onTransact method, the specific method to be processed is distinguished according to the method number passed in when calling transact in the Proxy.

  1. // Stub
  2. public boolean onTransact( int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
  3. switch (code) {
  4. ...
  5. case TRANSACTION_getPid: {
  6. data.enforceInterface(DESCRIPTOR);
  7. //Get method parameters
  8. java.lang.String _arg0 = data.readString();
  9. //Call the getPid method, which is implemented in the Stub subclass, Server
  10. int _result = this.getPid(_arg0);
  11. reply.writeNoException();
  12. reply.writeInt(_result);
  13. return   true ;
  14. }
  15. }
  16. return super.onTransact(code, data, reply, flags);
  17. }

4. Wake up the Client thread and return the result

After onTransact is completed, the result is written to reply and returned to the Binder driver, which wakes up the suspended Client thread and returns the result. At this point, a cross-process communication is completed.

3.2 Manual coding to implement ActivityManagerService

From the previous examples, we already know that the aidl file is only used to define the interface for C/S interaction. Android will automatically generate the corresponding Java class during compilation. The generated class contains Stub and Proxy static inner classes to encapsulate the data conversion process. In actual use, you only need to care about the specific Java interface class. Why are Stub and Proxy static inner classes? This is actually just to put the three classes in one file to improve the aggregation of the code. Through the above analysis, we can actually implement Binder communication without aidl and manually code. Let's implement ActivityManagerService through coding.

First define the IActivityManager interface:

  1. public interface IActivityManager extends IInterface {
  2. //binder descriptor
  3. String DESCRIPTOR = "android.app.IActivityManager" ;
  4. //Method number
  5. int TRANSACTION_startActivity = IBinder.FIRST_CALL_TRANSACTION + 0;
  6. //Declare a method to start an activity. For simplicity, only the intent parameter is passed in here
  7. int startActivity(Intent intent) throws RemoteException;
  8. }

Secondly, implement the local Binder object base class on the ActivityManagerService side:

  1. // The name is arbitrary, if it is inconsistent, it is called Stub
  2. public abstract class ActivityManagerNative extends Binder implements IActivityManager {
  3.  
  4. public   static IActivityManager asInterface(IBinder obj) {
  5. if (obj == null ) {
  6. return   null ;
  7. }
  8. IActivityManager in = (IActivityManager) obj.queryLocalInterface(IActivityManager.DESCRIPTOR);
  9. if ( in != null ) {
  10. return   in ;
  11. }
  12. //Proxy object, see the code below
  13. return new ActivityManagerProxy(obj);
  14. }
  15.  
  16. @Override
  17. public IBinder asBinder() {
  18. return this;
  19. }
  20.  
  21. @Override
  22. protected boolean onTransact( int code, Parcel data, Parcel reply, int flags) throws RemoteException {
  23. switch (code) {
  24. // Get the binder descriptor
  25. case INTERFACE_TRANSACTION:
  26. reply.writeString(IActivityManager.DESCRIPTOR);
  27. return   true ;
  28. // Start the activity. After deserializing the intent parameters from data, directly call the subclass startActivity method to start the activity.
  29. case IActivityManager.TRANSACTION_startActivity:
  30. data.enforceInterface(IActivityManager.DESCRIPTOR);
  31. Intent intent = Intent.CREATOR.createFromParcel(data);
  32. int result = this.startActivity(intent);
  33. reply.writeNoException();
  34. reply.writeInt(result);
  35. return   true ;
  36. }
  37. return super.onTransact(code, data, reply, flags);
  38. }
  39. }

Again, implement the proxy object on the Client side:

  1. public class ActivityManagerProxy implements IActivityManager {
  2. private IBinder mRemote;
  3.  
  4. public ActivityManagerProxy(IBinder remote) {
  5. mRemote = remote;
  6. }
  7.  
  8. @Override
  9. public IBinder asBinder() {
  10. return mRemote;
  11. }
  12.  
  13. @Override
  14. public   int startActivity(Intent intent) throws RemoteException {
  15. Parcel data = Parcel.obtain();
  16. Parcel reply = Parcel.obtain();
  17. int result;
  18. try {
  19. //Serialize the intent parameters and write them into data
  20. intent.writeToParcel(data, 0);
  21. // Call the transact method of the BinderProxy object and let the Binder driver handle it.
  22. mRemote.transact(IActivityManager.TRANSACTION_startActivity, data, reply, 0);
  23. reply.readException();
  24. // After waiting for the server to finish executing, read the execution result
  25. result = reply.readInt();
  26. finally
  27. data.recycle();
  28. reply.recycle();
  29. }
  30. return result;
  31. }
  32. }

***, implement the Binder local object (IActivityManager interface):

  1. public class ActivityManagerService extends ActivityManagerNative {
  2. @Override
  3. public   int startActivity(Intent intent) throws RemoteException {
  4. // Start the activity
  5. return 0;
  6. }
  7. }

The simplified version of ActivityManagerService has been implemented here. The remaining thing is that the Client needs to obtain the proxy object IActivityManager of AMS to communicate. In the actual development process, the intermediate code can be automatically compiled through the aidl file, and we do not need to implement it manually, but manual coding can deepen the understanding of the Binder mechanism. We will not use AMS directly during the development process, but understanding the implementation principle of AMS is essential for familiarity with the Framework. I will analyze the specific implementation principle of AMS in subsequent articles.

So far, the basic communication process of the Binder mechanism has been introduced. Since the Binder mechanism is too complicated and my level is limited, errors or shortcomings are inevitable in the article. You are welcome to correct me.

<<:  iPhone XS signal is worse than previous generation? We tested it to see if it works.

>>:  The monthly download volume of Bullet SMS is less than 6,000. Will Alipay be its life-saving straw?

Recommend

Asia-Pacific region can take six measures to build climate-resilient green roads

Sustainable road development balances economic gr...

Annual salary of 250,000 yuan plan Li Chen AE basic + advanced course

Course Catalog: Part I: Chapter 01 AE Interface a...

Today, we pay tribute to the old man!

4 years ago today January 16, 2019 Yu Min, the he...

Are you really ready to raise a dragon in the Year of the Dragon?

Dragons are animals from mythology, and it is pro...

Nature News: AI "disrupts" exoskeletons to help build a strong body

Contributing author: Su Hao (Professor at North C...

3 stages of APP user growth!

Today I’m going to join in the fun and talk about...

Understanding Russian History in One Breath (2)

Mixed Knowledge Specially designed to cure confus...