Detailed Use of Android Advanced AIDL

Keywords: Android Java xml SDK

The original text was first published in Wechat Public Number: jzman-blog, welcome to pay attention to communication!

AIDL (Android Interface Definition Language) can be used to define the programming interface of IPC between client and server. In Android, there is no shared memory (user space) between processes. Communication between different processes is usually handled by AIDL.

The main process is to define the AIDL interface in the. AIDL file and add it to the src directory of the application project. After the creation of the rebuild, the Android SDK tool will automatically generate the IBinder interface based on the. AIDL file. The specific business object implements the interface. This specific business object is also the IBinder object. When binding the service, it will be based on the actual situation. Case returns the specific communication object (local or proxy), and finally After the client is bound to the service, the method in IBinder can be invoked for interprocess communication (IPC). Here we will learn the use of AIDL from the following aspects:

  1. Create. aildl file
  2. Specific business object implements interface based on. aidl file generation
  3. Exposing interfaces to clients
  4. Client Remote Call
  5. Verify AIDL

Create. aildl file

In AIDL, the interface can be declared by one or more methods with parameters and return values. The parameters and return values can be of any type. The data types supported in AIDL are as follows:

  1. Eight data types of java: byte, short, int, long, float, double, boolean, char
  2. In addition, support String, charSequence, List, Map
  3. Custom Data Type

If the parameter or return value type in the business method is List or Map:

All elements in the List must be AIDL-supported data types, other AIDL-generated interfaces, or self-declared packaged types. You can choose to use List as a "generic" class (for example, List < String >). The specific class actually received at the other end is always ArrayList, but the generated method uses the List interface.

All elements in the Map must be AIDL-supported data types, other AIDL-generated interfaces, or packaged types that you declare. Universal Maps (such as Map < String, Integer > Maps) are not supported. The specific class actually received at the other end is always HashMap, but the generated method uses the Map interface.

Of course, AIDL also supports custom data types, which are described below.

First, create the. aidl file under the src directory of the project, as shown in the following figure:

Then, specific business methods are added to the. aidl file, which reads as follows:

// IPersonAidlInterface.aidl
package com.manu.aidldemo;
// Declare any non-default types here with import statements
interface IPersonAidlInterface {
    //Specific business
    void setName(String name);
    void setAge(int age);
    String getInfo();

    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

Then, rebuild the project, and Android SDK tools will generate corresponding. java interface files with the same name as. aidl files in the corresponding directory. The specific directory is as follows:

Specific business object implements interface based on. aidl file generation

The previous step only defines basic business operations using aidl. After rebuild, A. java file with the same file name as. aidl will be generated. The generated interface file has a subclass named Stub, which is also an abstract implementation of its parent interface. It is mainly used to generate all methods in. aidl file. The declaration of Stub class is as follows:

// Stub
public static abstract class Stub extends android.os.Binder implements com.manu.aidldemo.IPersonAidlInterface

Obviously, Stub implements the local interface and inherits Binder objects. Between the support of Binder objects at the bottom of the system, Stub objects have the ability to transmit data remotely. When Stub objects are generated, the asInterface method is called as follows:

// asInterface
public static com.manu.aidldemo.IPersonAidlInterface asInterface(android.os.IBinder obj){
    
    if ((obj==null)) {
        return null;
    }
    
    // Is retrieving Binder objects an implementation of the local interface?
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    
    if (((iin!=null)&&(iin instanceof com.manu.aidldemo.IPersonAidlInterface))) {
        return ((com.manu.aidldemo.IPersonAidlInterface)iin);
    }
    
    return new com.manu.aidldemo.IPersonAidlInterface.Stub.Proxy(obj);
}

The main function of asInterface is to retrieve whether the Binder object is the implementation of the local interface. According to the return value of queryLocalInterface() method, we can judge whether to use the proxy object. This retrieval process should be supported by the bottom of the system. If it is returned to null, we can create the proxy object of Stub, and conversely use the local object to transfer the number. According to this, let's see why Binder has the ability to communicate remotely, because Stub inherits the Binder class, which is as follows:

// Binder
public class Binder implements IBinder {
    //...
}

The following is a description of the IBinder interface on the official website:

The basic interface of remote object, the core part of lightweight remote procedure call mechanism, is designed for high performance in executing in-process and cross-process calls. This interface describes an abstract protocol that interacts with remotable objects. Instead of implementing this interface directly, extend it from Binder.

Here we know that Binder implements the IBinder interface, that is to say, Binder has the ability of remote communication. When communicating between different processes (remote), it is obvious that the agent object of Stub is used. The specific business processing logic in this agent class is as follows:


//Specific business
@Override 
public void setName(java.lang.String name) throws android.os.RemoteException{
    // Serialization of data
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeString(name);
        // This method will eventually call the onTransact method
        mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
        _reply.readException();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
}

The main purpose of this paper is to serialize the data and finally invoke the onTransact() method with system cross-process support. The following is the onTransact() method, as follows:

@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_setName:
        {
            //...
            // Finally, the business method in Stub is invoked.
            this.setName(_arg0);
            //...
        }
    }
}

