一文搞懂Android AIDL

介绍

AIDL(Android Interface Definition Language)用于在Android应用中实现进程间通信(IPC)。它允许不同应用或服务间的数据传递和方法调用,确保不同进程间能够相互访问和操作。通过AIDL,开发者可以定义一个接口,然后在服务端和客户端实现这个接口,实现跨进程的交互。背景上,AIDL主要解决了Android系统中的进程隔离问题,使得不同应用或进程能够安全有效地共享数据和功能。

示例

话不多说,直接开搂!

下面示例创建了两个安卓项目,一个服务端(com.xaye.aidl_1)一个客户端(com.xaye.aidl_2)

step1

如果你的项目中没有使用过AIDL,那么当你创建AIDL文件时,会遇到下图这种情况👇
在这里插入图片描述
解决方法是在你使用AIDL的模块build.gradle文件中添加以下代码

    buildFeatures {
        aidl true
    }

然后再rebuild一下就可以啦(●’◡’●)

step2

需要创建一个AIDL服务的接口,因为要供其他应用调用,创建AIDL文件时,需要右击main文件夹创建,因为AIDL需要和java文件夹在同级目录下,效果如下👇
在这里插入图片描述

此时的 libary 是作为主项目的子模块,当然 你也可以直接写在主模块中。

写之前先了解下在AIDL(Android Interface Definition Language)中,默认支持的几种数据类型:

  1. 基本数据类型
  • boolean:布尔值(true 或 false)
  • byte:8 位有符号整数
  • char:16 位 Unicode 字符
  • double:64 位双精度浮点数
  • float:32 位单精度浮点数
  • int:32 位有符号整数
  • long:64 位有符号整数
  • short:16 位有符号整数
  1. Java 对象
  • String:字符串
  • CharSequence:字符序列
  1. Bundle
  • Bundle 类可以用于在进程间传递一组键值对的集合,类似于一个映射表。
  1. List 和 Map
  • List<T>:支持基本数据类型和支持 Parcelable 接口的对象类型的列表。
  • Map<K, V>:支持基本数据类型和支持 Parcelable 接口的对象类型的映射表。
  1. Parcelable
  • 自定义的类如果实现了 Parcelable 接口,也可以在 AIDL 中使用。这是一个用来序列化对象的接口,可以将对象数据从一个进程传递到另一个进程。
  1. IBinder
  • IBinder 接口可以在 AIDL 中作为参数传递,允许在不同的进程间传递服务对象的引用。

step3

为了上点难度,咱们自定义两种类型 VideoParamsVideoCallback

创建服务接口(com.xaye.aidl_1):

可以参照上图

// IRemoteService.aidl
package com.xaye.library;

// 引用 VideoParams 和 VideoCallback
import com.xaye.library.VideoParams;
import com.xaye.library.VideoCallback;

interface IRemoteService {
   void processVideoClip(in VideoParams params, in VideoCallback callback);
}
VideoParams

VideoParams 是一个java bean,代码中需要对其序列化,在aidl中使用就是下面这样。

// VideoParams.aidl
package com.xaye.library;

import com.xaye.library.VideoParams;

parcelable VideoParams;
VideoCallback

一个简单的回调接口。

// VideoCallback.aidl
package com.xaye.library;

interface VideoCallback {
    void onSuccess(String filePath);
    void onError(String error, String throwableMessage);
}

aidl 包下要写的东西就这么些,其他应用要使用的话,需要把整个aidl文件夹复制到main下。

step4

如 step2 图示,在java包中 VideoParams 就是一个实现序列化后的bean,VideoService 是一个普通的安卓Service,在这个服务里处理其他应用发送的事件,代码如下,就简单处理返回了下。

public class VideoService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new IRemoteService.Stub() {
            @Override
            public void processVideoClip(VideoParams params, VideoCallback callback) {

                try {
                    callback.onSuccess("aidl 1 回调数据!");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        };
    }
}

这个 IRemoteService 需要rebuild 后才会有,没build会爆红。

至此… 呃,还有一点,在清单文件中注册该服务,这里主要注意要指定 action ,因为其他应用需要该 action 去找到你的服务。

        <service android:name=".VideoService"
            android:exported="true">
            <intent-filter><!--需要接受的action,其他应用指定-->
                <action android:name="com.xaye.libary.IRemoteService" />
            </intent-filter>
        </service>

