An article to get "AIDL Detailed Explanation"

foreword

First of all, this article wants to use AIDL to practice the Binder model C/S architecture of the previous article.
The structure of this article:
1. What is AIDL
2. How to use AIDL
3. Points to pay attention to in AIDL

What is AIDL

AIDL, the Android Interface Definition Language, is an interface definition language used for inter-process communication (IPC) on the Android platform. It allows the process of one Android application to communicate with the process of another application, realizing inter-process data sharing and method calling.

The difference between AIDL and ContentProvider

The process of all Android applications communicates with the process of another application. What is the difference between him and ContentProvider?
1. AIDL: AIDL is a method based on interface definition, which is used to realize communication between different processes. Its main features are as follows :

  • Strong typing: AIDL uses strongly typed interface definitions, which can ensure the type safety of parameters and return values.
  • Support two-way communication: In addition to calling the method of the server from the client, the server can also call the method of the client to realize two-way communication between the client and the server.
  • Support for multithreading: AIDL can communicate between multiple threads and implement concurrent calls.
  • Poor flexibility: AIDL needs to manually write interface definition and implementation, which is more cumbersome.

2. ContentProvider: ContentProvider is a mechanism for data sharing, which allows different applications to share data. Its main features are as follows :

  • Strong encapsulation: ContentProvider encapsulates data in specific data storage (such as SQLite database, files, etc.), and provides external access through URI.
  • Good security: Through authority control and URI restrictions, data can be safely controlled and shared.
  • Unified content access: Through ContentResolver, data of different applications can be accessed uniformly.
  • Object transfer limitation: ContentProvider is mainly used to share structured data, and it is not very convenient for complex objects and cross-process calls.

Summary:
AIDL is suitable for scenarios that require complex cross-process communication, two-way communication or multi-threaded concurrent calls. For example: Its advantages are type safety and flexibility, but it requires manual writing of interface definitions and implementations.
ContentProvider is suitable for scenarios that require shared data. It has the advantages of strong encapsulation and good security, but it is inconvenient for complex objects and cross-process calls.

scenes in the project

1. Control the music player
An application needs to interact with the music player service to control music playback, pause, and switch tracks.
In this case, the application can expose these operation methods to the music player service by defining the AIDL interface, and perform cross-process communication through AIDL. In this way, the application program can call the method provided by the remote service through the AIDL interface to realize the control of the music player.
2. Message push:
The application usually needs to communicate with the message push service to receive and process the push message from the server. In order to achieve this communication, the application can define an AIDL interface, which exposes the method of receiving messages. Then, the application registers the AIDL interface to the message push service, so that it can call back methods in the application and deliver push messages.
3. WebView in an independent process:
WebView runs in the main process of the application by default, but it can be run as an independent process by setting. In this way, the application can define the interface through AIDL, and expose the interface method to the WebView independent process. Through the AIDL interface, the application can send instructions to the WebView independent process, such as loading web pages, executing JavaScript and other operations.
4. Authentication, process keeping alive... There are many scenarios

Usage of AIDL

Next, an example is used to illustrate the creation process and usage of AIDL. This example is implemented with the help of two APPs. After all, the real needs in development also occur in the two APPs.
An example is: it is an address book, and a List is maintained on the server side to store contact information. The client can add contacts, obtain contact lists, and other functions through RPC.

1. Create AIDL

Here we use the way of creating lib to create AIDL, which is for the same client and server. Just need to reference this lib. Why should be the same, written in the following questions.

  • Directly right-click to create AIDL, here we name it Contact, and then a small directory structure is generated.

insert image description here

  • Then add the AIDL interface file:

First, create a new Contact class. Through the above introduction, we know that ordinary java classes cannot be used in AIDL. The Parcelable interface must be implemented and declared in the AIDL file:
Contact.java

/**
 * Author: mql
 * Date: 2023/7/21
 * Describe :
 */
public class Contact {
    
    
    private int phoneNumber;
    private String name;
    private String address;

    public Contact(int phoneNumber, String name, String address) {
    
    
        this.phoneNumber = phoneNumber;
        this.name = name;
        this.address = address;
    }

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

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

    private final static Creator<Contact> CREATOR = new Creator<Contact>() {
    
    
        @Override
        public Contact createFromParcel(Parcel source) {
    
    
            return new Contact(source);
        }

        @Override
        public Contact[] newArray(int size) {
    
    
            return new Contact[size];
        }
    };