Obviously, when the system calls back to the developer, the code passed back is a constant. In cross-process, each specific service (method) will correspond to a number, and then the corresponding service (business) will be executed according to this number. Here we talk about the final specific business to be executed, so where should the business be embodied, from the above. It is known that Stub is an abstract class, so the specific business it provides must need a specific implementation class to complete. The following implementation of this specific business class, as follows:

/**
 * Created by jzman
 * Powered by 2018/3/8 0008.
 */
public class IPersonImpl extends IPersonAidlInterface.Stub {
    private String name;
    private int age;
    
    @Override
    public void setName(String name) throws RemoteException {
        this.name = name;
    }

    @Override
    public void setAge(int age) throws RemoteException {
        this.age = age;
    }

    @Override
    public String getInfo() throws RemoteException {
        return "My name is "+name+", age is "+age+"!";
    }

    @Override
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

    }
}

This class is the specific business class provided to the outside world, and its instance is also a Binder object.

Exposing interfaces to clients

Create a Service to provide specific business to the outside world, as follows:

// Service
public class PersonService extends Service {
    public PersonService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new IPersonImpl();
    }
}

When the bindService() method is called externally to bind the Service, the onBind() method is called to return the IBinder object, which is also a specific business object. For example, the onBind() method here returns a specific business object, which is unified. In addition, the Service created is declared in the AndroidManifest.xml file as follows:

<service
    android:name=".PersonService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote">
</service>

The process keyword is used to open a separate process for the service, and remote can be arbitrary, indicating the name of the process, and ":" will add a new name as the name of the new process in the main process (process name is package name). For example, com.manu.study will become com.manu.study:remote.

Client Remote Call

Through the above steps to complete the construction of the service and run the service in an independent process, the following is the specific invocation of the client, the specific implementation of the reference is as follows:

// Client
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private IPersonAidlInterface iPersonAidlInterface;

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

    public void bindServiceClick(View view) {
        Log.i(TAG,"Binding service...");
        Intent intent = new Intent(this,PersonService.class);
        // Automatically create services when binding services
        bindService(intent,conn, Context.BIND_AUTO_CREATE);
    }

    public void unbindServiceClick(View view) {
        Log.i(TAG,"Unbundling service...");
        unbindService(conn);
    }

    public void callRemoteClick(View view) {
        Log.i(TAG,"Remote invocation of specific services...");
        try {
            iPersonAidlInterface.setName("Tom");
            iPersonAidlInterface.setAge(10);
            String info = iPersonAidlInterface.getInfo();
            System.out.println("This is the service information for remote invocation:"+info);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // Return IBinder's local object or its proxy object based on the actual situation
            iPersonAidlInterface = IPersonAidlInterface.Stub.asInterface(service);
            System.out.println("Specific business objects:"+iPersonAidlInterface);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // Called when Service accidentally interrupts
        }
    };
}

The above code is the process of client invoking a specific service.

Verify AIDL

Through the previous steps, the server and client have been completed. Here we verify whether the specific business can be invoked. There are two cases:

1. Same process

When creating a Service, do not use process to open a separate process in the AndroidManifest.xml file. At this time, the Service process and the client belong to the same process by default. The results are as follows:

2. Different processes

When creating a Service, you can use process to open a separate process in the AndroidManifest.xml file. As mentioned above, at this time, the Service process and the client process are located in different processes. The results are as follows:

Obviously, if the service and the client are in different processes, that is, inter-process communication, which is often said to be done by the proxy object of the IBinder object, and vice versa, by using the local object, that is, the local IBinder object, which is specifically the object generated by the business class that implements AIDL. The next article describes how to use custom types in AIDL.

You can choose to pay attention to the public number: jzman-blog, exchange and learn together!

Posted by mikesta707 on Sat, 18 May 2019 03:57:54 -0700