Android IPC series (two): AIDL source code analysis

Android IPC series (two): AIDL source code analysis

How to open multi-process in Android

Let s talk about the situation where one application opens multiple processes. I don t discuss multiple applications and multiple processes. Android has only one way to open multiple processes. In the AndroidManifest file, specify the android:process attribute for the four major components. In fact, there is another unconventional Method to fork a process in the native layer through JNI, this is a special case

  <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:name=".SecondActivity"
            android:process=":remote"/>
        <activity
            android:name=".ThreeActivity"
            android:process="om.baidu.bpit.aibaidu.ipc.remote.aa"/>
 

We have specified the process attribute for SecondActivity and ThreeActivity respectively, and their attribute values are different, so now there are three processes, the default process of MainActivity, the default process name is the package name, and there are two processes that we specify by ourselves through the shell command To see if there are any of these processes

$ adb shell ps | grep om.baidu.bpit.aibaidu.ipc
u0_a59    2343  204   1606528 98700          0 0000000000 S com.baidu.bpit.aibaidu.ipc
u0_a59    2360  204   1609616 97952          0 0000000000 S com.baidu.bpit.aibaidu.ipc:remote
u0_a59    2388  204   1766960 116352          0 0000000000 S om.baidu.bpit.aibaidu.ipc.remote.aa
 

We found that SecondActivity and ThreeActivity have different ways of specifying process names. One is android:process=":remote" and the other is android:process="om.baidu.bpit.aibaidu.ipc.remote.aa . Is there any difference between the two?

There are two differences

  • ":" means to add the package name to the current process name, and the other is a complete naming method
  • The process at the beginning of ":" belongs to the private process of the current application. Other application components cannot run in the same process with him. The complete package name belongs to the global process. The application can run in the same process with him through ShareUid.

The Android system assigns a unique Uid to each process. Only applications with the same Uid can share data, such as data directories, component information, etc., regardless of whether they run in the same process or not, if they run in the same process, in addition to sharing data Directory, component information, and shared memory

Problems encountered by multiple processes

Each process will have an independent virtual machine. Different virtual machines allocate different space in the memory, so as long as the shared memory is needed, the operation of sharing data will fail.

  • Static members and singletons are completely invalid
  • The thread synchronization mechanism is completely invalid
  • The reliability of SharePreference is reduced, because the bottom layer of sp is realized by reading and writing xml, then concurrent reading and writing problems
  • Application created multiple times

What is Binder

  • Intuitively, Binder is a class in Android that implements IBinder
  • From an IPC perspective, Binder is a way of cross-process communication in Android
  • Binder can also be understood as a virtual physical device, his driver is dev/binder, this communication method does not exist in Linux
  • From the perspective of Android Framework, Binder is a bridge between ServerManager to connect various Managers (such as ActivityManager) and ManagerService
  • From the Android application layer, it is the communication medium between the server and the client

IPC method in Android

  • Using Bundle, the four major components all support Bundle. Because Bundle implements the Parcelable interface, data can be passed in the process, and we can start the Bundle of another process in one process to communicate between processes.
  • Use file sharing
  • Use Messager
  • Use Aidl
  • Use ContentProvider
  • Use socket

Analyze the AIDL source code

In the previous blog, we analyzed how to use AIDL. In this article, we analyze what is generated by the previously defined BookName.aidl file. After compiling the project, we can find the BookName.java file in the gen directory. This is the aidl generated file. Click the contents of the file

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file:/Users/v_renxiaohui01/Downloads/aidl/app/src/main/aidl/com/baidu/bpit/aibaidu/aidl/BookName.aidl
 */
package com.baidu.bpit.aibaidu.aidl;

