preface

One of the ways to communicate between Android processes is to use AIDL. This article introduces how to use AIDL to communicate between processes

What is the AIDL

AIDL stands for Android Interface Definition Language. It’s described on the website

It allows you to define programming interfaces that both clients and services agree on to communicate with each other using interprocess communication (IPC)

Create an AIDL file

If you use AIDL for interprocess communication. I have been shown how to use AIDL interprocess communication in Google’s official website. The following shows how to use AIDL interprocess communication correctly from the basic type

Right-click in the project directory, select New and select AIDL as shown in 👇 to create an.aidl ending file

The.aidl file created is the same as Java and res files as shown in 👇. The final directory of the aidl file created in the example is com.starot.lib_intent.aidl

In the AIDL file we create, define the interface we need. For example,

// ITest.aidl
package com.starot.lib_intent.aidl;

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

interface ITest {
    String add(in String name, int a, int b);
}
Copy the code

After writing, be sure to rebuild the project.

After Rebuild SUCCESS, we will see the Java Interface generated from AIDL file generated by Android Studio under build

To this. The AIDL file creation process is OVER.

Server Settings

Create a Service that provides a method for a client to call

/ / step1: inherit the Service
class RemoteService : Service(a){

  //step2: inherit the Stub implementation defined in AIDL interface
  class MyBinder : ITest.Stub(a){
       override fun add(name: String, a: Int, b: Int): String {
            return name + ":" + (a + b)
        }
  }

  private var binder: MyBinder? = null
  //step3: override the Service onCreate method to create the Binder object
  override fun onCreate(a) {
      super.onCreate()
      binder = MyBinder()
  }

  
  override fun onBind(intent: Intent?): IBinder? {
    //step4: return the created binder object
    return binder
  }
}
Copy the code

After writing a service, you need to register it in XML. Since the example is between two different apps, you do not need to write processes to specify the process. Set true permission to custom permission, mainly to eliminate the warning of 👇. At the same time, because it is a custom permission, need to declare. In the use of Messenger interprocess communication article, about this question, also specifically asked Zhu Kai

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.starot.lib_intent">
      <! Declare permission level and description -->
      <permission android:name="com.starot.lib_intent.CUSTOME_PERMISSION"
          android:description="@string/permission_des"
          android:protectionLevel="signature"/>
      <application>
          <service android:name=".aidl.RemoteService"
              android:enabled="true"
              android:exported="true"
              android:permission="com.starot.lib_intent.CUSTOME_PERMISSION"/>
      </application>
</manifest>
Copy the code

This is basically the end of the server-side code.

Client Setup

Finished the server, the client is to copy and paste, looked back at the AIDL file directory of above, we create com. Starot. Lib_intent. AIDL, its complete copy to the client projects, it is important to note that directory location cannot be wrong.


After copying and pasting, remember that we need to Rebuild the client project for the same reason that we need Android Studio to generate the Stub object we need

Establish a connection

This is clearly stated on the official website

Note: If you bind to a Service using intents, use explicit intents to secure your application. Using implicit intents to start services has security implications because you can’t be sure which service will respond to the intent, and users can’t see which service will start. Starting with Android 5.0 (API level 21), if you call bindService() with implicit intents, the system will throw an exception.

Instead of adding actions and categories, all examples specify which service to bind to. This is also used in the use of Messenger interprocess communication article to specify servcie for unbinding

val intent = Intent()
intent.component =
    // Parameter 1 server appID,
    // The server needs to bind the full strength of the service
    ComponentName("com.allens.intent"."com.starot.lib_intent.aidl.RemoteService")
bindService(intent, connect, BIND_AUTO_CREATE)
Copy the code

In the second parameter to bindService, we need to pass in the interface for the connection, and after the connection is successful, we need to convert the returned parameter to type YourServiceInterface via the asInterface of the generated Stub object, in this case to type ITest

private var aidl: ITest? = null
private val connect = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName? , service: IBinder?) {
        // The binding service callback succeeded
        aidl = ITest.Stub.asInterface(service);
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        aidl = null}}Copy the code

Passing parameters

Passing arguments to here, that’s the method call.

val data = aidl? .add("Additive computation".10.20)
Copy the code

Pass custom data formats

In the example above, the base type String and int are used, but other base types are supported. For more information, see the documentation on the official website

If you want to customize the type, the official website also provides a solution to pass objects through IPC

Create a custom object

The first step must be to create a custom object. The following example places the entity class in the Java directory com.starot.lib_intent.aidl as shown in 👇.

Put in the complete code,

