Android AIDL用法介绍
一、简介
- 服务端
服务端首先要创建一个Service来监听客户端连接请求,然后创建一个aidl文件,将接口暴露给客户端,最后在Service中实现这个aidl接口 - 客户端
先绑定服务端的Service,将服务端返回的Binder对象转成aidl接口对应的类型,然后就可以调用aidl接口了 - AIDL接口
并不是所有的数据类型在aidl文件中都可以使用,那aidl文件支持哪些数据类型?
(a) 基本数据类型(int、long、char、boolean、double等)
(b) String和CharSequence
(c) List:只支持ArrayList,里面每个元素都必须被AIDL支持
(d) Map:只支持HashMap,key和value都必须被AIDL支持
(e) Parcelable:所有序列化的对象
(f) AIDL:所有AIDL接口本身也可以在AIDL文件中使用
(g) AIDL除了基本数据类型,其他类型参数需要标上in、out或inout,in为输入型参数,out为输出型参数
(h) AIDL接口中只支持方法,不支持使用静态常量
二、示例
场景是客户端调用服务端aidl接口getMovieList获取影片列表,客户端与服务端位于不同进程(或应用),下面通过例子详细了解下aidl跨进程通讯的基本用法
首先创建Movie.java、Movie.aidl和IMovieMgr.aidl文件,IMovieMgr中声明getMovieList接口,代码如下
// Movie.java
public class Movie implements Parcelable{
public String name;
public Movie(String name) {
this.name = name;
}
@Override public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
@Override public int describeContents() {
return 0;
}
public static final Parcelable.Creator<Movie> CREATOR = new Parcelable.Creator<Movie>() {
@Override public Movie createFromParcel(Parcel source) {
return new Movie(source);
}
@Override public Movie[] newArray(int size) {
return new Movie[size];
}
};
private Movie(Parcel in) {
this.name = in.readString();
}
}
// Movie.aidl
package com.rico.aidl;
parcelable Movie;
// IMovieMgr.aidl
package com.rico.aidl;
// Declare any non-default types here with import statements
import com.rico.aidl.Movie;
interface IMovieMgr {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
List<Movie> getMovieList();
void addMovie(in Movie movie);
}
编译后系统自动生成IMovieMgr.java,通过对IMovieMgr.java的源码分析,可以进一步了解Binder的工作机制
接着定义服务端MovieMgrService.java,这里使用CopyOnWriteArrayList存储影片,原因是CopyOnWriteArrayList支持并发读写,aidl方法是在服务端的binder线程池中执行,当多个客户端同时连接服务端,存在多个线程同时访问资源的情形,所以我们需要在aidl方法中处理线程同步,这里直接使用CopyOnWriteArrayList来自动进行线程同步,代码如下
// AndroidManifest.xml
<service android:name=".MovieMgrService" android:process=":remote"></service>
// MovieMgrService.java
public class MovieMgrService extends Service{
CopyOnWriteArrayList<Movie> mMoveList = new CopyOnWriteArrayList<>();
private Binder mBinder = new IMovieMgr.Stub() {
@Override public List<Movie> getMovieList() throws RemoteException {
System.out.println("movices return mMovieList");
return mMoveList;
}
@Override public void addMovie(Movie movie) throws RemoteException {
mMoveList.add(movie);
}
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble,
String aString) throws RemoteException {
}
};
@Override public void onCreate() {
super.onCreate();
mMoveList.add(new Movie("钢铁侠"));
mMoveList.add(new Movie("速度激情8"));
System.out.println("movices onCreate");
}
@Nullable @Override public IBinder onBind(Intent intent) {
return mBinder;
}
}
最后在客户端调用aidl接口
public class MovieMgrActivity extends Activity{
ServiceConnection mConnection = new ServiceConnection() {
@Override public void onServiceConnected(ComponentName name, IBinder service) {
IMovieMgr movieMgr = IMovieMgr.Stub.asInterface(service);
try {
List<Movie> list = movieMgr.getMovieList();
for (Movie movie : list) {
System.out.println(movie.name);
}
} catch (RemoteException e) {
}
}
@Override public void onServiceDisconnected(ComponentName name) {
}
};
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent i = new Intent(this, MovieMgrService.class);
bindService(i, mConnection, Context.BIND_AUTO_CREATE);
}
@Override protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
运行一下:
07-24 00:08:50.872 21145-21145/com.rico.aidl I/System.out: 钢铁侠
07-24 00:08:50.872 21145-21145/com.rico.aidl I/System.out: 速度激情8
到这里,基本用法介绍完毕,但是aidl的难点还没有涉及,下面继续分析~
服务端还提供了addMovie添加影片的接口,我们尝试在客户端添加一部影片,看看能否添加成功?只需要修改onServiceConnected接口
@Override public void onServiceConnected(ComponentName name, IBinder service) {
IMovieMgr movieMgr = IMovieMgr.Stub.asInterface(service);
try {
System.out.println("添加一部影片:七月安生");
movieMgr.addMovie(new Movie("七月安生"));
System.out.println("影片列表:");
List<Movie> list = movieMgr.getMovieList();
for (Movie movie : list) {
System.out.println(movie.name);
}
} catch (RemoteException e) {
}
}
运行如下:
07-24 07:27:02.207 29092-29092/com.rico.aidl I/System.out: 添加一部影片:七月安生
07-24 07:27:02.207 29092-29092/com.rico.aidl I/System.out: 影片列表:
07-24 07:27:02.208 29092-29092/com.rico.aidl I/System.out: 钢铁侠
07-24 07:27:02.208 29092-29092/com.rico.aidl I/System.out: 速度激情8
07-24 07:27:02.208 29092-29092/com.rico.aidl I/System.out: 七月安生
考虑一种情况,能不能在有影片的时候,自动通知客户端?这是典型的观察者模式,接下来实现这个需求
首先定义IOnNewMovieAddedListener.aidl监听器,针对注册了新影片提醒功能的客户端,服务端才会主动提醒
package com.rico.aidl;
// Declare any non-default types here with import statements
import com.rico.aidl.Movie;
interface IOnNewMovieAddedListener {
void onNewMovieAdded(in Movie movie);
}
接着修改客户端代码,注册监听器IOnNewMovieAddedListener,改动如下:
public class MovieMgrActivity extends Activity{
IMovieMgr movieMgr;
ServiceConnection mConnection = new ServiceConnection() {
@Override public void onServiceConnected(ComponentName name, IBinder service) {
movieMgr = IMovieMgr.Stub.asInterface(service);
try {
List<Movie> list = movieMgr.getMovieList();
for (Movie movie : list) {
System.out.println(movie.name);
}
movieMgr.registerListener(mListener);
} catch (RemoteException e) {
}
}
@Override public void onServiceDisconnected(ComponentName name) {
}
};
IOnNewMovieAddedListener mListener = new IOnNewMovieAddedListener.Stub() {
@Override public void onNewMovieAdded(Movie movie) throws RemoteException {
System.out.println("新电影【"+movie.name+"】上线了");
}
};
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent i = new Intent(this, MovieMgrService.class);
bindService(i, mConnection, Context.BIND_AUTO_CREATE);
}
@Override protected void onDestroy() {
unbindService(mConnection);
try {
if (mListener != null) {
movieMgr.unregisterListener(mListener);
}
} catch (RemoteException e) {
}
super.onDestroy();
}
}
最后修改服务端代码,服务端每个5s自动生成一部新电影,并通知注册过提醒功能的客户端:
public class MovieMgrService extends Service{
CopyOnWriteArrayList<Movie> mMoveList = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList<IOnNewMovieAddedListener> mListenerList = new CopyOnWriteArrayList<>();
private Binder mBinder = new IMovieMgr.Stub() {
@Override public List<Movie> getMovieList() throws RemoteException {
return mMoveList;
}
@Override public void addMovie(Movie movie) throws RemoteException {
mMoveList.add(movie);
}
@Override public void registerListener(IOnNewMovieAddedListener listener)
throws RemoteException {
if (!mListenerList.contains(listener)) {
mListenerList.add(listener);
}
}
@Override public void unregisterListener(IOnNewMovieAddedListener listener)
throws RemoteException {
if (mListenerList.contains(listener)) {
mListenerList.remove(listener);
}
}
};
private void onNewMovieAdded(Movie movie) throws RemoteException {
System.out.println("mListenerList size = "+mListenerList.size());
mMoveList.add(movie);
for (IOnNewMovieAddedListener listener:mListenerList) {
try {
listener.onNewMovieAdded(movie);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override public void onCreate() {
super.onCreate();
mMoveList.add(new Movie("钢铁侠"));
mMoveList.add(new Movie("速度激情8"));
new Thread(new Runnable() {
@Override public void run() {
try {
int id = 0;
while (!isDestory) {
Thread.sleep(5000);
onNewMovieAdded(new Movie("New Movie "+id++));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
boolean isDestory = false;
@Override public void onDestroy() {
super.onDestroy();
isDestory = true;
}
@Nullable @Override public IBinder onBind(Intent intent) {
return mBinder;
}
}
运行一下:
07-24 08:22:22.931 27228-27228/com.rico.aidl I/System.out: 钢铁侠
07-24 08:22:22.931 27228-27228/com.rico.aidl I/System.out: 速度激情8
07-24 08:22:27.930 27228-27240/com.rico.aidl I/System.out: 新电影【New Movie 0】上线了
07-24 08:22:32.932 27228-27241/com.rico.aidl I/System.out: 新电影【New Movie 1】上线了
07-24 08:22:37.935 27228-27240/com.rico.aidl I/System.out: 新电影【New Movie 2】上线了
07-24 08:22:42.939 27228-27241/com.rico.aidl I/System.out: 新电影【New Movie 3】上线了
到这里还没完,当我们按返回键关闭页面的时候,理论上在onDestory中调用movieMgr.unregisterListener解除绑定,可是日志显示:
07-24 08:37:29.020 9341-9353/com.rico.aidl:remote I/System.out: not found, can't unregister
为什么解绑失败了?
在解绑过程中服务端无法找到之前注册的listener,在客户端我们注册和解绑明明用的是同一个listener,为什么服务端无法正常解绑?这种注册解绑的方式是对的,但在多进程中却不适用了,原因是Binder会把客户端传递过来的重新转换成新的对象,服务端接收到的对象已经不是客户端传递的对象了,对象跨进程传输的本质是反序列化的过程,这也是对象为什么需要实现Parcelable接口的原因。
解决方法:使用RemoteCallbackList
RemoteCallbackList是系统专门提供用于删除跨进程listener的接口,虽然跨进程传输客户端的同一个对象会在服务端产生不同对象,但这些新的对象有一个共同点就是他们底层的Binder对象是同一个,我们只要遍历服务端所有的listener,找出那个和解注册listener具有相同binder对象的服务端的listener并删除即可,这些事情RemoteCallbackList都替我们完成了,并且RemoteCallbackList内部还实现了线程同步的功能。
修改服务端代码:
// MovieMgrService.java
RemoteCallbackList<IOnNewMovieAddedListener> mListenerList = new RemoteCallbackList<>();
private Binder mBinder = new IMovieMgr.Stub() {
@Override public List<Movie> getMovieList() throws RemoteException {
return mMoveList;
}
@Override public void addMovie(Movie movie) throws RemoteException {
mMoveList.add(movie);
}
@Override public void registerListener(IOnNewMovieAddedListener listener)
throws RemoteException {
mListenerList.register(listener);
}
@Override public void unregisterListener(IOnNewMovieAddedListener listener)
throws RemoteException {
mListenerList.unregister(listener);
}
};
private void onNewMovieAdded(Movie movie) throws RemoteException {
mMoveList.add(movie);
int num = mListenerList.beginBroadcast();
for (int i=0; i<num; i++) {
IOnNewMovieAddedListener l = mListenerList.getBroadcastItem(i);
if (l != null) {
l.onNewMovieAdded(movie);
}
}
mListenerList.finishBroadcast();
}
aidl的使用介绍完了,最后在强调一下,客户端调用远程的aidl接口,由于被调用的方法运行在服务端binder线程池中,同时客户端被挂起,如果服务端执行了耗时操作,客户端会出现ANR问题,所以不要在UI线程中访问aidl接口~
深入理解aidl,可以阅读之前的博客
Android Binder通讯机制和Android多进程模式
参考【Android开发艺术探索】