Binder AIDL的使用
参考Demo:https://github.com/gqq519/BinderAIDL
- Binder是Android的一个类,实现了IBinder接口
- IPC角度来说,Binder是Android的一种跨进程通信方式,可以理解为一种虚拟的物理设备,设备驱动是/dev/binder。
- 从Framework角度说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager等)和相应的ManagerService的桥梁。
- 从应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回服务端业务调用的Binder对象,客户端由此获取服务端提供的服务或数据。服务包括普通服务和基于AIDL的服务。
Android中,Binder主要用于Service中,包括AIDL和Messenger,普通Service不涉及进程间通信,不涉及Binder的核心,而Messenger底层其实也是AIDL实现的,所以拿AIDL来了解Binder的工作机制。
AIDL简述
AIDL:Android Interface Definition Language,通过编写aidl文件,系统会编译生成Binder接口,用于进程间通信。
AIDL支持的数据格式:
- Java的基本数据类型
- String和CharSequence
- List和Map:
- 元素必须是 AIDL 支持的数据类型
- 具体的类里则必须是 ArrayList 或者 HashMap
- 其他AIDL生成的接口
- 实现Parcelable的类
创建AIDL示例
1. 创建工程
2. 创建要操作的实体类,需要实现Parcelable接口,跨进程使用
1 | public class User implements Parcelable { |
3. 创建实体类的映射aidl文件
右键新建AIDL文件User.aidl(名称与实体类保持一致),会在main下生成aidl目录,包名与java包名一致。
User.aidl文件为实体类的映射文件,需要声明映射的实体类和类型:
1 | // User.aidl |
4. 创建操作接口aidl文件
在aidl目录的包名下创建AIDL文件IUserManager.aidl,内部是一个接口,主动实现了void basicTypes()
方法,在接口中定义需要跨进程操作的接口:
1 | // IUserManager.aidl |
比如我们定义了两个方法用于操作:
- addUser()添加User
- getUserList()获取用户列表
注意:
- 定义的Parcelable实体类型,需要导入它的全路径,比如User,需要导入
import com.gqq.binderaidl.User;
- 方法参数中,除了基本数据类型外都需要标上类型:in(输入), out(输出), inout(输入输出)
5. Make Project,生成Binder的java文件
上述操作完成后,点击Build
-> Make Project
,完成后可以在build/generated/source/aidl/packageName/
下找到生成的java文件。
IUserManager的大致预览:
生成的代码主要给客户端使用,后续再介绍里面的内容吧~
至此,通信的媒介我们已经完成了。
注意:Make Project 出现错误可能的原因
- 映射的aidl:User.aidl和实体类User,名称要保持一致,包名要保持一致。
- 定义的AIDL接口文件的方法参数需要标上类型。
- AIDL接口文件中导入实体类的包名。
编写服务端代码
1. 创建Service
在项目中创建Service,需要实现onBind()方法,返回值为IBinder,根据上述生成的IUserManager.java,内部Stub继承自Binder,所以,onBind() 的返回值设置为AIDL的接口的实例。
1 | public class UserService extends Service { |
2. 清单注册Service
Service创建好之后在清单文件注册:
1 | <service android:name=".UserService" |
至此,服务端工作已完成。
编写客户端代码
1. 新建Client工程
客户端可以做为一个单独的App,或者跟服务端在不同的进程都可。
2. 拷生成文件到Client工程
在客户端工程下,将服务端生成的AIDL的java文件以及实体类User.java 一起拷过来,更改client的目录与Server保持一致(拷过来会报错)。
3. 绑定服务
1 | public class MainActivity extends AppCompatActivity { |
运行
首先运行服务端,再运行客户端,就可以通过客户端操作了。
升级:设置监听
假如现在用户希望服务端当有新用户时实时的告诉我,这个是一个典型的观察者模式,在实际中也用到很多。
1. 提供AIDL接口,作为监听
提供一个AIDL接口,客户端需要实现这个接口并注册提醒的功能,也可以随时取消这个提醒。使用AIDL接口是因为AIDL中无法使用普通接口。
服务端创建一个aidl文件:
1 | package com.gqq.binderaidl; |
2. 在原用的IUserManager.aidl接口中添加注册和取消注册的方法
添加注册和反注册的方法,以便于客户端可以监听。
1 | package com.gqq.binderaidl; |
写完make project。
3. 完善服务端的Service
主要是实现Service中的IUserManager.Stub的实现,因为新增了两个方法。另外模拟场景:开启线程,每隔5s新增一个用户并通知客户端。
1 | // ------------定义的变量--------------- |
服务端的修改已经完成。
4. 客户端注册监听并处理接收
把服务端新加的aidl文件生成的java文件复制到客户端项目中,客户端注册监听,并在页面退出时解除注册。同时在onUserArrived方法中接收到数据后要回到主线程显示等,所以借助Handler实现。
1 | private ServiceConnection connection = new ServiceConnection() { |
升级:取消监听
在上述的场景设置中,当退出客户端页面,取消注册,通过日志可以查看到,当取消注册的时候会发现unregisterListener中remove的时候发生了异常,因为在多线程中,Binder会把客户端传递过来的对象重新转化并生成一个新的对象。对象是不能跨进程传递的,我们跨进程传递的时候都是把对象进行序列化和反序列化。那如何实现取消注册呢?需要借助RemoteCallbackList。
RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口,可以从源码中看出:
1 | public class RemoteCallbackList<E extends IInterface> |
跨进程的对象虽然不一样,但Binder是同一个,利用Binder来实现,客户端解除注册的时候遍历所有的服务端的listener,将具有相同Binder 的listener删除即可,这个是RemoteCallbackList所做的事情。同时,还有一个功能,当客户端进行终止后,它会自动解除客户端所注册的listener。
代码改善
利用RemoteCallbackList实现解除注册:用RemoteCallbackList代替List<>
1 | private RemoteCallbackList<IOnNewUserArrivedListener> listeners = new RemoteCallbackList<>(); |
修改注册和解注册的方法:
1 |
|
修改onNewUserArrived方法:
1 | private void onNewUserArrived(User user) throws RemoteException { |
注意:RemoteCallbackList并不是一个List,无法像操作List一样操作它,要像上述的方式一样去遍历它,其中
beginBroadcast() 与 finishBroadcast() 必须配对使用,哪怕是想要获取RemoteCallbackList的元素个数。
升级:耗时处理
客户端调用服务端的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端挂起。如果服务端的方法是耗时的操作,客户端在UI线程的话,就会导致ANR。客户端的onServiceConnected和onServiceDisconnected运行在UI线程中,不能直接在里面调用服务端的耗时方法。服务端的方法本身运行在Binder线程池中,可以做大量的耗时操作,不用再开启线程去进行异步操作。模拟一下耗时的操作:
1 | // 服务端模拟耗时操作的方法 |
客户端点击按钮去直接调用服务端的方法获取list数据,多次点击就会出现ANR,那么就需要把调用放到非UI线程,比如:
1 | findViewById(R.id.btn_client).setOnClickListener(new View.OnClickListener() { |
同样,我们不可以在服务端UI线程中调用客户端耗时的操作,另外AIDL接口方法都运行在Binder线程池中,访问UI需要切换线程。例如客户端的onNewUserArrived方法。
升级:服务重连
Binder在通信中可能意外死亡,往往由于服务端进程意外停止,需要重新连接服务。
1. Binder设置DeathRecipient监听
设置DeathRecipient监听,当Binder死亡时,会收到binderDied方法的回调,可以在binderDied方法中重连服务。
2. 在OnServiceDisconnected中重连远程服务
区别:onServiceDisconnected在客户端UI线程被回调,binderDied在客户端的binder线程池中被回调,即binderDied中不能访问UI。
升级:权限验证
定义权限非本节重点:定义权限参考
首先在服务端的AndroidMenifest中声明所需权限
1 | <permission android:name="com.gqq.binderaidl.permission.ACCESS_USER_SERVICE" |
第一种方法:在onBind中进行验证(permission验证)
1 |
|
第二种方法:在onTransact中进行验证(包名验证)
1 | public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws RemoteException { |
客户端AndroidMenifest中声明:
1 | <uses-permission android:name="com.gqq.binderaidl.permission.ACCESS_USER_SERVICE" /> |
总结
主要是AIDL的整体使用流程。