AIDL的使用

397 阅读6分钟

CS(Client-Server)架构
1.客户端 Client
通过绑定服务端的Service来获取一个IBinder对象(也可以理解为是服务端AIDL的java类),然后用这个对象调用AIDL中定义的接口方法
步骤如下:
1)将服务端的jar包放在自己工程下的libs中,add as library导包
2)在Activity中绑定服务端的Service(通过Service的包名和类名)
3)创建一个ServiceConnection对象,实现onServiceConnected和onServiceDisconnected方法,在onServiceConnected方法中获取IBinder对象的代理,并转换为AIDL接口类型。 4)获取到IBinder对象的代理后,调用服务端提供的接口,做处理。

ComponentName componentName = new ComponentName("com.example.aidlserver", "com.example.aidlserver.AIDLService");
intent.setComponent(componentName);
bindService(intent, connection, BIND_AUTO_CREATE);

2.服务端 Server
是指提供数据或功能给客户端,它通过创建一个Service并在onBind()方法中返回一个IBinder对象来实现通信接口,该对象需要重写.aidl文件中定义的接口方法 。
步骤如下:
1)在工程下右键new 选择Module,创建一个library,在这里创建.aidl,这样做可以打成jar包给其他模块使用。
2)library下右键new AIDL创建一个 IMyAIDL.aidl文件 在这里定义给客户端提供的接口(代码AIDL里边没有提示只能自己手敲)
3)接口都定义好后,点下锤子编译,会自动生成AIDL对应的一个java类,不用管生成了就行,生成的位置如下:

image.png 4)在工程下创建一个AIDLService,实现AIDL接口,在onbind中返回binder对象(IMyAidlInterface.Stub()获得IBinder)。
5)在AndroidManifest.xml文件中将Service添加上,并设置如下属性: 设置intent-filter 定义一个action,让其他应用可以通过intent启动服务

<service
    android:name=".AIDLService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.example.aidlserver.AIDL_SERVICE" />
    </intent-filter>
</service>

6)这些都做好了之后,在Module下的build.gradle中找到编译jar包的地方运行编译个jar包出来,路径在build文件下的lib中,把这个jar包给客户端使。

客户端和服务端需要共享一个.aidl文件,用来声明通信接口和方法,该文件会被Android SDK工具转换成一个Java接口,该接口包含一个Stub类和一个Proxy类 。


如果单纯创建一个.aidl文件的话,那么客户端和服务端都需要放这个.aidl文件,并且包名必须完全一致,aidl文件一模一样。因为AIDL机制在底层是通过Binder实现的,Binder需要:

  • 相同的接口描述:确保两端对方法签名、参数、返回值的理解一致
  • 相同的包名:用于唯一标识和匹配接口
  • 相同的接口名:用于查找和调用
    (可以把服务端的aidl文件夹直接拷贝过来,aidl文件夹和java文件夹同级目录)

实践:
服务端
1)创建aidl文件夹,创建.aidl文件,前提是build.gradle中得把aidl true,然后就能选择AIDL创建了,在aidl中定义好接口,然后点锤子Build,自动生成对应的接口java文件。
2)选择activity自动创建好一个activity不用改啥东西,然后创建service,new binder实例并实现(重写)接口,然后在onBind方法中返回binder,manifest里service保证是 android:exported="true"
客户端:
1)选择empty views activity创建活动,在oncreate里面bindService()

  private void bindService() {
        // 客户端绑定方式
        Intent intent = new Intent();
// 直接指定组件名称,不依赖intent-filter
        intent.setComponent(new ComponentName(
                "com.ljx.aidltest",  // 服务端包名
                "com.ljx.aidltest.aidl.MyService2"  // 服务类全名
        ));
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
        //下面这个排查没连上的原因,首先排除服务端是否是OK状态,我没连上是因为包名错了,包名是build.gradle中那个包名
        PackageManager pm = getPackageManager();
        ComponentName cn = new ComponentName("com.ljx.aidltest", "com.ljx.aidltest.aidl.MyService2");
        try {
            getPackageManager().getPackageInfo("com.ljx.aidltest", 0);
            Log.i("ljn", "服务端应用已安装");
        } catch (PackageManager.NameNotFoundException e) {
            Log.e("ljn", "服务端应用未安装");
        }
    }

    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.i("ljn", "onServiceConnected: ");
            myAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
            try {
                int count = myAidlInterface.getCount(1);
                Log.i("ljn", "onServiceConnected: " + count);
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };
manifest里面活动这样写的
<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTask">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

AIDL进阶
一、实体类Parcelable

如果AIDL中定义的接口传的参数是基本数据类型,那么直接用即可。???如果不是基本数据类型,例如实体类这种包含多个类型的,需要创建实体Bean类,并序列化Parcelable 序列化就是将对象转换为可存储或可传输的状态。

1)服务端创建个实体类,并实现Parcelable接口将其序列化。序列化都是模板代码,如果不想自己写自动生成的话,AS需要安装插件Android Parcelable Code Generator,在需要序列化的类中,右键->generate->parcelable 选中需要序列化的成员变量,即可完成对象的序列化。

2)创建实体类同名的AIDL,写下面的代码就完事

package com.example.aidlserver;

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

parcelable TestBean;

3)在给外部提供的AIDL的接口中传入实体类

二、AIDL参数的数据流向

aidl中这样使用:TestBean changeTestBean(in TestBean bean);
在传递序列化参数时,必须定义这些参数的数据流方向:in、out、inout

  • in:表示数据从客户端流向服务端,客户端会将参数对象复制一份并发送给服务端,服务端收到后可以对该对象进行修改,但不会影响客户端的原始对象 。

  • out:表示数据从服务端流向客户端,客户端会将参数对象的空引用发送给服务端,服务端收到后可以创建一个新的对象并赋值给该引用,然后返回给客户端,客户端会将原始对象替换成服务端返回的对象 。

  • inout:表示数据双向流动,客户端会将参数对象复制一份并发送给服务端,服务端收到后可以对该对象进行修改,并将修改后的对象返回给客户端,客户端会将原始对象替换成服务端返回的对象 。

三、oneway修饰

oneway 关键字用于修改远程调用的行为,用来修饰 AIDL 接口中的方法。
1.本地调用(同步调用) 如果 oneway 用于本地调用,则不会有任何影响,调用仍是同步调用。
2.远程调用(异步调用) 使用oneway时,远程调用不会阻塞;它只是发送事务数据并立即返回。将远程调用改为「异步调用」,使得远程调用变成非阻塞式的
Aidl中接口方法用了关键字oneway,主要是将数据或通知发送给服务端,不需要等待服务端的回调,是非阻塞的。

注意:oneway不能用于修饰有返回值的方法

参考juejin.cn/post/722132…