至此,服务端代码完成。

补充: 在实际开发中其实没必要传实体类,定义和接收都比较繁琐,直接将实体类使用fastjson 转为json字符串,客户端接收的时候再解析下json就行了!

step5 :实现客户端代码(com.xaye.aidl_2)

无图言吊,上图,客户端代码结构
在这里插入图片描述
正如上面说的,把aidl文件夹 直接复制进来,然后,客户端调用 processVideoClip 方法,需要VideoParamsVideoCallback 两个类,因为 VideoCallback 类中的数据aidl默认都支持,所以你rebuild后就能直接导入,而VideoParams是自定义数据类,需要把它放到java包 和 服务端同包名下即可,这样在运行时就能通过包名找到同一个VideoParams了。

服务端代码如下👇

class TestActivity : AppCompatActivity() {
    private val TAG = "TestActivity"

    private lateinit var binding: ActivityTestBinding

    private var remoteService: IRemoteService? = null
    private var connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.i(TAG,"test aidl client onServiceConnected")
            remoteService = IRemoteService.Stub.asInterface(service)
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            Log.i(TAG,"test aidl client onServiceDisconnected")
        }

    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTestBinding.inflate(layoutInflater)
        setContentView(binding.root)
        bindService()
        binding.btnTest.setOnClickListener {
            Log.i(TAG,"test aidl client click  remoteService == null : ${remoteService == null}")
            remoteService?.processVideoClip(VideoParams("","",""),object : VideoCallback.Stub(){
                override fun onSuccess(filePath: String?) {
                    Log.i(TAG,"test aidl client onSuccess $filePath")
                    runOnUiThread {
                        Toast.makeText(this@TestActivity,filePath,Toast.LENGTH_LONG).show()
                    }
                }

                override fun onError(error: String?, throwableMessage: String?) {
                    Log.i(TAG,"test aidl client onError $error")
                    runOnUiThread {
                        Toast.makeText(this@TestActivity,"$error $throwableMessage",Toast.LENGTH_LONG).show()
                    }
                }

            })
        }
    }

    private fun bindService() {
        val intent = Intent()
        intent.action = "com.xaye.libary.IRemoteService"//子模块中注册时 要求的action
        intent.setPackage("com.xaye.aidl_1") //目标应用包名,注意不是子模块的
        val bound = bindService(intent, connection, BIND_AUTO_CREATE)
        if (bound) {
            Log.i(TAG,"test aidl client bindService succeed")
        } else {
            Log.i(TAG,"test aidl client bindService failed")
        }

    }
}

首先就是bindService去绑定远程服务,在ServiceConnection 中拿到服务端的服务,然后就可以愉快滴调用服务端方法了,这里主要注意下 intent.action 是服务注册时指定的!setPackage 是服务端所属的应用包名,即使你的服务在子模块中,也要写主应用包名!

安装上客户端 和 服务端应用,运行上面代码,效果如下图
在这里插入图片描述

至此,全剧终。


AIDL没什么难的,只是平时用的比较少,记得刚入行的时候听到感觉好高级,因为工作中没有需求,就一直没有机会去了解(其实就是懒😄),当亲手实操下,看到数据返回时,内心还是有点兴奋的,又get到一个技能,哈哈。

源码已上传csdn资源,0积分:AIDL客户端服务端源码