public interface BookName extends android.os.IInterface {
   /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.baidu.bpit.aibaidu.aidl.BookName {
        private static final java.lang.String DESCRIPTOR = "com.baidu.bpit.aibaidu.aidl.BookName";

       /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

       /**
         * Cast an IBinder object into an com.baidu.bpit.aibaidu.aidl.BookName interface,
         * generating a proxy if needed.
         */
        public static com.baidu.bpit.aibaidu.aidl.BookName asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.baidu.bpit.aibaidu.aidl.BookName))) {
                return ((com.baidu.bpit.aibaidu.aidl.BookName) iin);
            }
            return new com.baidu.bpit.aibaidu.aidl.BookName.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 {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getName: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _result = this.getName();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
                case TRANSACTION_getList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.baidu.bpit.aibaidu.aidl.User> _result = this.getList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addInout: {
                    data.enforceInterface(DESCRIPTOR);
                    com.baidu.bpit.aibaidu.aidl.User _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.baidu.bpit.aibaidu.aidl.User.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addInout(_arg0);
                    reply.writeNoException();
                    if ((_arg0 != null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                case TRANSACTION_addIn: {
                    data.enforceInterface(DESCRIPTOR);
                    com.baidu.bpit.aibaidu.aidl.User _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.baidu.bpit.aibaidu.aidl.User.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addIn(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_addout: {
                    data.enforceInterface(DESCRIPTOR);
                    com.baidu.bpit.aibaidu.aidl.User _arg0;
                    _arg0 = new com.baidu.bpit.aibaidu.aidl.User();
                    this.addout(_arg0);
                    reply.writeNoException();
                    if ((_arg0 != null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.baidu.bpit.aibaidu.aidl.BookName {
            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 java.lang.String getName() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public java.util.List<com.baidu.bpit.aibaidu.aidl.User> getList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.baidu.bpit.aibaidu.aidl.User> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.baidu.bpit.aibaidu.aidl.User.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addInout(com.baidu.bpit.aibaidu.aidl.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_addInout, _data, _reply, 0);
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        user.readFromParcel(_reply);
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void addIn(com.baidu.bpit.aibaidu.aidl.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_addIn, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void addout(com.baidu.bpit.aibaidu.aidl.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);
                    mRemote.transact(Stub.TRANSACTION_addout, _data, _reply, 0);
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        user.readFromParcel(_reply);
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_addInout = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
        static final int TRANSACTION_addIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
        static final int TRANSACTION_addout = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
    }

    public java.lang.String getName() throws android.os.RemoteException;

    public java.util.List<com.baidu.bpit.aibaidu.aidl.User> getList() throws android.os.RemoteException;

    public void addInout(com.baidu.bpit.aibaidu.aidl.User user) throws android.os.RemoteException;

    public void addIn(com.baidu.bpit.aibaidu.aidl.User user) throws android.os.RemoteException;

    public void addout(com.baidu.bpit.aibaidu.aidl.User user) throws android.os.RemoteException;
}

 

Let s analyze this class. 1. BookName.java inherits from the android.os.IInterface interface, and at the same time it is also an interface itself. All transmissions in the Binder need to inherit the IInterface interface. This class first declares 5 methods getName, getList, addInout , Addin, addout, these are defined in the aidl file, and he also declares an integer for each method. In order to distinguish methods in transct, the transct method needs to be used when the application is in a different process. This logic is proxied by the sub internal class proxy

IBinder

IBinder is an interface, which represents a cross-process transfer capability; as long as this interface is implemented, this object can be transferred across processes; this is supported by the driver bottom layer; when cross-process data flows through the driver, the driver The data of the IBinder type will be recognized, so as to automatically complete the conversion of Binder local objects and Binder proxy objects in different processes.

IInterface

IInterface here represents what capabilities the remote server object has. Specifically, it is the interface in aidl.

DESCRIPTOR

The unique identifier of Binder, which is generally represented by the class name of Binder,

  private static final java.lang.String DESCRIPTOR = "com.baidu.bpit.aibaidu.aidl.BookName";
 

asInterface

It is used to convert the Binder of the server into the AIDL interface type object required by the client. This conversion distinguishes the process. If the client and the server are in the same process, the returned object is the Stub of the server itself, if they are different Process, return the Stub.Proxy object

asBinder

This method is used to return the current Binder object

onTransact

This method runs in the Binder thread pool of the server. When the client initiates a cross-process request, the request will be encapsulated by the bottom layer and handed over to this method for processing. The server uses the code to determine the method of the client request, and then from the data Take out the parameters required by the target method (if the target method has parameters), then execute the target method, and write the return value to the reply after the execution (if the target method has a return value), this is the execution process of this method, if This method returns false, then the client request fails, you can use this to verify

Proxy/addList

This method runs on the client. When the client calls the slave method, its internal implementation is like this. 1. prepare the input Parcel object _data, output Parcel object _reply, and return value object List, and then put the method's Write parameters to _data (if there are parameters), then call the transact method to initiate an RPC (remote procedure call), while the current thread is suspended, and then the onTransact method of the server will be called. After knowing that the RPC process returns, the current thread continues to execute. And take out the return result from _reply

The methods in several other Proxy are the same as this process

This is the working mechanism of Binder, we need to pay attention to two points

  • When the client initiates a remote request, the current thread will be suspended until the server returns. If the remote method is a time-consuming method, it cannot be done in the UI thread
  • The Binder method of the server runs in the Binder thread pool, so the Binder method can be called synchronously regardless of whether it takes time or not

Customize a Binder

It can be seen from the above process that we can actually implement Binder without using aidl. The reason we have Aidl is to facilitate code generation, so we manually generate a Bidner

First generate an interface of aidl nature, which can be inherited from IInterface, as follows:


public interface CustomBookName extends IInterface {

    static final java.lang.String DESCRIPTOR = "com.baidu.bpit.aibaidu.aidl.BookName";

    static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    static final int TRANSACTION_addInout = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    static final int TRANSACTION_addIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
    static final int TRANSACTION_addout = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);

    public java.lang.String getName() throws android.os.RemoteException;

    public java.util.List<com.baidu.bpit.aibaidu.aidl.User> getList() throws android.os.RemoteException;

    public void addInout(com.baidu.bpit.aibaidu.aidl.User user) throws android.os.RemoteException;

    public void addIn(com.baidu.bpit.aibaidu.aidl.User user) throws android.os.RemoteException;

    public void addout(com.baidu.bpit.aibaidu.aidl.User user) throws android.os.RemoteException;
}
 

Then we need to implement a Binder inherited from Binder, and the interface above

public abstract class CustomBinder extends Binder implements CustomBookName {

   /**
     * Construct the stub at attach it to the interface.
     */
    public CustomBinder() {
        this.attachInterface(this, DESCRIPTOR);
    }

   /**
     * Cast an IBinder object into an com.baidu.bpit.aibaidu.aidl.BookName interface,
     * generating a proxy if needed.
     */
    public static CustomBookName asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof CustomBookName))) {
            return (CustomBookName) iin;
        }
        return new CustomBinder.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_getName: {
                data.enforceInterface(DESCRIPTOR);
                java.lang.String _result = this.getName();
                reply.writeNoException();
                reply.writeString(_result);
                return true;
            }
            case TRANSACTION_getList: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<com.baidu.bpit.aibaidu.aidl.User> _result = this.getList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
            case TRANSACTION_addInout: {
                data.enforceInterface(DESCRIPTOR);
                com.baidu.bpit.aibaidu.aidl.User _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.baidu.bpit.aibaidu.aidl.User.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addInout(_arg0);
                reply.writeNoException();
                if ((_arg0 != null)) {
                    reply.writeInt(1);
                    _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }
                return true;
            }
            case TRANSACTION_addIn: {
                data.enforceInterface(DESCRIPTOR);
                com.baidu.bpit.aibaidu.aidl.User _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.baidu.bpit.aibaidu.aidl.User.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addIn(_arg0);
                reply.writeNoException();
                return true;
            }
            case TRANSACTION_addout: {
                data.enforceInterface(DESCRIPTOR);
                com.baidu.bpit.aibaidu.aidl.User _arg0;
                _arg0 = new com.baidu.bpit.aibaidu.aidl.User();
                this.addout(_arg0);
                reply.writeNoException();
                if ((_arg0 != null)) {
                    reply.writeInt(1);
                    _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    private static class Proxy implements CustomBookName {
        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 java.lang.String getName() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.lang.String _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(TRANSACTION_getName, _data, _reply, 0);
                _reply.readException();
                _result = _reply.readString();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

        @Override
        public java.util.List<com.baidu.bpit.aibaidu.aidl.User> getList() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List<com.baidu.bpit.aibaidu.aidl.User> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(TRANSACTION_getList, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(com.baidu.bpit.aibaidu.aidl.User.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

        @Override
        public void addInout(com.baidu.bpit.aibaidu.aidl.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(TRANSACTION_addInout, _data, _reply, 0);
                _reply.readException();
                if ((0 != _reply.readInt())) {
                    user.readFromParcel(_reply);
                }
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }

        @Override
        public void addIn(com.baidu.bpit.aibaidu.aidl.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(TRANSACTION_addIn, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }

        @Override
        public void addout(com.baidu.bpit.aibaidu.aidl.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);
                mRemote.transact(TRANSACTION_addout, _data, _reply, 0);
                _reply.readException();
                if ((0 != _reply.readInt())) {
                    user.readFromParcel(_reply);
                }
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }
    }
}
 

The main methods here are extremely useful. We have all mentioned above

Below we use a custom Bidner class

  • First implement your own binder

    class MyCustomBinder extends CustomBinder {

        @Override
        public String getName() throws RemoteException {
            return " binder";
        }

        @Override
        public List<User> getList() throws RemoteException {
            return null;
        }

        @Override
        public void addInout(User user) throws RemoteException {

        }

        @Override
        public void addIn(User user) throws RemoteException {

        }

        @Override
        public void addout(User user) throws RemoteException {

        }
    }
 
  • Then return the custom binder in the service
  @Override
    public IBinder onBind(Intent intent) {
        return new MyCustomBinder();
    }
 
  • Copy the custom binder class into the client
  • Client binds service to get binder
 private void bindServer() {
        Intent mIntent = new Intent();
       //service action
        mIntent.setAction("com.aaa.aaa");
       //
        mIntent.setPackage("com.baidu.bpit.aibaidu.aidl");
        bindService(mIntent, new ServiceConnection() {

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                customBookName = CustomBinder.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        }, BIND_AUTO_CREATE);
    }
 
  • Click the button to call the getName method of the custom binder

    private void initView() {
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    String name = customBookName.getName();
                    Log.d("mmm",name);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
 
  • Look at the log
01-18 19:14:45.688 3554-3554/com.baidu.bpit.aibaidu.client D/mmm:  binder
 

Succeeded