    public Contact(Parcel parcel) {
    
    
        phoneNumber = parcel.readInt();
        name = parcel.readString();
        address = parcel.readString();
    }

    @Override
    public String toString() {
    
    
        return "Contact{" +
                "phoneNumber=" + phoneNumber +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
  • Declare the Contact class

Contact.aidl

package com.example.aidl_lib;
parcelable Contact;
  • Create an AIDL interface file and declare the methods that need to be exposed to the client.

IContactsManager.aidl

package com.example.aidl_lib;
import com.example.aidl_lib.Contact;

interface IContactsManager {
    
    
    int getPhoneNumber(in String name);
    String getName(int phoneNumeber);
    Contact getContact(int phoneNumber);
    List<Contact> getContactList();
    boolean addContact(in Contact contact);
}

The structure is as follows:
insert image description here

2. Create a server

First, we need to rely on the aidl_lib module we created.

  • Create a Service to respond to the client's binding request. We will call this Service ContactManagerService.
  • Then create an inner class, let this class inherit the Stub class in the AIDL interface, and implement its abstract method. Return the object of this newly created class in Service.

The detailed implementation is as follows: ContactManagerService.java

public class ContactManagerService extends Service {
    
    

    private final static String TAG = ContactManagerService.class.getSimpleName();

    private CopyOnWriteArrayList<Contact> contacts = new CopyOnWriteArrayList<>();

    @Override
    public void onCreate() {
    
    
        super.onCreate();
        contacts.add(new Contact(110, "报警电话", "派出所"));
        contacts.add(new Contact(119, "火警电话", "消防局"));
        contacts.add(new Contact(112, "故障电话", "保障局"));
    }

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

    private class ContactManagerBinder extends IContactsManager.Stub{
    
    

        /**
         * 根据号码返回手机号
         * @param name
         * @return
         * @throws RemoteException
         */
        @Override
        public int getPhoneNumber(String name) throws RemoteException {
    
    
            if (!TextUtils.isEmpty(name)) {
    
    
                for (Contact contact:contacts) {
    
    
                    if (contact.name.equals(name)){
    
    
                        return contact.phoneNumber;
                    }
                }
            }
            return 0;
        }

        /**
         * 根据号码返回名称
         * @param phoneNumber
         * @return
         * @throws RemoteException
         */
        @Override
        public String getName(int phoneNumber) throws RemoteException {
    
    
            for (Contact contact:contacts) {
    
    
                if (contact.phoneNumber == phoneNumber){
    
    
                    return contact.name;
                }
            }
            return null;
        }

        /**
         * 根据号码返回联系人对象
         * @param phoneNumber
         * @return
         * @throws RemoteException
         */
        @Override
        public Contact getContact(int phoneNumber) throws RemoteException {
    
    
            for (Contact contact:contacts) {
    
    
                if (contact.phoneNumber == phoneNumber) {
    
    
                    return contact;
                }
            }
            return null;
        }

        /**
         * 获取联系人集合
         * @return
         * @throws RemoteException
         */
        @Override
        public List<Contact> getContactList() throws RemoteException {
    
    
            return contacts;
        }

        /**
         * 添加联系人
         * @param contact
         * @return
         * @throws RemoteException
         */
        @Override
        public boolean addContact(Contact contact) throws RemoteException {
    
    
            if (contact != null) {
    
    
                return contacts.add(contact);
            }
            return false;
        }
    }
}
  • Finally, add configuration to this Service in the manifest file, and set the export attribute to true for external calls:
<service android:name=".aidl.contact.ContactManagerService"
         android:exported="true"/>

In other words, the Server is just to use the registration service to let the outside world use the relevant interface methods.

3. Create a client

First, we need to rely on the aidl_lib module we created.

  • Bind the Service of the server in the client. After the binding is successful, the returned Binder object can be converted to the type of the AIDL interface in the onServiceConnected method of ServiceConnection.

First specify the Component to the Intent, and you need to pass in two parameters, one is the project package name of the remote Service, and the other is the fully qualified name of the remote Service, and then use bindService to bind the remote Service
:

Intent intent = new Intent();
intent.setComponent(new ComponentName("cn.codingblock.ipc", "cn.codingblock.ipc.aidl.contact.ContactManagerService"));
bindService(intent, serviceConnection, BIND_AUTO_CREATE);

Obtain the returned Binder in serviceConnection and use the IContactsManager.Stub.asInterface() method to convert the Binder object to the IContactsManager type.

private ServiceConnection serviceConnection = new ServiceConnection(){
    
    
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
    
    
        mIContactsManager = IContactsManager.Stub.asInterface(service);
        Log.i(TAG, "onServiceConnected: mIContactsManager=" + mIContactsManager);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
    
    
        mIContactsManager = null;
        Log.i(TAG, "onServiceDisconnected: ");
    }
};
  • After getting the Binder object, you can call the method declared in the AIDL file. Let's take a look at the complete code:
public class ContactMangerActivity extends AppCompatActivity {
    
    
    private static final String TAG = ContactMangerActivity.class.getSimpleName();

