AIDL

AIDL使用讲解

Posted by Allen Vork on May 6, 2018

AIDL 是什么

AIDL(Android Interface Definition Language)安卓接口定义语言用于定义客户端与服务器端的通信接口,目的是为了实现跨进程通信。

AIDL 语法

  • aidl 文件以 .aidl 为后缀
  • aidl 支持的数据类型:
    • 8种基本数据类型:short、int、long、float、double、byte、char、boolean
    • CharSequence、String
    • List 类型。List 中的所有元素必须是这里列出来的类型,或者是其他 AIDL 生成的接口或实现了 Parcelabe 的类型。
    • Map 类型。 Map 所持有的参数类型和 List 一样。泛型的 Map (如 Map<String,String>)是不支持的。
  • AIDL 文件分为两类:
    • 用于声明实现了 Parcelable 接口的数据类型。上面讲到 List 和 Map 是可以持有实现了 Parcelable 接口的数据类型。该类型并不能直接被 aidl 文件使用,而是要创建一个同名的 AIDL 文件,然后声明一下,后面会具体讲
    • 用于定义进程间通信的接口。我们把需要给客户端调用的方法在该文件中暴露出来。
  • 定向 Tag。它决定了跨进程通信中数据的流向。作用于上面 AIDL 文件中定义的方法中的参数,有 in、out、inout 三种。由于方法是由服务器端定义的,所以 in 代表数据只能由客户端流入服务器端,out 则相反,inout 是双向流通。基本数据类型、CharSequence、String 和其他 AIDL文件定义的方法接口的定向 Tag 的默认值为 in 且不能修改。其余的类型需要明确标注定向 Tag。
  • AIDL 文件需要明确标明引用到的数据类型所在的包名。

Sample

服务器端

1.首先创建一个项目, 如 aidlServer,包名为 com.example.allen.aidlserver 。
2.创建一个实现了 Parcelable 的数据类,为了统一管理,我创建了一个 aidl 目录用于存放所有 aidl 使用到的数据类:

package com.example.allen.aidlserver.aidl;

/**
 * AIDL 文件是如何使用 Parcelable 类型的数据的
 */
public class Avenger implements Parcelable {
    public String name;
    
    @Override
    public int describeContents() {
        return 0;
    }

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

    //注意要手动添加 readFromParcel 方法,否则后面调用的时候会报错
    public void readFromParcel(Parcel in) {
        this.name = in.readString();
    }

    public Avenger() {
    }

    protected Avenger(Parcel in) {
        this.name = in.readString();
    }

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

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

3.由于 Parcelable 数据类不能被 aidl 文件直接引用,要创建一个与上面 Avenger 数据类同名的 aidl 文件将该数据类声明进去。 右键 module -> New -> AIDL -> AIDL File 然后命名为 Avenger。 注意到我们之前创建的 Avenger.java 数据类在我们自定义的一个 aidl 目录中,而要声明该数据类的 aidl 文件除了要与之同名外,地址也必须相同,所以我们也要创建一个 aidl 目录,然后将刚才创建的 Avenger.aidl 文件移进去。 我们将该类中的所有东西全部删掉,然后将该数据类的包名和类名声明一下:

package com.example.allen.aidlserver.aidl;

parcelable Avenger;

这样 AIDL 类中的方法就可以直接使用该 Parcelable 类型的数据了。
4.这时我们就可以真正开始定义客户端与服务器端进行交互的 aidl 接口了。同样创建一个 aidl 文件:

package com.example.allen.aidlserver;
//要将所引用的的数据类型手动 import 进来,基本类型之类的数据则不用
import com.example.allen.aidlserver.aidl.Avenger;

interface IAvengerAidlInterface {
        List<String> getAvengers();

        void addAvengerInOut(inout Avenger avenger);

        void addAvengerIn(in Avenger avenger);

        void addAvengerOut(out Avenger avenger);
}

然后 clean 下就会生成相应的 IAvengerAidlInterface.java 文件 5.通信接口定义好后就需要创建一个 Service 来给客户端远程绑定。

class AidlService : Service() {

    //前面讲过 AIDL 只支持 ArrayList,为什么这里可以用 CopyOnWriteArrayList 
    //以为 AIDL 中支持的是抽象的 List 而 List 只是一个接口。虽然返回的是 CopyOnWriteArrayList
    //但 Binder 会根据 List 的规范去访问数据并返回 ArrayList 给客户端
    private var avengerList: MutableList<Avenger> = CopyOnWriteArrayList<Avenger>()

    override fun onCreate() {
        super.onCreate()
        initAvengers()
    }

    private fun initAvengers() {
        for (i in 0..4) {
            val avenger = Avenger()
            avenger.name = "avenger$i"
            avengerList.add(avenger)
        }
    }

