AIDL简介
AIDL是Android接口定义语言,有点类似于我们开发中的普通接口。由于不同进程间不能共享内存,为了解决进程间通信的问题,可以通过AIDL接口语言来实现进程间的通信。
AIDL文件支持的数据类型
- 基本数据类型(int、long、char、boolean、double)
- String和CharSequence
- List和Map集合
- 集合内元素必须是AIDL支持的数据类型
- 服务端具体使用的集合必须是ArrayList和HashMap
- Parcelable:实现了Parcelable接口的对象
- AIDL本身接口也可以在AIDl文件使用。
AIDL使用步骤
主要有三大步骤
- 创建AIDL
- 创建实例类,必须实现Parcelable接口,用于序列化和反序列化
- 新建aidl文件夹,在其创建与实例类同名的aidl文件及其他aidl接口文件
- rebuild项目,生成Binder的java接口文件。
- 创建服务端(Service)
- 创建Service,在onBind方法中返回Binder实例对象,并实现aidl接口方法。
- 编写客服端(Activity或其他)
- 使用bindService方式启动服务,实现ServiceConnection接口,拿到aidl生成的Binder对象。
- 调用aidl定义好的函数方法。
AIDL具体实现
为了方便AIDL的开发,建议所有和AIDL相关的类和文件全部放在同一个包,这样方便其他地方需要使用,可以直接复制过去。
下面是具体例子的文件目录:
aidl文件目录是和java目录同级的
1、创建进程间需要传输的实体类,实现 Parcelable 接口
public class Book implements Parcelable{
private String bookName;
public Book(String bookName) {
this.bookName = bookName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.bookName);
}
protected Book(Parcel in) {
this.bookName = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public String toString() {
return "Book{" +
"bookName='" + bookName + '\'' +
'}';
}
}
实现 Parcelable 接口是为了实例类能够在进程间通信。
其实进程间通信是一个序列化和反序列化的过程,关于序列化可以参考这篇文章Android进程通信 - 序列化Serialzable与Parcelable
2、新建aidl文件夹,在其创建与实例类同名的aidl文件及其他aidl接口文件
a.、新建aidl文件夹,主要aidl目录和java目录是同级的,在main目录下面。
b、创建与实体类(Book)同名的aidl文件:Book.aidl
// Book.aidl
package com.hzw.progress.aidl;
//声明Book实体类已序列化,可用于进程间传输
parcelable Book;
这样做的目的是让aidl文件知道Book类实现了Parcelable接口,可将Book对象作为进程间传输数据的载体。
注意:Book实体类和Book.aidl文件必须要在相同包结构目录下,否则会运行出错,是因为客服端需要反序列化服务端的AIDL接口相关的类,如果类的路径不一样的话,客户端会反序列失败。
IBookManager.aidl接口
// IBookManager.aidl
package com.hzw.progress.aidl;
// Declare any non-default types here with import statements
//虽然在IBookManager.aidl和Book.aidl同一个包名下,但必须以import方式将Book.aial文件导入(手动导入)
import com.hzw.progress.aidl.Book;
import com.hzw.progress.aidl.OnNewBookAddListener;
//定义客户端与服务端间的回调接口
interface IBookManager {
//得到全部数据(全部书籍)
List<Book> getBookList();
//添加数据的操作(添加书籍)
void addBook(in Book book);
//注册监听,用于监听新数据的变化。是典型的观察者模式的运用
void registerListener(OnNewBookAddListener listener);
//解注册
void unregisterListener(OnNewBookAddListener listener);
}
OnNewBookAddListener.aidl接口文件
// OnNewBookAddListener.aidl
package com.hzw.progress.aidl;
import com.hzw.progress.aidl.Book;
// Declare any non-default types here with import statements
//定义监听新数据变化的接口(添加新书籍)
interface OnNewBookAddListener {
void onNewBookAdd(in Book book);
void onAllBook();
}
以上需要注意的点,在addBook和onNewBookAdd方法接参中有个in,表示输入型参数。AIDL 中除了基本数据类型,其他数据类型必须标上方向,in,out 或者 inout。
- in 表示输入型参数
- out 表示输出型参数
- inout 表示输入输出型参数
c、在创建好了aidl文件后,需要 rebuild项目,生成Binder的java接口文件。
3、创建服务端BookManagerService继承于Service
public class BookManagerService extends Service {
private static final String TAG = "BookManagerService";
private List<Book> mBookList=new ArrayList<>();
private static List<OnNewBookAddListener> mAddListenerList=new ArrayList<>();
public BookManagerService() {
}
@Override
public void onCreate() {
super.onCreate();
//初始化书籍
mBookList.add(new Book("Android群英传"));
mBookList.add(new Book("Android开发艺术探索"));
}
@Override
public IBinder onBind(Intent intent) {
//获取Service需要的权限,进行自我验证,防止其他进程访问该服务
int check= checkCallingOrSelfPermission("com.hzw.progress.aidl.permission.LOCAL");
//未授权该应用,则无法访问
if (check== PackageManager.PERMISSION_DENIED){
return null;
}
//添加新书的过程
BookFactory factory = new BookFactory();
factory.execute(5); //添加五本书
return mBinder;
}
/*
* 初次new IBookManager方式构建Binder对象,会报错找不到IBookManager对象
* 需要Rebuild下项目
* */
private Binder mBinder=new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
@Override
public void registerListener(OnNewBookAddListener listener) throws RemoteException {
if (!mAddListenerList.contains(listener)){
mAddListenerList.add(listener);
}
Log.i(TAG, "registerListener: "+mAddListenerList.size());
}
@Override
public void unregisterListener(OnNewBookAddListener listener) throws RemoteException {
if (mAddListenerList.contains(listener)){
mAddListenerList.remove(listener);
}
}
};
/**
* 使用静态内部类,避免泄漏
*/
private static class BookFactory extends AsyncTask<Integer,Book,Book>{
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Book doInBackground(Integer... integers) {
//每隔2秒添加新建一本新书,最多只能添加5本
int i=0;
Book mBook=null;
while (i< integers[0]){
mBook=new Book("新书"+(i+1));
try {
Thread.sleep(2000);
publishProgress(mBook);
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return mBook;
}
@Override
protected void onProgressUpdate(Book... values) {
super.onProgressUpdate(values);
//监听每次添加数据内容
for (int i = 0; i <mAddListenerList.size() ; i++) {
OnNewBookAddListener listener = mAddListenerList.get(i);
try {
if (listener!=null){
listener.onNewBookAdd(values[0]);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
protected void onPostExecute(Book book) {
super.onPostExecute(book);
//得到全部的书籍
for (int i = 0; i <mAddListenerList.size() ; i++) {
OnNewBookAddListener listener = mAddListenerList.get(i);
try {
if (listener!=null){
listener.onAllBook();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
服务端是Service的典型实现,具体使用可以参考这篇文章Service实例深入理解,以上需要注意的点:
- 在onBind方法中,最好添加访问权限的判断,防止一些恶意的访问,可使用 Permission 验证,在 manifest 中声明。
- 创建Binder 对象时,不能使用new Binder ()的方式,必须使用new IBookManager.Stub()创建Binder对象,这是AIDL接口文件自动生成的Binder对象。
最后需要在Mainfest文件中注册Service,同时自定义定义服务端的访问权限并引用。
<!--自定义访问Service的权限-->
<permission android:name="com.hzw.progress.aidl.permission.LOCAL"/>
<!--声明本拥有该权限-->
<uses-permission android:name="com.hzw.progress.aidl.permission.LOCAL"/>
<service
android:name=".aidl.BookManagerService"
android:enabled="true"
android:exported="true"
android:process=":remote"
android:permission="com.hzw.progress.aidl.permission.LOCAL">
</service>
注意:BookManagerService要在独立进程中需要,必须定义属性 android:process=":remote"
3、编写客服端
绑定远程服务后,通过ServiceConnection得到代理对象Binder,同时将Binder对象转换成AIDL接口,然后通过AIDL接口去调用服务端的方法。
public class AIDLActivity extends AppCompatActivity {
private static final String TAG = "AIDLActivity";
private Intent mIntent;
private IBookManager mIBookManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
mIntent = new Intent(this, BookManagerService.class);
}
public void onClick(View view) {
switch (view.getId()){
case R.id.bindService:
//绑定服务
bindService(mIntent,mConnection,BIND_AUTO_CREATE);
break;
case R.id.unbindService:
//解绑服务
unbindService(mConnection);
break;
}
}
private ServiceConnection mConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
//ClassCastException
// IBookManager iBookManager=(IBookManager)service;
mIBookManager = IBookManager.Stub.asInterface(service);
mIBookManager.addBook(new Book("Android进阶之光"));
List<Book> bookList = mIBookManager.getBookList();
// Log.i(TAG, "全部书籍: "+bookList.toString());
mIBookManager.registerListener(sListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
try {
mIBookManager.unregisterListener(sListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private OnNewBookAddListener sListener=new OnNewBookAddListener.Stub() {
@Override
public void onNewBookAdd(Book book) throws RemoteException {
mIBookManager.addBook(book);
Log.i(TAG, "onNewBookAdd: "+book.toString());
}
@Override
public void onAllBook() throws RemoteException {
List<Book> bookList = mIBookManager.getBookList();
Log.i(TAG, "onAllBook: "+bookList.toString());
}
};
}
以上就是客户端的基本实现,需要注意一下几点:
- 在得到代理对象Binder时,不能直接使用强转的方式,否则会报类型转换异常,必须使用IBookManager.Stub.asInterface()得到AIDL接口文件生成的Binder对象。
- 在需得到一个AIDl接口实例对象,需要.Stub()方法得到实例。
以上三大步骤就是AIDL实现进程通信的简单例子,运行结果如下:
07-14 17:59:47.974 6970-6988/com.hzw.progress I/AIDLActivity: onNewBookAdd: Book{bookName='新书1'}
07-14 17:59:49.974 6970-6989/com.hzw.progress I/AIDLActivity: onNewBookAdd: Book{bookName='新书2'}
07-14 17:59:51.974 6970-6988/com.hzw.progress I/AIDLActivity: onNewBookAdd: Book{bookName='新书3'}
07-14 17:59:53.975 6970-6989/com.hzw.progress I/AIDLActivity: onNewBookAdd: Book{bookName='新书4'}
07-14 17:59:55.976 6970-6988/com.hzw.progress I/AIDLActivity: onNewBookAdd: Book{bookName='新书5'}
07-14 17:59:55.977 6970-6989/com.hzw.progress I/AIDLActivity: onAllBook: [Book{bookName='Android群英传'},
Book{bookName='Android开发艺术探索'}, Book{bookName='Android进阶之光'}, Book{bookName='新书1'},
Book{bookName='新书2'}, Book{bookName='新书3'}, Book{bookName='新书4'}, Book{bookName='新书5'}]
总结
上面的例子基本实现了AIDL进程间的通信,其中还存在很多疑问,比如:
1.AIDL 在实现通信过程中具体做哪些内容?
2. 服务端创建Binder对象或者得到AIDL接口对象,都在调用Stub()方法实现,那么该方法具体有哪些操作?
3. 什么是Binder?
使用AIDL需注意的点
- AIDL接口文件的包结构必须与实体类的同结构,否则会反序列化失败
- 与实体类同名的AIDL接口文件,必须使用parcelable声明Book类已序列化,注意全是小写
- 在一个AIDL文件中使用另外一个AIDL文件时,需手工显式(全包名)导入。
以上任何一个点不小心搞错的话,AIDL就无法实现进程通信。
关于上面的几个疑问,计划在下篇学习
Android进程通信 - AIDL使用解析与Binder浅谈
参考
- 安卓开发艺术探索
- https://blog.csdn.net/luoyanglizi/article/details/51980630
- https://www.jianshu.com/p/b9b15252b3d6
- https://blog.csdn.net/u011240877/article/details/72765136