Android 远程调用服务之 AIDL

目录

一、AIDL 是什么?
二、为什么要使用 AIDL?

1、使用 AIDL 是为了跨进程调用第三方服务?

2、使用 AIDL 是为了向第三方服务传输数据/参数?

3、使用 AIDL 是为了获取第三方服务直接或者异步返回的数据?
三、提供哪些文件给客户端?

1、直接 copy 所有的 .aidl 文件给客户端,并要求客户端保持包结构;

2、先编译 .aidl 文件,然后把 build 编译后生成的 .java 文件 copy 给客户端,并要求客户端保持包结构;

3、把 .aidl 文件放在一个单独的 (Android Studio) Module 中,build 编译生成的 .aar 文件 copy 给客户端依赖;
四、AIDL 项目结构

1、Server 独立工程,AIDL 独立工程,Client 独立工程。

2、Server + AIDL 独立工程,Client 独立工程。(推荐)
五、案例:Client 跨进程调用 Server 端的 Service 服务,并获取异步处理数据的结果。

一)、本案例项目结构:

二)、AidlLib 接口编写:

1、新建 AIDL Folder

2、新建 .aidl 文件配置

3、新建 AIDL 通讯接口文件 IMyTestAidlInterface.aidl

4、新建 searchKeyWord() 方法中的参数回调接口 IMyTestCallback.aidl

5、如果需要使用 Java Bean 数据类了,需要怎么处理呢?

6、最后单独 aidl 代码的 Module 执行 build 编译,把生成的 .aar 文件 copy 给 客户端依赖。

三)、Server 服务提供方:

1、修改 Server 的 build.gradle 文件,源码依赖 aidl 的 Module

2、新建 Service 类 MyService.java

3、在清单文件中配置 Servcie

四)、Client 业务调用方:

1、把 .aar 文件 copy 到 libs 目录中,并配置依赖

2、在界面上添加一个 TextView

3、点击 TextView 时 bind 调用远端 Service

4、创建上面 bindService() 需要的 intent 参数

5、创建上面 bindService() 需要的 ServiceConnection 参数

6、创建 callback 对象,提供给 searchKeyWord() 方法参数使用

7、在清单文件中配置 <queries>
六、测试步骤和效果

1、 安装 Servcie 类所在的应用

2、安装客户端应用

3、启动客户端应用

4、点击客户端 Activity 的 TextView,异步获取服务端的内容
七、测试环境

​----------------------------------

Demo 代码:

AIDL_Server_test:https://github.com/mengzhinan/AIDL_Server_test
AIDL_Client_test:https://github.com/mengzhinan/AIDL_Client_test


正文内容

一、AIDL 是什么?

AIDL 英文全称:Android Interface Definition Language ,中文含义:Android 接口定义语言

Android 接口定义语言 (AIDL) 官方文档:Android 接口定义语言 (AIDL) | Android 开发者 | Android Developers

AIDL 是 Android 接口定义语言,是一种代码规范,需要通过编译后生成 .java 文件,然后提供给 Server 和 Client 使用,实现 Android 不同应用之间跨进程通讯。

二、为什么要使用 AIDL?

1、使用 AIDL 是为了跨进程调用第三方服务?

我觉得不一定。不使用 AIDL ,构造好 intent,直接调用 context.startService / context.bindService 调起第三方服务也可以通讯。

2、使用 AIDL 是为了向第三方服务传输数据/参数?

我觉得不一定。在使用 context.startService / context.bindService 方法调用服务时,可以在 intent 中携带参数。

3、使用 AIDL 是为了获取第三方服务直接或者异步返回的数据?

对,我认为是这个。要不然还需要在 .aidl 文件中编写接口方法干嘛,当然是想获取远端 Service 的处理结果,甚至是远端 Service 的异步处理结果。

三、提供哪些文件给客户端?

AIDL 只是一种接口定义语言规范,不是可执行的 Java 代码。编写好 .aidl 文件后,还需要 build 编译才会生成最终的 .java 文件。

那么,应该提供哪些文件给调用端(客户端)呢?

1、直接 copy 所有的 .aidl 文件给客户端,并要求客户端保持包结构;

2、先编译 .aidl 文件,然后把 build 编译后生成的 .java 文件 copy 给客户端,并要求客户端保持包结构;

3、把 .aidl 文件放在一个单独的 (Android Studio) Module 中,build 编译生成的 .aar 文件 copy 给客户端依赖;

