BAT factory technology leader will take you to understand android Binder mechanism in 3 minutes. You must see it!!!

Keywords: Python Java Android

1, Introduction
Recently, due to work, I came into contact with the framework a little more. It is inevitable to deal with Binder and sort out some relevant knowledge, but I was still a little flustered when preparing to write this article. Moreover, the complexity of the whole Binder mechanism can not be described clearly in a few words. I'm also afraid that my understanding is biased and misleads some friends (ps: no one looks at it anyway.... tie my heart). Therefore, I also refer to a lot of materials.

This article mainly analyzes some knowledge and principles of Binder in the java layer from the perspective of Android development, so as to form a complete concept for everyone's mind, such as the implementation principle of AIDL, how Binder communicates, etc. there are many words in this article. Please watch it patiently

Familiar friends can take a look at the next article, which will introduce the startup process of Activity and the Hook technology in Android:

Shock! Activity does not need to register? Teach you Hook hand in hand

2, Binder overview
2.1 why did Android choose Binder
Android is based on the Linux kernel, so in order to realize inter process communication, Android can use some of the original means of Linux, such as pipeline, shared memory, socket and so on. However, Android still uses Binder as the main mechanism, which shows that Binder has incomparable advantages.

In fact, there are probably two factors in process communication. One is the performance and transmission efficiency. The traditional pipeline queue mode adopts the mode of memory buffer. The data is copied from the sender's buffer to the buffer opened by the kernel, and then from the kernel buffer to the receiver's buffer. There are at least two copying processes, and the socket knows that the transmission efficiency is low, The overhead is large, and it is used for cross network process interaction. Although there is no need to copy the shared memory.

This is a security issue. As an open platform with many developers, Android has a wide range of application sources. It is very important to ensure terminal security. The traditional IPC communication mode has no measures and basically depends on the upper layer protocol. First, it is unable to confirm the other party's reliable identity. Android assigns its own UID to each installed application, Therefore, the UID of the process is an important symbol to identify the process identity. If the traditional IPC wants to send a similar UID, it can only be placed in the data packet, but it is also easy to be intercepted. For malicious attacks, the socket needs to expose its own ip and port. Knowing these malicious programs, it can be accessed arbitrarily.

To sum up, Android needs a process communication mode with high efficiency and high security, that is, binder. Binder only needs one copy, and its performance is second only to shared memory. Moreover, the traditional C/S structure is adopted, and its stability is needless to say. Sending and adding UID/PID has high security.

2.2 Binder implementation mechanism
2.2.1 process isolation
We know that there is no direct interaction between processes. Each process enjoys its own data. In order to ensure its own security and stability, the operating system separates the system Kernel space from the user space to ensure that the collapse of user program processes will not affect the whole system. In short, the Kernel space is the space where the system Kernel runs, User space is the space where user programs run. In order to ensure security, they are isolated, so the process in user space needs to drive the whole process through Kernel space.

2.2.2 C/S structure
Binder is based on the C/S mechanism. To implement this mechanism, the server must have a specific node to receive the client's request, that is, the entry address. For example, enter a web address, resolve the corresponding ip through DNS, and then access it. This is the node address provided by the server. Binder is different from the traditional C/S, Binder itself serves as the node provided in the server. The client gets the address corresponding to the binder entity object to access the server. For the client, how to get this address and establish the whole channel is the key to the whole interaction. Moreover, as an entity in the server, binder provides a series of methods to realize the requests between the server and the client, As long as the client gets the reference, or a reference with the proxy object of the method, it can communicate.

A paragraph from Android Bander design and Implementation - design is quoted to summarize:

The introduction of object-oriented idea transforms the inter process communication into the method of calling a binder object through the reference of a binder object. Its uniqueness is that the binder object is an object that can be referenced across processes. Its entity is located in a process, but its references are all over the processes of the system. The most attractive thing is that this reference, like the reference in java, can be strong or weak, and can be passed from one process to other processes, so that everyone can access the same Server, just like assigning an object or reference to another reference. Binder blurs the process boundary and weakens the inter process communication process. The whole system seems to run in the same object-oriented program. All kinds of binder objects and scattered references are like glue for bonding various applications, which is also the original meaning of binder in English.

2.2.3 Binder communication model
Based on the C/S structure, binder defines four roles: Server, Client, ServerManager and binder driver. The first three roles are in user space, that is, they cannot interact directly with each other. Binder driver belongs to the kernel space and belongs to the core of the whole communication. Although it is called driver, it has little to do with hardware, Only the implementation method is similar to that of the driver. The driver is responsible for the establishment of binder communication between processes, binder transfer between processes, binder reference count management, data packet transfer and interaction between processes and a series of low-level support.