    private IContactsManager mIContactsManager;
    private EditText et_contact_name;
    private EditText et_contact_phone_number;
    private EditText et_contact_address;

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

        ViewUtils.findAndOnClick(this, R.id.btn_add_contact, mOnClickListener);
        ViewUtils.findAndOnClick(this, R.id.btn_get_phone_number, mOnClickListener);
        ViewUtils.findAndOnClick(this, R.id.btn_get_name, mOnClickListener);
        ViewUtils.findAndOnClick(this, R.id.btn_get_contact, mOnClickListener);
        ViewUtils.findAndOnClick(this, R.id.btn_get_list, mOnClickListener);

        et_contact_name = ViewUtils.find(this, R.id.et_contact_name);
        et_contact_phone_number = ViewUtils.find(this, R.id.et_contact_phone_number);
        et_contact_address = ViewUtils.find(this, R.id.et_contact_address);

        Intent intent = new Intent();
        intent.setComponent(new ComponentName("cn.codingblock.ipc", "cn.codingblock.ipc.aidl.contact.ContactManagerService"));
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
        
    }

    private ServiceConnection serviceConnection = new ServiceConnection(){
    
    
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
    
    
            mIContactsManager = IContactsManager.Stub.asInterface(service);
            Log.i(TAG, "onServiceConnected: mIContactsManager=" + mIContactsManager);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
    
    
            mIContactsManager = null;
            Log.i(TAG, "onServiceDisconnected: ");
        }
    };

    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v) {
    
    
            switch (v.getId()) {
    
    
                case R.id.btn_add_contact:

                    Contact contact = new Contact(getEtContactPhoneNumber(), getEtContactName(), getEtContactAddress());
                    try {
    
    
                        mIContactsManager.addContact(contact);
                    } catch (RemoteException e) {
    
    
                        e.printStackTrace();
                    }

                    break;

                case R.id.btn_get_phone_number:
                    String name = getEtContactName();

                    try {
    
    
                        Log.i(TAG, "onClick: " + name + "的电话:" + mIContactsManager.getPhoneNumber(name));
                    } catch (RemoteException e) {
    
    
                        e.printStackTrace();
                    }

                    break;

                case R.id.btn_get_name:

                    int number = getEtContactPhoneNumber();

                    try {
    
    
                        Log.i(TAG, "onClick: " + number + " 对应的名称:" + mIContactsManager.getName(number));
                    } catch (RemoteException e) {
    
    
                        e.printStackTrace();
                    }

                    break;

                case R.id.btn_get_contact:

                    int number1 = getEtContactPhoneNumber();

                    try {
    
    
                        Contact contact1 = mIContactsManager.getContact(number1);

                        System.out.println(contact1);

                    } catch (RemoteException e) {
    
    
                        e.printStackTrace();
                    }

                    break;

                case R.id.btn_get_list:

                    try {
    
    
                        List<Contact> contacts = mIContactsManager.getContactList();

                        System.out.println(contacts);

                    } catch (RemoteException e) {
    
    
                        e.printStackTrace();
                    }

                    break;
            }
        }
    };

    private String getEtContactName() {
    
    
        String str = et_contact_name.getText().toString();
        if (TextUtils.isEmpty(str)) {
    
    
            Toast.makeText(this, "请输入联系人名称", Toast.LENGTH_SHORT).show();
            return null;
        }
        return str;
    }

    private int getEtContactPhoneNumber() {
    
    
        String str = et_contact_phone_number.getText().toString();
        if (TextUtils.isEmpty(str)) {
    
    
            Toast.makeText(this, "请输入联系人电话", Toast.LENGTH_SHORT).show();
            return 0;
        }
        return Integer.valueOf(str);
    }

    private String getEtContactAddress() {
    
    
        String str = et_contact_address.getText().toString();
        if (TextUtils.isEmpty(str)) {
    
    
            Toast.makeText(this, "请输入联系人地址", Toast.LENGTH_SHORT).show();
            return null;
        }
        return str;
    }
    
    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        unbindService(serviceConnection);
    }
}