四、AIDL 项目结构

从上面的信息了解到,可以以多种形式向客户端提供 .java 文件。同时发现,如果提供 .aar 包的话,连 .aidl 文件的包结构都不用关心。

.aidl 文件独立 Module 的方式也有两种,我更倾向于第二种:

1、Server 独立工程,AIDL 独立工程,Client 独立工程。

新建一个 AIDL 的工程,然后新建一个 Module,把所有的 .aidl 文件和必要的 .java 数据传输文件放在改 Module 中,build 编译产出 .aar 文件。

最后把 .aar 文件提供给 Server 和 Client 依赖使用。

测试时,只需要运行 Server 和 Client 的 app Module 即可,AIDL 工程的 app Module 不需要安装在测试机中。

2、Server + AIDL 独立工程,Client 独立工程。(推荐)

在 Server 工程中,新建一个 Module 专门用于存放某业务的 .aidl 文件,build 编译产出 .aar 文件。

Server 与 AIDL 之间直接使用 Module 方式源码依赖就可以了,Client 与 AIDL 之间还是使用 .aar 包方式依赖。

五、案例:Client 跨进程调用 Server 端的 Service 服务,并获取异步处理数据的结果。

一)、本案例项目结构:

二)、AidlLib 接口编写:

1、新建 AIDL Folder

aidl 目录下存放所有的 .aidl 文件,java 目录下存放所有的 .java 文件。

建议 aidl 和 java 目录的包名一致。如果坚决不想保持一致,那数据类 Java Bean 的包结构必须保持一致,比如: UserData.java 和 UserData.aidl 文件。

2、新建 .aidl 文件配置

如果 AIDL File 选项是灰色的不可点击,则需要在该 Module 下的 build.gradle 文件中添加配置 aidl = true,如:

java 复制代码
android {
    。。。
 
    // 添加 aidl folder
    sourceSets {
        getByName("main") {
            aidl {
                srcDirs("src/main/aidl")
            }
        }
    }
 
    buildFeatures {
        // 如果 aidl 目录未变色/无法新建 aidl 文件,则需要配置这个
        aidl = true
    }
}
3、新建 AIDL 通讯接口文件 IMyTestAidlInterface.aidl

计划在客户端调用 searchKeyWord() 方法,并传入 IMyTestCallback 回调接口。

等到服务端处理完数据后,调用 IMyTestCallback 回调接口向客户端传回数据。

java 复制代码
// IMyTestAidlInterface.aidl
package com.fffffff.aidllib;
 
// Declare any non-default types here with import statements
import com.fffffff.aidllib.IMyTestCallback;
 
interface IMyTestAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void searchKeyWord(int anInt, String aString, IMyTestCallback myCallback);
 
    int addNum(int a, int b);
}
4、新建 searchKeyWord() 方法中的参数回调接口 IMyTestCallback.aidl

等待远端 Server 的 Service 处理完数据后,主动调用该接口的 onXXX() 方法回传数据。

java 复制代码
// IMyTestCallback.aidl
package com.fffffff.aidllib;
 
// Declare any non-default types here with import statements
import com.fffffff.aidllib.UserData;
 
interface IMyTestCallback {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void onResult(String msg, in UserData userData);
 
    void onFailure(String error);
}
5、如果需要使用 Java Bean 数据类了,需要怎么处理呢?

首先,在 java 目录下新建 Java 类 UserData.java ,然后实现序列化接口 Parcelable。

如果 UserData 参数设置了 out 或者 inout 标签的话,还需要实现如下方法:

java 复制代码
//    /**
//     * 参数方向为 out or inout 时,才需要此方法
//     * 手动添加此方法
//     *
//     * @param in in
//     */
//    public void readFromParcel(Parcel in) {
//        percentage = in.readInt();
//        msg = in.readString();
//    }

更多关于 in、out 和 inout 标签的用法,请继续查阅资料,我也不太了解。

第二步,还需要在 aidl 目录下编写 UserData.java 的 UserData.aidl 描述文件

注意,文件中 parcelable 的 p 是小写的。

6、最后单独 aidl 代码的 Module 执行 build 编译,把生成的 .aar 文件 copy 给 客户端依赖。

三)、Server 服务提供方:

1、修改 Server 的 build.gradle 文件,源码依赖 aidl 的 Module
2、新建 Service 类 MyService.java