What is the role of ServerManager?

We know that ServerManager is also a process belonging to user space. Its main function is to serve as a bridge between Server and client. Client can get the reference of Binder entity in Server through ServerManager. This may be a little vague. For a simple example, we visit www.baidu.com and Baidu home page will be displayed. First of all, we know, This page must be published on a Baidu Server. DNS resolves the corresponding ip address through your address, then accesses the corresponding page, and then returns the data to the client to complete the interaction. This is very similar to Binder's C/S. DNS here is the corresponding ServerManager. First, Binder entity objects in the Server register their own references (i.e. ip addresses) to ServerManager. Clients bind to this reference through a specific key (i.e. Baidu website). ServerManager maintains a MAP like table for one-to-one correspondence, Through this key, you can get the reference of Binder in Server from ServerManager. Corresponding to Android development, we know that many system services interact with AMS through Binder, such as obtaining volume services:

AudioManager am = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);

Careful friends should find that ServerManager and server are also two different processes. If server wants to register with ServerManager, it also involves inter process communication. At present, inter process communication also needs to be used to realize inter process communication. Don't worry, Binder's cleverness is that when ServerManager is used as the server end, The Binder provided by it is special. It has no name and does not need to be registered. When a process uses Binder_ SET_ CONTEXT_ When the Mgr command registers itself as SMgr, the Binder driver will automatically create a Binder entity for it. The Binder reference is fixed to 0 in all clients without obtaining it by other means. In other words, if a server wants to register its Binder with the ServerManager, it must communicate with the Binder of the ServerManager through the reference number 0. A friend will ask again. The server and the client belong to two different processes. How can the client get the objects in the server? You might as well take a look at the following interaction diagram first

It can be clearly seen from the above figure that the whole interaction process. The reference to the binder originally obtained from SM is returned to the client as a proxy object after being processed by the binder driver layer. In fact, if the client and server are in the same process, the current binder object is returned. If the client and server are not in the same process, What is returned to the client is a proxy object. For this, you can see the source code if you are interested.

2.2.4 Binder role positioning
Binder essentially only provides a way of communication, which has nothing to do with what we want to implement. In order to implement this service, we need to define some interfaces so that clients can call the service remotely. Because it is cross process, we need to design the proxy mode to implement the interface function based on the interface function bit, client and server, Server is the real implementation of the service, and the client is a remote call.

From the perspective of Server process, Binder is an existing entity object. The client is driven by Binder through transact() function, and finally called back to onTransact() function of Binder entity.
From the perspective of Client process, Binder refers to the Binder proxy object, which is a remote proxy of Binder entity object and interacts through Binder driver
3. Handwriting process communication
After all that has been said above, everyone is tired. Let's deepen our understanding of Binder in the form of code. If inter process communication is involved in daily development, our first thought may be aidl, but I don't know if there are any friends who feel the same as me.. Writing aidl for the first time is deceptive. Through the. aidl file, the compiler automatically generates code and generates a java file. There are Stub classes and Proxy classes in it. We don't understand the mechanism. It's really inconvenient for us to understand and learn. In order to deepen our understanding, we abandon aidl and write a communication code.

First, we need to define an interface service, that is, the capabilities of the above server to provide to the client, and define an interface to inherit IInterface, which represents the capabilities of the server

public interface PersonManger extends IInterface {

void addPerson(Person mPerson);
List<Person> getPersonList();

}
Copy code
Next, we will define a Binder entity object in the Server. First, we must inherit Binder, and then we need to implement the service interface defined above

public abstract class BinderObj extends Binder implements PersonManger {

public static final String DESCRIPTOR = "com.example.taolin.hellobinder";
public static final int TRANSAVTION_getPerson = IBinder.FIRST_CALL_TRANSACTION;
public static final int TRANSAVTION_addPerson = IBinder.FIRST_CALL_TRANSACTION + 1;
public static PersonManger asInterface(IBinder mIBinder){
    IInterface iInterface = mIBinder.queryLocalInterface(DESCRIPTOR);
    if (null!=iInterface&&iInterface instanceof PersonManger){
        return (PersonManger)iInterface;
    }
    return new Proxy(mIBinder);
}
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
    switch (code){
        case INTERFACE_TRANSACTION:
            reply.writeString(DESCRIPTOR);
            return true;

        case TRANSAVTION_getPerson:
            data.enforceInterface(DESCRIPTOR);
            List<Person> result = this.getPersonList();
            reply.writeNoException();
            reply.writeTypedList(result);
            return true;

        case TRANSAVTION_addPerson:
            data.enforceInterface(DESCRIPTOR);
            Person arg0 = null;
            if (data.readInt() != 0) {
                arg0 = Person.CREATOR.createFromParcel(data);
            }
            this.addPerson(arg0);
            reply.writeNoException();
            return true;
    }
    return super.onTransact(code, data, reply, flags);

}

