AIDL学习分享
AIDL文件分类:一类是parcelable对象,另一类是interface来定义接口。
服务器端
- parcelable对象,那序列化对象的方法有两种,为什么选择parcelable而不是serserializable,是因为设计内存操作时,在进程间通信serserializable的开销过大
// Book.aidl
//用于引入了一个序列化对象Book,供其他的AIDL文件使用
//注意:Book.aidl与Book.java的包名应当是一样的
package com.example.service;
//注意parcelable是小写
parcelable Book;
- 用来定义方法接口,以供项目使用来完成跨进程通信
// BookManager.aidl
package com.example.service;
//导入所需要使用的非默认支持数据类型的包
import com.example.service.Book;
interface BookManager {
//所有的返回值前都不需要加任何东西,不管是什么数据类型
List<Book> getBooks();
Book getBook();
int getBookCount();
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么按需而定
void setBookPrice(in Book book , int price)
void setBookName(in Book book , String name)
void addBookIn(in Book book);
void addBookOut(out Book book);
void addBookInout(inout Book book);
}
- 服务器端还需要编写自己的service,这个service需要在manifest中注册,就是安卓的四大组件之一。
AIDLService.java类
package com.example.service;
public class AIDLService extends Service {
//BookManager是aidl通信接口的名字
private final IBinder mBookManager = new BookManager.Stub() {
//实现aidl中声明的方法
//mBookManager就是IBinder对象
}
public void onCreate() {
//可以不用设置视图,可以做一些数据的初始化操作
}
public IBinder onBind(Intent intent) {
//连接成功,返回获取到的aidl通信接口
//将IBinder对象返回,提供给client端使用
return mBookManager;
}
public void onDestroy() {
//结束service
super.onDestroy();
}
- 此处还需要一个Book实体类,保证其包名与Book.aidl一致
客户端
客户端(client)的Activity类
private BookManager mBookManager = null;
private List<Book> mBooks;
/**
* 尝试与服务端建立连接
*/
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("com.example.aidl");
intent.setPackage("com.example.service");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
//创建ServiceConnection来实现IBinder对象调用aidl生成的类的操作方法
//asInterface是在跨进程时返回Stub.Proxy对象。这个Proxy对象可以直接调用aidl生成类的方法
//有了mServiceConnection ,才能使用attemptToBindService方法连接服务端Service
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(getLocalClassName(), "service connected");
//据实际情况返回 IBinder 的本地对象或其代理对象
mBookManager = BookManager.Stub.asInterface(service);
mBound = true;
//连接后才可以使用mBookManager 的方法
if (mBookManager != null) {
try {
mBooks = mBookManager.getBooks();
Log.e(getLocalClassName(), mBooks.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
重写onServiceConnected,会传入一个IBinder对象,这个IBinder对象可以转换为mBookManager来操作我们在service中定义的方法。
项目结构图
客户端发起请求,Service端返回请求,根据Service端的要求,处理请求。
AIDL工作原理
客户端AIDLActivity和服务器端AIDLService是通过BookManager这个aidl接口文件来实现通信的,从用户的角度看,是AIDLService.java和AIDLActivity.java在进行通信,实际上是客户端的本地代理Proxy在和服务器端的Stub类通过transact和onTransact进行通信;
下面来梳理一下Stub-Proxy的工作流程:
(1)AIDLService中的IBinder对象mBookManager是Stub的具体实现,在AIDLService和AIDLActivity绑定的时候被返回;
(2)被返回的mBookManager最终作为客户端onServiceConnnected中的参数;
(3)客户端AIDLActivity会通过本地代理Proxy调用Stub中的静态方法asInterface,mServiceBinder作为参数,最后拿到接口对象mBookManager;
在asInterface方法中,有如下判断流程:
(1)判断Binder是否处于当前进程,若不在,则构建Proxy并返回,构建Proxy时,把mBookManager赋值给mRemote;
(2)Proxy中实现的接口getInformation会调用mRemote的transact方法,而Binder的通信是靠transact和onTransact实现的;
(3)最后会走到Stub的onTransact,完成对mServiceBinder的调用;
生成java文件的源码分析
- IInterface:IRemoteService继承自IInterface,所有Binder通信的接口必须继承IInterface接口
- Stub继承了Binder类并实现了IRemoteService,而学过activity与service通信的都知道,在Service中的onBind方法会返回一个Binder对象,而Stub继承了Binder,又是一个抽象类,所以Stub可以作为我们跨进程通信时,服务端自定义Binder的父类。
Stub 的构造方法中 , 调用了 Binder 的 attachInterface 方法 , 传入了 AIDL 文件的全类名 ; 作用是将该 AIDL 接口与 Binder 进行关联 - DESCRIPTOR:Binder的唯一标识,通常是包名+接口名组成的字符串
- asInterface:asInterface将服务端Binder转换为客户端所需的AIDL接口类型对象。在方法中首先会调用queryLocalInterface(DESCRIPTOR),参数是binder的唯一标识,用来查询本地接口。如果能查询到本地接口IRemoteService(客户端和服务端处于同一进程),就返回本地接口,不跨进程通信。如果查询不到本地接口,就构造Proxy代理对象并返回,通过代理对象可以跨进程通信。
- asBinder():用于返回当前Binder对象
- Proxy代理类:构造Proxy时,把服务端的Binder对象赋值给mRemote,Proxy中实现了自定义方法:setPause和setPlay ,通过调用mRemote的transact方法,最后走到Stub的onTransact,完成对服务端Binder的调用。
- transact 方法对应 Binder 底层发起 IPC 的请求函数 ;
- onTransact 方法用于处理不同的 IPC 请求 , 根据不同的值 , 处理不同的 IPC 请求 ;
要处理的 IPC 请求对应的常量值定义 : 将要调用的方法变成 int 类型的 ID 常量值 , 根据传入的常量值执行相应的方法 , 方便 IPC 跨进程调用 ;
为什么有ADIL来定义接口,直接public interface不好吗?
通过继承Binder的方式,会获得Service的实例,然后调用Service里所有的方法。当有些方法不想被公开的时候,当然你可以将方法声明为private,但是倘若到后期,又需要公开这些方法,你是不是还要将声明改为public?当方法较多时,显然不符合松耦合的思想。
可以将MyBinder继承Binder的类私有,用MyBinder里的方法,调用Service中的私有方法(这些方法是不公开的,但是可以通过这种方法调用)。这时候,我们如果将MyBider里的方法私有,外部就访问部了Service的方法了。
参考文章:
https://www.jianshu.com/p/332c5a6e5608
https://blog.csdn.net/qq_41739313/article/details/123322808
https://blog.csdn.net/bless2015/article/details/45896427