因为客户端想要获取服务端的计算结果,所以要使用 bindService 方式调用服务。

因此服务端需要实现 onBind(Intent intent)() 方法,并返回 binder 对象,使客户端能够调用到 aidl 定义的方法。

onBind(Intent intent)() 方法需要返回一个 IBinder 对象,刚好 IMyTestAidlInterface.Stub 抽象类实现了 IBinder 接口:

java 复制代码
package com.fffffff.aidlserver;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.Nullable;
import com.fffffff.aidllib.IMyTestAidlInterface;
import com.fffffff.aidllib.IMyTestCallback;
import com.fffffff.aidllib.UserData;
 
public class MyService extends Service {
 
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new IMyTestAidlInterface.Stub() {
            @Override
            public void searchKeyWord(int i, String s, IMyTestCallback iMyTestCallback) throws RemoteException {
                UserData userData = new UserData();
                userData.percentage = i;
                userData.msg = s;
                iMyTestCallback.onResult("服务端处理完毕", userData);
            }
 
            @Override
            public int addNum(int a, int b) throws RemoteException {
                return a + b;
            }
        };
    }
}

注意:

IMyTestAidlInterface.Stub.searchKeyWord() 方法没有直接返回值,而是使用异步的方式回传数据。

当数据处理完毕后,调用 IMyTestCallback 接口的 onResult() 方法回传数据。

3、在清单文件中配置 Servcie

注意配置:

是否启用此 Service 类:android:enabled="true"

是否允许其他进程调用此服务:android:exported="true"

自己定义一个 Action,不重复就行:<action android:name="com.fffffff.aidlserver.action.MY_SERVICE_CENTER" />

四)、Client 业务调用方:

1、把 .aar 文件 copy 到 libs 目录中,并配置依赖
2、在界面上添加一个 TextView
3、点击 TextView 时 bind 调用远端 Service
java 复制代码
tvShow.setOnClickListener {
    tvShow.isClickable = false
    // 点击 TextView 绑定服务
    val intent = BindServerUtil.buildIntent3()
    this.applicationContext?.bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
4、创建上面 bindService() 需要的 intent 参数

调用 servcie 的 intent 方式很多。

setComponent() 方式:

java 复制代码
@JvmStatic
fun buildIntent1(): Intent {
    val intent = Intent()
    // setComponent() 方式
    intent.component = ComponentName("com.fffffff.aidl_server_test", "com.fffffff.aidlserver.MyService")
    return intent
}

setClassName() 方式:

java 复制代码
@JvmStatic
fun buildIntent2(): Intent {
    val intent = Intent()
    // setClassName() 方式
    intent.setClassName("com.fffffff.aidl_server_test", "com.fffffff.aidlserver.MyService")
    return intent
}

setAction() 方式:

java 复制代码
@JvmStatic
fun buildIntent3(): Intent {
    val intent = Intent()
    // setAction() 方式
    intent.action = "com.fffffff.aidlserver.action.MY_SERVICE_CENTER"
    intent.setPackage("com.fffffff.aidl_server_test")
    return intent
}

注意:

上面提到的包名为,数据提供方(服务端)的可执行 Module 的包名,比如常见的 App Module,不是 Service 类所在的子 Module 的包名。

上面提到的 Service 全类名为,Service 类实际所在的包名 + 类名称。

上面提到的 Action 为,数据提供方(服务端)中 Service 类清单文件中定义的 Action 值。

上面三种方法,推荐使用第三方方法 action 的方式。因为你不确定服务端以后会不会对代码重构,如果修改 Service 的类名或包名了呢。

5、创建上面 bindService() 需要的 ServiceConnection 参数

ServiceConnection 是一个接口,直接 new 一个实现类传入到 bindService() 方法中。

在 onServiceConnected() 抽象方法中,使用 IMyTestAidlInterface.Stub.asInterface(IBinder) 方法把 service: IBinder? 参数转换为 IMyTestAidlInterface 类型。即服务端 Service 中返回的 binder 对象。

java 复制代码
// 服务绑定成功/失败的回调
private val connection: ServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        tvShow.text = "连接成功"
        try {
            iMyTestAidlInterface = IMyTestAidlInterface.Stub.asInterface(service)
 
            val callback = createBinderCallback()
            iMyTestAidlInterface?.searchKeyWord(404, "wuhan", callback)
        } catch (e: Exception) {
            tvShow.text = "连接失败 error = ${e.message}"
            e.printStackTrace()
        }
    }
 
    override fun onServiceDisconnected(name: ComponentName?) {
        tvShow.text = "连接断开"
    }
}