    //aidl 接口在编译后所生成的 java 同名文件中会有一个 Stud 的内部类
    private val stub = object : IAvengerAidlInterface.Stub() {
        override fun getAvengers(): MutableList<Avenger> {
            return avengerList
        }

        override fun addAvengerInOut(avenger: Avenger?) {
            if (avenger == null)
                Log.e("AidlService", "addAvengerInOut:null")
            else {
                avengerList.add(avenger)
                Log.e("AidlService", "addAvengerInOut:${avenger.name}")
            }
        }

        override fun addAvengerIn(avenger: Avenger?) {
            if (avenger == null)
                Log.e("AidlService", "addAvengerIn:null")
            else {
                avengerList.add(avenger)
                Log.e("AidlService", "addAvengerIn:${avenger.name}")
            }
        }

        override fun addAvengerOut(avenger: Avenger?) {
            if (avenger == null)
                Log.e("AidlService", "addAvengerOut:null")
            else {
                avengerList.add(avenger)
                Log.e("AidlService", "addAvengerOut:${avenger.name}")
            }
        }
    }

    // 直接返回 IAvengerAidlInterface.Stub 给客户端来调用我们定义的方法
    override fun onBind(intent: Intent): IBinder? {
        return stub
    }
}

Service 完成后就需要去 Manifest 中声明下。客户端可以直接通过指定 Service 类名的方式来绑定,也可以通过先指定包名,然后配置 Action 的方式来绑定(这种方式需要定义 action)。

        <service android:name=".service.AidlService"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.allen.aidlserver.avengeraction"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>

总结:服务器端做的就是定义与服务器端通信的接口,然后创建 Service 并实现所定义的 aidl 接口里的方法供客户端调用。

客户端

创建一个项目 aidlclien,包名为 com.example.allen.aidlclient。然后将服务器端的 aidl 目录复制过来到 java 的同级目录。 然后创建一个与服务器端的 Avenger.java 文件所在相同的包名来存放该类。

然后我们就可以绑定 Service 并调用服务器端接口所定义的方法了。

class MainActivity : AppCompatActivity() {

    private var avengerAidlInterface: IAvengerAidlInterface ?= null
    private var isConnected = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        bindService()
        initView()
    }

    override fun onDestroy() {
        super.onDestroy()
        if (isConnected) unbindService(serviceConnection)
    }

    //--------------绑定服务器的服务,拿到 IAvengerAidlInterface-----------------
    private fun bindService() {
        val intent = Intent();
        intent.`package` = "com.example.allen.aidlserver";
        intent.action = "com.example.allen.aidlserver.avengeraction";
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    private val serviceConnection = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?) {
            isConnected = false
        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            avengerAidlInterface = IAvengerAidlInterface.Stub.asInterface(service)
            isConnected = true
        }
    }
    //-----------------------------------------------------------------------

    private fun initView() {
        inoutButton.setOnClickListener {
            if (isConnected) {
                try {
                    val avenger = Avenger()
                    avenger.name = "Black Panther"
                    avengerAidlInterface?.addAvengerInOut(avenger)
                } catch (e: RemoteException) {
                    Log.e("MainActivity", "Oh man! What's going on? Black Panther will come for U")
                }
            }
        }

        inButton.setOnClickListener {
            if (isConnected) {
                try {
                    val avenger = Avenger()
                    avenger.name = "Black Widow"
                    avengerAidlInterface?.addAvengerInOut(avenger)
                } catch (e: RemoteException) {
                    Log.e("MainActivity", "Oh man! What's going on? Black Widow will come for U.")
                }
            }
        }

        outButton.setOnClickListener {
            if (isConnected) {
                try {
                    val avenger = Avenger()
                    avenger.name = "Captain America"
                    avengerAidlInterface?.addAvengerInOut(avenger)
                } catch (e: RemoteException) {
                    Log.e("MainActivity", "Oh man! What's going on? Captain America will come for U.")
                }
            }
        }

        getAvengersButton.setOnClickListener {
            if (isConnected) {
                try {
                    val avenger = Avenger()
                    avenger.name = "Black Panther"
                    val avengers = avengerAidlInterface?.avengers
                } catch (e: RemoteException) {
                    Log.e("MainActivity", "Give me the list of avengers or U will be in trouble.")
                }
            }
        }
    }
}

这样,一个完整的进程间通信就完成了。

定向 Tag

in: 数据只能有客户端传给服务器,服务器对数据的修改不会影响到客户端。如客户端调用 addAvengerIn(in Avenger) 方法将 Avenger 对象传给服务器,服务器拿到后,修改 avenger 的名字,客户端是不会发生改变的。
out: 数据只能由服务器传给客户端,若客户端调用 addAvengerIn(out Avenger) 方法,服务器端能接受到这个对象,但该对象内的属性值为空。服务器端拿到该对象后,修改其属性值会同步到客户端。
inout: 所有修改都会同步。

与之相关的还有死亡代理,Binder 连接池,下面的文章比较详细,就不赘述了