@Override
public IBinder asBinder() {
    return this;
}

}
Copy code
First, let's look at the asInterface method. The IBinder object passed from the Binder driver. Find the local Binder object through the queryLocalInterface method. If the returned is PersonManger, it means that the client and the server are in the same process and return directly. If not, return to a proxy object.

Of course, as a proxy object, you also need to implement the service interface

public class Proxy implements PersonManger {

private IBinder mIBinder;
public Proxy(IBinder mIBinder) {
    this.mIBinder =mIBinder;
}

@Override
public void addPerson(Person mPerson) {
    Parcel data = Parcel.obtain();
    Parcel replay = Parcel.obtain();

    try {
        data.writeInterfaceToken(DESCRIPTOR);
        if (mPerson != null) {
            data.writeInt(1);
            mPerson.writeToParcel(data, 0);
        } else {
            data.writeInt(0);
        }
        mIBinder.transact(BinderObj.TRANSAVTION_addPerson, data, replay, 0);
        replay.readException();
    } catch (RemoteException e){
        e.printStackTrace();
    } finally {
        replay.recycle();
        data.recycle();
    }
}

@Override
public List<Person> getPersonList() {
    Parcel data = Parcel.obtain();
    Parcel replay = Parcel.obtain();
    List<Person> result = null;
    try {
        data.writeInterfaceToken(DESCRIPTOR);
        mIBinder.transact(BinderObj.TRANSAVTION_getPerson, data, replay, 0);
        replay.readException();
        result = replay.createTypedArrayList(Person.CREATOR);
    }catch (RemoteException e){
        e.printStackTrace();
    } finally{
        replay.recycle();
        data.recycle();
    }
    return result;
}

@Override
public IBinder asBinder() {
    return null;
}

}
Copy code
The proxy object here is essentially the proxy service that client gets at last. Through this, you can communicate with Server. First, serialize data through Parcel, then call remote.transact() to transfer the method code and data to the past, and the corresponding callbacks will be in onTransact() in Server.

Then there is our Server process. The onBind method returns the mStub object, that is, the Binder entity object in the Server

public class ServerSevice extends Service {

private static final String TAG = "ServerSevice";
private List<Person> mPeople = new ArrayList<>();

@Override
public void onCreate() {
    mPeople.add(new Person());
    super.onCreate();
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return mStub;
}
private BinderObj mStub = new BinderObj() {
    @Override
    public void addPerson(Person mPerson) {
        if (mPerson==null){
            mPerson = new Person();
            Log.e(TAG,"null obj");
        }
        mPeople.add(mPerson);
        Log.e(TAG,mPeople.size()+"");
    }

    @Override
    public List<Person> getPersonList() {
        return mPeople;
    }
};

}
Copy code
Finally, in the client process, bindService passes in a ServiceConnection object. When establishing a connection with the server, we return a proxy object through the asInterface method of BinderObj defined by us, and then call the method to interact

public class MainActivity extends AppCompatActivity {

private boolean isConnect = false;
private static final String TAG = "MainActivity";
private PersonManger personManger;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    start();
    findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (personManger==null){
                Log.e(TAG,"connect error");
                return;
            }
            personManger.addPerson(new Person());
            Log.e(TAG,personManger.getPersonList().size()+"");
        }
    });
}

private void start() {
    Intent intent = new Intent(this, ServerSevice.class);
    bindService(intent,mServiceConnection,Context.BIND_AUTO_CREATE);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.e(TAG,"connect success");
        isConnect = true;
        personManger = BinderObj.asInterface(service);
        List<Person> personList = personManger.getPersonList();
        Log.e(TAG,personList.size()+"");
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.e(TAG,"connect failed");
        isConnect = false;
    }
};

}
Copy code
In this way, the interaction between processes will be completed at one time. Does it feel that it is not as difficult as expected? Finally, it is recommended that you manually realize the communication between Client and Server processes without the help of AIDL to deepen your understanding of Binder communication process.

Posted by demetri007 on Wed, 10 Nov 2021 13:20:35 -0800