In our last article, Understanding Android Binder with AIDL — Binder In Context, we looked at why Binder was used and analyzed the Binder mechanism, In this chapter, we will continue to experience the use and principles of Binder at the application layer through the use of AIDL.

AIDL uses steps

1. Create the userManager. aidl interface file to declare the capabilities of the remote Service on the Server

UserManager. Aidl:

package com.me.guanpj.binder;

import com.me.guanpj.binder.User;
// Declare any non-default types here with import statements

interface UserManager {
    void addUser(in User user);

    List<User> getUserList();
}
Copy the code

For object references, you also need to introduce entity classes

User. Aidl:

// User.aidl
package com.me.guanpj.binder;

// Declare any non-default types here with import statements

parcelable User;
Copy the code

Cross-process transfer objects must implement the Parcelable interface

User.java

public class User implements Parcelable {
    public int id;
    public String name;

    public User() {}

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    protected User(Parcel in) {
        id = in.readInt();
        name = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            returnnew User[size]; }}; }Copy the code

The generated UserManager class looks like this:

UserManager. Java:

package com.me.guanpj.binder;
// Declare any non-default types here with import statements

public interface UserManager extends android.os.IInterface
{
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.me.guanpj.binder.UserManager
    {
        private static final java.lang.String DESCRIPTOR = "com.me.guanpj.binder.UserManager";
        /** Construct the stub at attach it to the interface. */
        public Stub()
        {
            this.attachInterface(this, DESCRIPTOR);
        }
        /**
         * Cast an IBinder object into an com.me.guanpj.binder.UserManager interface,
         * generating a proxy if needed.
         */
        public static com.me.guanpj.binder.UserManager asInterface(android.os.IBinder obj)
        {
            if ((obj==null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if(((iin! =null)&&(iin instanceof com.me.guanpj.binder.UserManager))) {return ((com.me.guanpj.binder.UserManager)iin);
            }
            return new com.me.guanpj.binder.UserManager.Stub.Proxy(obj);
        }
        @Override public android.os.IBinder asBinder()
        {
            return this;
        }
        @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
        {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code)
            {
                case INTERFACE_TRANSACTION:
                {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_addUser:
                {
                    data.enforceInterface(descriptor);
                    com.me.guanpj.binder.User _arg0;
                    if((0! =data.readInt())) { _arg0 = com.me.guanpj.binder.User.CREATOR.createFromParcel(data); }else {
                        _arg0 = null;
                    }
                    this.addUser(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getUserList:
                {
                    data.enforceInterface(descriptor);
                    java.util.List<com.me.guanpj.binder.User> _result = this.getUserList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                default:
                {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }
        private static class Proxy implements com.me.guanpj.binder.UserManager
        {
            private android.os.IBinder mRemote;
            Proxy(android.os.IBinder remote)
            {
                mRemote = remote;
            }
            @Override public android.os.IBinder asBinder()
            {
                return mRemote;
            }
            public java.lang.String getInterfaceDescriptor()
            {
                return DESCRIPTOR;
            }
            @Override public void addUser(com.me.guanpj.binder.User user) throws android.os.RemoteException
            {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if((user! =null)) { _data.writeInt(1); user.writeToParcel(_data, 0); }else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
                    _reply.readException();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
            @Override public java.util.List<com.me.guanpj.binder.User> getUserList() throws android.os.RemoteException
            {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.me.guanpj.binder.User> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getUserList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.me.guanpj.binder.User.CREATOR);
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }
        static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getUserList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
    public void addUser(com.me.guanpj.binder.User user) throws android.os.RemoteException;
    public java.util.List<com.me.guanpj.binder.User> getUserList() throws android.os.RemoteException;
}
Copy the code

3. Create the Service, implement the UserManager.Stub class, and return the instance of the implementation class in onBind

MyService. Java:

public class MyService extends Service {

    class UserManagerNative extends UserManager.Stub {

        List<User> users = new ArrayList<>();

        @Override
        public void addUser(User user) {
            Log.e("gpj"."Process:" + Utils.getProcessName(getApplicationContext())
                    + ", thread: + Thread.currentThread().getName() + "————" + "Server execution addUser");
            users.add(user);
        }

        @Override
        public List<User> getUserList() {
            Log.e("gpj"."Process:" + Utils.getProcessName(getApplicationContext())
                    + ", thread: + Thread.currentThread().getName() + "————" + "Executive getUserList Server");
            return users;
        }
    }

    private UserManagerNative mUserManagerNative = new UserManagerNative();

    @Override
    public IBinder onBind(Intent intent) {
        Log.e("gpj"."Process:" + Utils.getProcessName(getApplicationContext())
                + ", thread: + Thread.currentThread().getName() + "————" + "Server onBind");
        returnmUserManagerNative; }}Copy the code

4. In the Activity as a Client, bind the remote Service and get the proxy object of the Server

5. Invoke Server methods through the Server proxy object

MainActivity. Java:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    Button btnBind;
    Button btnAddUser;
    Button btnGetSize;
    TextView tvResult;
    IUserManager mUserManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnBind = (Button) findViewById(R.id.btn_bind);
        btnAddUser = (Button) findViewById(R.id.btn_add_user);
        btnGetSize = (Button) findViewById(R.id.btn_get_size);

        btnBind.setOnClickListener(this);
        btnAddUser.setOnClickListener(this);
        btnGetSize.setOnClickListener(this);

        tvResult = (TextView) findViewById(R.id.txt_result);
    }

    @Override
    protected void onDestroy() {
        unbindService(mConn);
        super.onDestroy();
    }

    private ServiceConnection mConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e("gpj"."Process:" + Utils.getProcessName(getApplicationContext())
                    + ", thread: + Thread.currentThread().getName() + "————" + "Client onServiceConnected"); mUserManager = UserManagerImpl.asInterface(service); Try {// Register the remote service death notification service.linkToDeath(mDeathRecipient, 0); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { mUserManager = null; }}; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if(mUserManager ! = null) { mUserManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mUserManager = null; // Rebind the servicebindService(); }}}; @Override public void onClick(View v) { switch (v.getId()) {case R.id.btn_bind:
                bindService();
                break;
            case R.id.btn_add_user:
                if(null ! = mUserManager) { try { Log.e("gpj"."Thread:" + Thread.currentThread().getName() + "————" +"The Client invoking addUser");
                        mUserManager.addUser(new User(111, "gpj")); } catch (RemoteException e) { e.printStackTrace(); }}else {
                    Toast.makeText(MainActivity.this, "Bind Service before calling method", Toast.LENGTH_LONG).show();
                }
                break;
            case R.id.btn_get_size:
                if(null ! = mUserManager) { try { Log.e("gpj"."Thread:" + Thread.currentThread().getName() + "————" +"The Client calls getUserList");
                        List<User> userList = mUserManager.getUserList();
                        tvResult.setText("getUserList size:" + userList.size());
                        Log.e("gpj"."Thread:" + Thread.currentThread().getName() + "————" +"Call result:"+ userList.size()); } catch (RemoteException e) { e.printStackTrace(); }}else {
                    Toast.makeText(MainActivity.this, "Bind Service before calling method", Toast.LENGTH_LONG).show();
                }
                break;
            default:
        }
    }

    private void bindService() {
        Intent intent = new Intent();
        intent.setAction("com.me.guanpj.binder");
        intent.setComponent(new ComponentName("com.me.guanpj.binder"."com.me.guanpj.binder.MyService"));

        Log.e("gpj"."Process:" + Utils.getProcessName(getApplicationContext())
                + ", thread: + Thread.currentThread().getName() + "————" + "Start service binding");
        bindService(intent, mConn, Context.BIND_AUTO_CREATE); }}Copy the code

AIDL implementation process

To make it easier to understand, here is a Demo of AIDL implementation: The Activity as a Client realizes data interaction with the remote Service as a Server. After binding the remote Service, click AddUser and the Service will add the User object passed in by the Client to the list. Click GetSize and the remote Service returns the length of the list to the client. It is recommended to view or run the project source code before continuing:

After the userManager. aidl file is created in the project, the system automatically generates an interface class with UserManager. Java in the build directory that inherits the IInterface interface. Stubs inherit from Binder and implement the UserManager interface. They also have a static internal Proxy class that inherits from UserManager.

The reason for this nesting is to avoid having multiple.aidl files that automatically generate the same class names. To improve code readability, We disassembled and renamed the generated UserManager and Stub classes to the IUserManager and UserManagerImpl classes and added comments or logs to the key methods.

IUserManager. Java:

Public interface IUserManager extends Android.os. IInterface {// Uniquely identifies static final Java.lang.String DESCRIPTOR ="com.me.guanpj.binder.IUserManager"; Int TRANSACTION_addUser = (Android.os.ibinder.first_call_TRANSACTION + 0); int TRANSACTION_addUser = (Android.os.ibinder.first_call_transaction + 0); int TRANSACTION_getUserList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); / / Server has the ability to void addUser (User User) throws. Android OS. RemoteException; List<User> getUserList() throws android.os.RemoteException; }Copy the code

UserManagerImpl. Java:

public abstract class UserManagerImpl extends Binder implements IUserManager {
    /**
     * Construct the mLocalStub at attach it to the interface.
     */
    public UserManagerImpl() { this.attachInterface(this, DESCRIPTOR); Public static IUserManager asInterface(Android.os.ibinder obj) {/** * public static IUserManager asInterface(Android.os.ibinder obj) {if ((obj == null)) {
            returnnull; } // Find the local object Android.os. IInterface iIN = obj.queryLocalInterface(DESCRIPTOR);if(((iin ! = null) && (iin instanceof IUserManager))) { Log.e("gpj"."Thread:" + Thread.currentThread().getName() + "————" + "Return local object");
            return ((IUserManager) iin);
        }
        Log.e("gpj"."Thread:" + Thread.currentThread().getName() + "————" + "Return proxy object");
        return new UserManagerImpl.Proxy(obj);
    }

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

    @Override
    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;
            }
            case TRANSACTION_addUser: {
                Log.e("gpj"."Thread:" + Thread.currentThread().getName() + "————" + "Local object executes addUser with Binder");
                data.enforceInterface(DESCRIPTOR);
                User arg0;
                if((0! = data. ReadInt ())) {/ / remove the client data passed arg0 = User. The CREATOR. CreateFromParcel (data); }else{ arg0 = null; } // Call the Binder native object this.addUser(arg0); reply.writeNoException();return true;
            }
            case TRANSACTION_getUserList: {
                Log.e("gpj"."Thread:" + Thread.currentThread().getName() + "————" + "Local object executes getUserList with Binder"); data.enforceInterface(DESCRIPTOR); // Call Binder local object List<User> result = this.getUserList(); reply.writeNoException(); // Return the result to the client reply.writetypedList (result);return true;
            }
            default:
                break;
        }
        return super.onTransact(code, data, reply, flags);
    }

    private static class Proxy implements IUserManager {
        private android.os.IBinder mRemote;

        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }

        @Override
        public android.os.IBinder asBinder() {
            return mRemote;
        }

        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        @Override
        public void addUser(User user) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
               if(user ! = null) { _data.writeInt(1); user.writeToParcel(_data, 0); }else {
                   _data.writeInt(0);
               }
                Log.e("gpj"."Thread:" + Thread.currentThread().getName() + "————" + "Proxy object calls addUser through Binder");
                mRemote.transact(UserManagerImpl.TRANSACTION_addUser, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }

        @Override
        public List<User> getUserList() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            List<User> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                Log.e("gpj"."Thread:" + Thread.currentThread().getName() + "————" + "Proxy object calls getUserList with Binder");
                mRemote.transact(UserManagerImpl.TRANSACTION_getUserList, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(User.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return_result; }}}Copy the code

Before we go further, a few concepts should be understood:

  1. IInterface : As you can see from the comments, interfaces that declare (automatically generated or manually created) the nature of AIDL must inherit this interface, which has only one IBinder asBinder() method, The class that implements it represents that it can transfer across processes (Binder native objects) or holds references to objects that can transfer across processes (Binder proxy objects).
  2. IUserManager: This is also an interface that inherits the IInterface class and declares the capabilities that the Server promises to the Client
  3. IBinder: An interface that implements cross-process transmission. The driver recognizes IBinder type data as it flows through the driver and automatically converts Binder local objects and proxy objects.
  4. Binder : The BinderProxy class is its inner class, which is the local proxy for the Server side Binder objects. Both inherit the IBinder interface, so they can be transferred across processes. Binder drivers automatically convert these two objects when they are transferred across processes.
  5. UserManagerImpl: It inherits Binder and implements the IInterface interface, indicating that it is a Binder native object on the Server side and has the capabilities promised by the Server to clients.

Start with the callback method after the service is bound to MainActivity:

private ServiceConnection mConn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mUserManager = UserManagerImpl.asInterface(service); Try {// Register the remote service death notification service.linkToDeath(mDeathRecipient, 0); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { mUserManager = null; }};Copy the code

In the onServiceConnected ected argument, the first is the name of the Service component, indicating which Service is started. The focus is on the second argument of type IBinder, in the onBind method of service.java. Binder drivers already have UserManagerNative instances returned to the Server side:

private UserManagerNative mUserManagerNative = new UserManagerNative();

@Override
public IBinder onBind(Intent intent) {
    return mUserManagerNative;
}
Copy the code

Therefore, when the Service is bound, the Binder driver decides whether to return a local object or a proxy object to the client based on the process the Service is in. When the Service and MainActivity are in the same process, OnServiceConnected objects return Binder native objects — UserManagerNative objects — to the client; When a Service is running in a different process, it returns a BinderProxy object.

Then, after passing the IBinder object to UserManagerImpl’s asInterface method and returning the IUserManager interface, the asInterface method is implemented as follows:

Public static IUserManager asInterface(Android.os.ibinder obj) {public static IUserManager asInterface(android.os.ibinder obj) {if ((obj == null)) {
        returnnull; } // Find the local object Android.os. IInterface iIN = obj.queryLocalInterface(DESCRIPTOR);if(((iin ! = null) && (iin instanceof IUserManager))) {return ((IUserManager) iin);
    }
    return new UserManagerImpl.Proxy(obj);
}
Copy the code

First, the queryLocalInterface method of the IBinder object is called according to DESCRIPTOR, so it’s up to the implementation class of the IBinder to handle this method:

Implementation in Binder classes:

Public @nullable IInterface queryLocalInterface(@nonNULL String Descriptor) {// Check that mDescriptor is the same as parameter Descriptor. Return mOwnerif(mDescriptor ! = null && mDescriptor.equals(descriptor)) {return mOwner;
    }
    return null;
}
Copy the code

So when does this mOwner and mDescriptor get assigned? The answer is in the Binder subclass UserManagerImpl constructor:

public UserManagerImpl() {// Inject UserManagerImpl and DESCRIPTOR into parent (Binder) this.attachInterface(this, DESCRIPTOR); }Copy the code

Binder$BinderProxy

BinderProxy is not a Binder local object, but a Binder local proxy, so queryLocalInterface returns NULL:

public IInterface queryLocalInterface(String descriptor) {
    return null;
}
Copy the code

If the obj.queryLocalInterface(DESCRIPTOR) method has a return value and an object of type IUserManager, it is a Binder native object. Otherwise, use the UserManagerImpl$Proxy class to wrap it and return it. The Proxy class also implements the IUserManager interface, so in the Client’s eyes, it also has the capabilities promised to the Client by the Server. How does the wrapped object interact with the Server?

First, it saves the BinderProxy object:

Proxy(android.os.IBinder remote) {
    mRemote = remote;
}
Copy the code

Then, implement the IUserManager method:

@Override
public void addUser(User user) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        if(user ! = null) { _data.writeInt(1); // Write the value of the user object to _data user.writetoparcel (_data, 0); }else{ _data.writeInt(0); } / / mRemote interact through transact with the Server. The transact (UserManagerImpl TRANSACTION_addUser, _data while forming, _reply, 0). _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public List<User> getUserList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); List<User> _result; try { _data.writeInterfaceToken(DESCRIPTOR); / / interact through transact with Server mRemote. Transact (UserManagerImpl TRANSACTION_getUserList, _data while forming, _reply, 0). _reply.readException(); / / access Server return values and process transformation _result = _reply. CreateTypedArrayList (User. CREATOR); } finally { _reply.recycle(); _data.recycle(); }return _result;
}
Copy the code

Transact method Server. MRemote is a BinderProxy type. In the BinderProxy class, mRemote is a BinderProxy type. The final call is the transactNative method:

public native boolean transactNative(int code, Parcel data, Parcel reply, int flags) throws RemoteException;
Copy the code

The final implementation is in the Native layer, where the Binder driver wakes up the Server process via an IOCtl system call and calls the onTransact function on the Server Native object:

@Override
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;
        }
        case TRANSACTION_addUser: {
            data.enforceInterface(DESCRIPTOR);
            User arg0;
            if((0! = data. ReadInt ())) {/ / remove the client data passed arg0 = User. The CREATOR. CreateFromParcel (data); }else{ arg0 = null; } // Call the Binder native object this.addUser(arg0); reply.writeNoException();return true;
        }
        caseTRANSACTION_getUserList: { data.enforceInterface(DESCRIPTOR); // Call Binder local object List<User> result = this.getUserList(); reply.writeNoException(); // Return the result to the client reply.writetypedList (result);return true;
        }
        default:
            break;
    }
    return super.onTransact(code, data, reply, flags);
}
Copy the code

In the Server process, onTransact decides which method to call based on the method code passed by the Client. The Binder driver returns the result to the Client.

conclusion

Back to the onServiceConnected callback method, the Client needs to interact with the Server once the service is connected. If the Server is in the same process as the Client, the Client can call the Server’s local object directly. When they are not in the same process, the Binder driver automatically converts the Server’s local objects into BinderProxy objects, which, after a layer of wrapping, returns a new proxy object to the Client. In this way, the whole IPC process is completed.

Refer to the article

Binder principle analysis for Android application engineers

Binder Learning Guide

The code in this article has been uploaded to my Github. If you have any questions or different opinions about the content of this article, please leave a comment and we will discuss it together.