07-10
### AIDL 使用指南 #### 1. **AIDL 的基本概念** Android 接口定义语言(AIDL)是一种接口描述语言,用于在 Android 应用之间进行跨进程通信(IPC)。它允许一个应用通过 Binder 机制调用另一个应用中的服务方法。这种机制基于代理模式实现,使得客户端和服务端能够通过接口进行交互[^2]。 #### 2. **AIDL 的优势** 与其他 IPC 方式相比,例如广播、共享文件或 `ContentProvider`,AIDL 提供了更高的效率和更好的用户体验: - **高效性**:AIDL 直接使用 Binder 进行通信,相较于使用 Intent 或广播传递复杂对象,具有更高的性能。 - **异步支持**:AIDL 支持异步调用,服务端可以通过回调机制响应客户端请求。 - **类型安全**:AIDL 支持多种数据类型(包括复杂结构),编译器会在编译时检查数据类型以确保安全性。 - **易于维护**:AIDL 接口定义清晰,便于后续的维护和扩展[^3]。 #### 3. **AIDL 接口的定义与实现** ##### 定义 AIDL 接口 首先,在 Android 项目中创建 `.aidl` 文件来定义接口。例如,假设需要定义一个简单的远程服务接口 `IMyAidlInterface.aidl`: ```aidl // IMyAidlInterface.aidl package com.example.myapp; interface IMyAidlInterface { int add(int a, int b); } ``` 该接口定义了一个名为 `add` 的方法,接受两个整数参数并返回它们的和。 ##### 实现服务端逻辑 接下来,在服务端应用中创建一个 `Service` 类,并实现上述接口: ```java public class MyAidlService extends Service { private final IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() { @Override public int add(int a, int b) throws RemoteException { return a + b; } }; @Override public IBinder onBind(Intent intent) { return binder; } } ``` 同时,需要在 `AndroidManifest.xml` 中注册此服务,并声明适当的权限以允许外部访问: ```xml <service android:name=".MyAidlService" android:exported="true"> <intent-filter> <action android:name="com.example.myapp.IMyAidlInterface"/> </intent-filter> </service> ``` ##### 客户端绑定服务 客户端通过绑定到服务的方式获取接口实例,并调用远程方法: ```java private IMyAidlInterface myAidlInterface; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { myAidlInterface = IMyAidlInterface.Stub.asInterface(service); try { int result = myAidlInterface.add(5, 7); Log.d("AIDL", "Result: " + result); } catch (RemoteException e) { Log.e("AIDL", "Remote call failed", e); } } @Override public void onServiceDisconnected(ComponentName name) { myAidlInterface = null; } }; Intent intent = new Intent(); intent.setComponent(new ComponentName("com.example.myapp", "com.example.myapp.MyAidlService")); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); ``` #### 4. **常见问题与解决方案** ##### 问题 1: `RemoteException` 异常处理 当客户端尝试调用远程服务的方法时,可能会抛出 `RemoteException`。这是由于 IPC 通信失败导致的,例如服务未启动或 Binder 被断开连接。 **解决方案**: - 在调用 AIDL 方法时使用 `try-catch` 捕获异常。 - 确保服务已正确启动并在客户端成功绑定后才调用方法。 ```java try { int result = myAidlInterface.add(5, 7); } catch (RemoteException e) { Log.e("AIDL", "Remote call failed", e); } ``` ##### 问题 2: 权限不足 (`SecurityException`) 如果客户端没有足够的权限访问服务,会抛出 `SecurityException`。 **解决方案**: - 在服务端的 `AndroidManifest.xml` 中配置正确的权限。 - 客户端申请必要的权限(如 `uses-permission`)。 ```java try { int result = myAidlInterface.add(5, 7); } catch (SecurityException e) { Log.e("AIDL", "Permission denied", e); } ``` ##### 问题 3: 大对象传输效率低下 AIDL 不适合传输大对象(如大型图片或视频)。这是因为每次传输都需要序列化和反序列化,这可能导致性能下降。 **解决方案**: - 避免使用 `inout` 关键字传递大对象。 - 对于大数据传输,建议使用其他方式,如 `ContentProvider` 或 `Socket` 通信。 ##### 问题 4: 线程安全问题 AIDL 调用默认运行在 Binder 线程池中,因此需要注意线程安全问题。 **解决方案**: - 如果服务端方法涉及共享资源操作,应使用同步机制(如 `synchronized`)保护关键代码段。 - 避免在 AIDL 方法中执行耗时操作,以免阻塞 Binder 线程。 #### 5. **最佳实践** - **接口设计简洁**:保持 AIDL 接口简单明了,避免过于复杂的逻辑。 - **版本控制**:随着功能迭代,注意接口版本管理,确保兼容性。 - **日志记录**:在关键位置添加日志输出,方便调试和排查问题。 - **测试验证**:编写单元测试验证 AIDL 接口的功能和稳定性。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值