There are a lot of articles about Binder on the Internet, and many great experts have tried to show it to you. All of them are of high level, but unfortunately, they are not friendly to developers. Why? Because they’re all down to top, not top to down. As app developers, we definitely prefer a top-down presentation. This article is an attempt and can be read as a precursor to Weishu’s article. Disclaimer: This article is the author’s original, welcome to freely reprint. Please do not change the content of the article and write my account milter and the link to the original article at the beginning.

First, identify the problem

This article revolves around the problem of how to pass two integers from process A to process B, which adds the two numbers and returns the result to process A.

Overview of Binder mechanism framework

Android gives us a package of solutions for cross-process communication. Let’s take a look at how the scheme is designed in general: Process A uses the bindService method to bind A service registered in process B. After receiving the bindService request from process A, the system invokes the onBind method of the corresponding service in process B. The onBind method returns A special object, and the system receives this special object. We then generate A proxy object for this particular object and return it to process A, which receives it in the onServiceConnected method of the ServiceConnection callback. With the help of this proxy object, we can solve our problem.

Three, step by step analysis

Step 1: Process B creates Binder objects

Implement a special object for process B that is returned by the onBind method of the service mentioned earlier. This object has two properties:

  • One is the ability to accomplish a specific task (in our case, the ability to add two integers and return the result)
  • One is the ability to be transmitted across processes.

What kind of object has this ability? The answer is objects of the Binder class. Let’s look at how Binder has these two capabilities. Binder has the following key methods:

public class Binder implement IBinder{ void attachInterface(IInterface owner, String Descriptor) IInterface queryLocalInterface(String Descriptor) // Inherited from IBinder Boolean onTransact(int code, Parcel data, Parcel Reply, int flags) . final class BinderProxy implements IBinder { ...... // An inner class for Binder. }}Copy the code

Binder has the ability to be transported across processes because it implements the IBinder interface. The system provides cross-process transport for every object that implements this interface, which is a great benefit of the system. The ability of a Binder to perform a specific task is acquired through its attachInterface method, which we can simply understand as storing (Descriptor, owner) as a (key,value) pair into a Map object in the Binder object. Binder objects can hold a reference to an IInterface object (owner) through the attachInterface method and rely on it for the ability to perform a specific task. The queryLocalInterface method can be thought of as finding the corresponding IInterface object based on the key value (that is, the parameter descriptor). Forget about the onTransact method for now, we’ll talk about it later.

Ok, now let’s implement IInterface and Binder objects with the following code outline:

Public class Plus Implement IInterface {public int add(int a,int b){return a+b; } public IBinder asBinder(){// implement IInterface only method, return null; } } IInterface owner = new Plus(); public class Stub extends Binder { @Override boolean onTransact(int code, Parcel data, Parcel reply, int flags){ ...... // We overwrite the onTransact method. }... } Binder binder = new Stub(); binder.attachIInterface(owner,"PLUS TWO INT");Copy the code

Step 2: Process A receives process B’s Binder objects

Ok, now we have this particular object binder, which is returned by the onBind method in process B’s service, return binder; Here’s where the magic happens. The system will first receive the Binder object. It will then generate an object of class BinderProxy, let’s call it BinderProxy, and return this object to process A. Now process A finally receives A BinderProxy object from its onServiceConnected method. . Binder class summary is posted again for the convenience of the following explanation.

public class Binder implement IBinder{ void attachInterface(IInterface owner, String Descriptor) IInterface queryLocalInterface(String Descriptor) // Inherited from IBinder Boolean onTransact(int code, Parcel data, Parcel Reply, int flags) final class BinderProxy implements IBinder { IInterface queryLocalInterface(Stringdescriptor) { return null ; // Pay attention to this line of code! // This will be covered below. This line of code is an example, not source code. }... }}Copy the code

Process A is excited because it thinks it has received A Binder object. It can’t wait to get the owner object of the Binder through queryLocalInterface and use its addition function to perform addition calculations. The result? First, binderproxy. QueryLocalInterface (PLUS TWO “INT”) call is legal, because in the queryLocalInterface method is IBinder method, BinderProxy and Binder implement the IBinder interface. However, the BinderProxy object obviously does not have an owner object because it has no attachInterface method at all (this is for Binder). So, it is conceivable that process A binderproxy. QueryLocalInterface (” PLUS TWO INT “) call returns will be A null sample code (see above).

Step 3: Process A initiates A request using the object passed by process B

Process A got angry. I needed Binder and the owner to do the addition for me. Process B gave me A fake Binderproxy (apparently, it wronged process B because of the system). The BinderProxy object says while process A is smoking: “Don’t be mad at process A, I’m just A proxy for binder objects, but I’m not A meager. You give me your data (two ints) and the operation you want to do (owner.add) through my transact method (which is defined in the IBinder interface). I can ask binder objects for the functionality you need, and when binder objects give me the results, I’ll give them to you.” Process A submits the request through the Transact method of the BinderProxy object. The code outline is as follows:

android.os.Parcel data = android.os.Parcel.obtain(); android.os.Parcel reply = android.os.Parcel.obtain(); int _result; data.writeInterfaceToken("PLUS TWO INT"); data.writeInt(a); data.writeInt(b); binderproxy.transact(1, data, reply, 0); // For simplicity, leave the last 0 out for nowCopy the code

A quick explanation of the above code. Data is used to write process A’s data (that is, integers A and B), and reply is prepared to receive the results. The first parameter in the Transact method is the integer 1, which is A convention between process A and process B. 1 means that process B is expected to add the data passed in by process A. This convention can also be defined in Stub classes, as follows: public static final int ADD = 1; Binderproxy.transact (1, data, reply, 0); Replace 1 with stub.add. Stub.add can be any integer, and we chose 1 for simplicity.

Step 4: Process B receives and processes the request from process A

When the binderProxy.transact call occurs, the system notices that BinderProxy wants to perform an operation with its real binder object (see! Binder and BinderProxy are always in the system. . Binder will receive data from BinderProxy in onTransact (stub.add,data,reply,0) and fetch the data from process A. Stub.add determines that process A wants it to ADD, so it adds and writes the result back to Reply. The code outline is as follows:

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } // Boilerplate code, don't worry, the next line is important. case Stub.ADD: { data.enforceInterface("PLUS TWO INT"); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this.queryLocalIInterface("PLUS TWO INT") .add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; } } return super.onTransact(code, data, reply, flags); }Copy the code

Explain the above code briefly. WriteInterfaceToken (“PLUS TWO INT”); writeInterfaceToken(“PLUS TWO INT”); This means that process B calls the queryLocalIInterface method with PLUS TWO INT in its Binder object to find the corresponding IInterface object that process A wants to perform. It is easy to understand that stub.add represents the ADD method in owner. This is a two-level lookup where PLUS TWO INT determines that the owner is to perform the function and stub.add determines that the ADD method in owner is to perform the function.

Step 5: Process A obtains the processing result returned by process B

After process B writes the results to Reply, process A can read the results from Reply. The code outline is as follows:

binderproxy.transact(Stub.ADD, data, reply, 0); 
reply.readException(); 
_result = reply.readInt();Copy the code

Well, with Android’s Binder mechanism, we’ve managed to solve the problem at the beginning of this article. If you have a deeper understanding of Binder through this article, it clears up your lingering confusion. Thumb up!!!!