有了服务端的 IMyTestAidlInterface 对象,就可以直接调用服务端实现的接口方法了。

此处调用 searchKeyWord() 方法,并传入回调接口,类似于网络请求的成功与失败的 callback 接口。

6、创建 callback 对象,提供给 searchKeyWord() 方法参数使用

小心,有坑,巨坑

searchKeyWord() 方法需要 IMyTestCallback 类型的对象作为参数,那么是直接 new 一个 IMyTestCallback 的实现类吗?不是啊,坑坑坑

而是需要 new 一个 IMyTestCallback.Stub 类型的实现类对象:

java 复制代码
/**
 * 客户端的回调接口需要 new IMyTestCallback.Stub 的实现类,
 * 而不是 new IMyTestCallback 的实现类。
 */
private fun createBinderCallback(): IMyTestCallback.Stub {
    return object : IMyTestCallback.Stub() {
        override fun onResult(p0: String, p1: UserData?) {
            tvShow.text = "$p0:\n" + "UserData = $p1\nint = ${p1?.percentage}\nmsg = ${p1?.msg}"
        }
 
 
        override fun onFailure(p0: String?) {
            tvShow.text = "获取失败 = $p0"
        }
 
    }
}

最后,在回调 callback 中获取服务端 Service 返回的数据,设置到 TextView 中。

7、在清单文件中配置 <queries>

如果只是上面的内容及配置,在高版本的 Android 系统中运行是无法调起远端 Service 的,因为 Android 11 版本对手机已安装应用的包可见行做了限制。

如果不配置的话,则客户端应用无法发现服务端应用,导致无法调起。

配置如下:

包可见行定义有两种方式。

-> 定义 action 可见。

即只对服务端某个 Service 的 action 配置可见。配置好后,客户端就可以通过 action 隐私启动绑定服务端的 Service 了。

java 复制代码
<!-- 定义某个 app 的某个 action 可见 -->
<intent>
    <action android:name="com.fffffff.aidlserver.MY_SERVICE_CENTER" />
</intent>

-> 定义应用的包名可见。

即针对某个应用的包名可见。

拿本例说,不是服务端的 aidl Module 的包名,也不是服务端的 Service 类所在的 Module 的包名。而是 Service 类所在的可运行的 Module,即 app Module 的包名。

java 复制代码
<!-- 定义某个 app 应用的包名可见 -->
<package android:name="com.fffffff.aidl_server_test" />

六、测试步骤和效果

1、 安装 Servcie 类所在的应用

注意:杀死服务端 app,无需提前启动服务端

2、安装客户端应用

3、启动客户端应用

TextView 显示默认的内容

4、点击客户端 Activity 的 TextView,异步获取服务端的内容

客户端已经成功获取服务端返回的数据了。

>>>>>>>> 完毕 <<<<<<<

七、测试环境

当前测试环境的详情如下:

Android Studio 版本:Android Stuido Giraffe | 2022.3.1 Patch 1

Gradle::8.0

JDK:17

TargetSDK:33

模拟器版本:Pixel 7 Pro API 33

tip:

一开始不要使用真机测试,尤其是定制化的手机,比如小米/华为等。我的 Demo 在小米/华为手机上,一直无法调起服务端 Service,网上查阅资料说是手机系统做限制了,禁止关联启动。

使用 Google 模拟器测试,就可以成功调起。

Demo 代码:

AIDL_Server_test:https://github.com/mengzhinan/AIDL_Server_test
AIDL_Client_test:https://github.com/mengzhinan/AIDL_Client_test

相关推荐
不爱学习的啊Biao5 分钟前
【13】MySQL如何选择合适的索引?
android·数据库·mysql
Clockwiseee33 分钟前
PHP伪协议总结
android·开发语言·php
mmsx7 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
众拾达人10 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
吃着火锅x唱着歌11 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
_Shirley12 小时前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙
hedalei14 小时前
RK3576 Android14编译OTA包提示java.lang.UnsupportedClassVersionError问题
android·android14·rk3576
锋风Fengfeng14 小时前
安卓多渠道apk配置不同签名
android
枫_feng14 小时前
AOSP开发环境配置
android·安卓
叶羽西15 小时前
Android Studio打开一个外部的Android app程序
android·ide·android studio