Points to note in AIDL

Data types supported by AIDL

  • Eight basic data types: byte, char, short, int, long, float, double, boolean
  • String,CharSequence
  • List type: The data carried by the List must be a type supported by AIDL, or other declared AIDL objects
  • Map type: The data carried by the Map must be a type supported by AIDL, or other declared AIDL objects
  • Parcelable: all objects that implement this interface

Orientation tag in AIDL file: the difference between in, out, and inout

interface IController {
    
    
    int transIn(in State state);
    int transOut(out State state);
    int transInOut(inout State state);
}
  • In the AIDL file, the direction tag (direction tag) appears in the form of in, out and inout, and is used to specify the transfer direction and access rights of the parameters.
  • in: After the server receives the object, any modification to the object will not be synchronized to the client. In other words, the in tag is suitable for passing data from the client to the server.
  • out: After the server receives the object, any modification to the object will be synchronized to the client. In other words, the out tag is suitable for passing data from the server to the client.
  • inout: After the server accepts the object, the modifications made to the object by both the client and the server will be synchronized at both ends. This tag is used to specify that parameters can be passed from the client to the server, or from the server to the client. In server-side methods, parameters can be read and written.
  • To sum up, in tags are used for one-way transfer from client to server, out tags are used for one-way transfer from server to client, and inout tags are used for two-way transfer.

How to add permission check in AIDL

In fact, in the formal development work, we don't want any client to be able to bind our server, because this will pose a great security risk, so when the client wants us to send a binding request, we need to do permission verification , only clients that meet our permission requirements can establish a link with our server.
1. Add permissions on the client and server

<!--声明权限-->
<uses-permission 
    android:name="cn.codingblock.permission.ACCESS_CONTACT_MANAGER"/>
<!--定义权限-->
<permission
    android:name="cn.codingblock.permission.ACCESS_CONTACT_MANAGER"
    android:protectionLevel="normal"/>

2. Then perform permission verification in the onBinde method of the Service, and return null directly if the verification fails.

@Nullable
@Override
public IBinder onBind(Intent intent) {
    
    
    if (checkCallingOrSelfPermission("cn.codingblock.permission.ACCESS_CONTACT_MANAGER") == PackageManager.PERMISSION_DENIED) {
    
    
        Log.i(TAG, "onBind: 权限校验失败,拒绝绑定...");
        return null;
    }
    Log.i(TAG, "onBind: 权限校验成功!");
    return new ContactManagerBinder();
}

Note: The above codes for declaring permissions and defining permissions should be added to both the client and server projects.

Why should the client and server use the exact same path and structure

Because the client needs to deserialize all AIDL-related classes in the server, if the full path of the class is inconsistent, the deserialization cannot be successful.
Tip: In order to create AIDL files more conveniently, we can create a new lib project, so that the client APP and server APP depend on this lib at the same time, so we only need to add AIDL files in this lib project!
Or create one and copy the other directly.

Why the parameters of the basic type can only be in

  • Primitive types are value types, not reference types.
  • If the parameter uses the inout or out tag, it means that the parameter can be modified by the server, but since the basic type is a value type, the modified value cannot be returned.
  • Therefore, in order to avoid invalid parameter passing, parameters of basic types can only use in tags.

what is a stub

The stub acts as a message passing bridge in AIDL, responsible for passing the client's request to the server, and passing the server's response back to the client.
Simply put, it is the exit of AIDL

what is proxy

In AIDL, the proxy acts as a proxy for the interaction between the client and the server. It implements the AIDL interface and is responsible for forwarding the client's method calls to the stub object so that the client can communicate with the server across processes.
Simply put, it is the entrance of AIDL

What does onTransact do

The onTransact() method is the core method for processing client requests and server responses in AIDL services. It is responsible for parsing the request data, executing the corresponding business logic, and packaging the result as an output Parcel object and returning it to the client.
Simply put, the receiver

what does transact do

The transact() method is used to send the request to a remote Binder object (such as a stub or proxy object generated by AIDL). Through this method, cross-process method calling and data transmission can be realized. When the client calls the transact() method, it packs information such as the method descriptor (descriptor) and parameters to be called into a Parcel object, and sends the Parcel to the server through the Binder driver.
Simply put, sending

Guess you like

Origin blog.csdn.net/weixin_45112340/article/details/131861467