package com.starot.lib_intent.aidl

class BookInfo(a) : Parcelable {
    var name: String?  = null
    var index: Int = 0

    companion object CREATOR : Parcelable.Creator<BookInfo> {
        override fun createFromParcel(parcel: Parcel): BookInfo {
            return BookInfo(parcel)
        }

        override fun newArray(size: Int): Array<BookInfo> {
            return Array(size) { BookInfo() }
        }
    }

    public constructor(inParcel: Parcel) : this(a) {
        readFromParcel(inParcel)
    }

    override fun writeToParcel(outParcel: Parcel, flags: Int) {
        outParcel.writeString(name)
        outParcel.writeInt(index)
    }

    public fun readFromParcel(inParcel: Parcel) {
        name = inParcel.readString()
        index = inParcel.readInt()
    }

    override fun describeContents(a): Int {
        return 0}}Copy the code

Create an AIDL file

You need to create an AIDL file in the same directory as you used before

Underline 👆 👆 👆

A bookinfo.aidl file is created in the example

// Note that 👇 is in the same level of directory as previously written
package com.starot.lib_intent.aidl;

// Note the lowercase 👇
parcelable BookInfo;

Copy the code

Use the newly created AIDL file

After creating objects that need to be passed between processes, we need to add other methods to do data interaction between processes as per the above basic types of methods. Modify the ITest. Aidl add method created earlier. Note that inout inout is added to the front of all added methods. I’ll put that in the back.

// ITest.aidl
package com.starot.lib_intent.aidl;

// Import the package name of the object
import com.starot.lib_intent.aidl.BookInfo;


interface ITest {
    String add(in String name, int a, int b);

    BookInfo updateBookIn(in BookInfo book);
    BookInfo updateBookOut(out BookInfo book);
    BookInfo updateBookInOut(inout BookInfo book);
}
Copy the code

Copy to the client

Aidl bookinfo. aidl and the entity class bookinfo. kt will be copied to the client. It needs to be in the same directory location

To highlight 👆


Pass custom objects

It’s no different than the basic type, just a method call, okay

var bookInOut = BookInfo().apply {
    name = "allens"index = currentIndex } val updateBookInOut = aidl? .updateBookInOut(bookInOut)Copy the code

In out inout

When we use the custom types above, we find that all the custom types are preceded by a small tag like inout inout. Will there be any problem if eUMM is not added? Tried it on

Throw an error directly. This is explained on the website as well

All non-primitive parameters require direction markers indicating where the data is going

Why??

The official website gives an explanation

You should limit the directions to those you really want, because the marshalling parameters are expensive

What’s the difference

On the client side, the BookInfo object is created with an initial value of name allens and index 1, then synchronized to the server via AIDL, where the property value is modified, and the member variable properties are viewed on the client side

The client code is as follows

var bookIn = BookInfo().apply {
    name = "allens"
    index = 1} aidl? .updateBookIn(bookIn) Log.i(TAG,In name:${bookin. name} index:${bookin. index})


var bookOut = BookInfo().apply {
    name = "allens"
    index = 1} idl? .updateBookOut(bookOut) Log.i(TAG,${bookout. name} index:${bookout. index}")

var bookInOut = BookInfo().apply {
    name = "allens"
    index = 1} aidl? .updateBookInOut(bookInOut) Log.i(TAG,Inout name:${bookinout. name} index:${bookinout. index})

Copy the code

The server code is as follows

override fun updateBookIn(book: BookInfo): BookInfo {
    Log.i(TAG,In name:${book.name} index:${book.index})
    return book.apply {
        name = "River ocean"
        index = 100}}override fun updateBookOut(book: BookInfo): BookInfo {
    Log.i(TAG,Out name:${book.name} index:${book.index})
    return book.apply {
        name = "River ocean"
        index = 100}}override fun updateBookInOut(book: BookInfo): BookInfo {
    Log.i(TAG,Inout name:${book.name} index:${book.index})
    return book.apply {
        name = "River ocean"
        index = 100}}Copy the code

View log print after execution

Demo I: Indicates that the server received the intent. In name:allens index:1 com.allens.demo I: indicates that the client received the intent. Out name:null index:0 com.allens.demo I: Out name: jianghai index:100 com.allens.intent I: The server received InOut name: Allens index:1 com.allens.demo I: the client received inout name: River Ocean index:100Copy the code

As can be seen from printing, data of type IN is allowed to flow from client to server, but not from server to client, as shown below

The OUT type, as opposed to the IN type, allows data to flow from the server to the client, but not from the client to the server

Inout is both