AIDL使用注意事项
第一、AIDL支持的文件类型
1、基本类型
2、String和CharSequence
3、List:只支持ArrayList
4、Map:只支持HashMap
5、Parcelable:所有实现Parcelable的对象
6、AIDL:所有的AIDL接口本身也可以在AIDL文件中使用
第二、自定义的Parcelable对象和ADIL对象的时候,必须通过import引入
第三、自定义的Parcelable对象,必须新建一个同名的AIDL文件,并在其中声明他为parcelable类型。
第四、处理基本数据类型,其他类型的参数必须表明方向,in(输入型), out(输出型),inout(输入输出型)
第五、AIDL接口只支持方法,不支持静态常量
第六、AIDLde 包结构在服务端和客户端要保持一致
AIDL实现客户端和服务端通信
注册
<service
android:name=".ipc_demo3.BookManagerService"
android:process="com.book.manager.remote" />
<activity android:name=".ipc_demo3.ClientAndServiceDemoActivity" />
Activity类
public class ClientAndServiceDemoActivity extends Activity implements View.OnClickListener {
private ServiceConnection serviceConnection;
public IBookManager iBookManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.client_and_service_demo);
findViewById(R.id.binder_service_tv).setOnClickListener(this);
findViewById(R.id.add_book).setOnClickListener(this);
findViewById(R.id.get_book_list).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.binder_service_tv:
bindMyService();
break;
case R.id.add_book:
addBook();
break;
case R.id.get_book_list:
getBookList();
break;
default:
break;
}
}
/**
* 绑定服务
*/
private void bindMyService() {
Intent serviceIntent = new Intent(this, BookManagerService.class);
serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBookManager = IBookManager.Stub.asInterface(service);
//获取书单列表
try {
List<Book> booKList = iBookManager.getBooKList();
printBookList(booKList);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
/**
* 添加书单
*/
private void addBook() {
if (iBookManager == null) {
return;
}
try {
iBookManager.addBook(new Book(14, "Android进阶"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 获取书单列表
*/
private void getBookList() {
if (iBookManager == null) {
return;
}
try {
List<Book> booKList = iBookManager.getBooKList();
printBookList(booKList);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void printBookList(List<Book> booKList) {
if (EmptyUtils.isEmpty(booKList)) {
return;
}
for (Book book : booKList) {
Log.e("IPC 通信", "书单:" + book);
}
}
@Override
protected void onDestroy() {
iBookManager = null;
if (serviceConnection != null) {
unbindService(serviceConnection);
serviceConnection = null;
}
super.onDestroy();
}
}
Service类
public class BookManagerService extends Service {
/**
* 使用CopyOnWriteArrayList进行自动的线程同步
* AIDL支持的List数据只支持ArrayList类型,这里为什么可以使用CopyOnWriteArrayList呢?
* 因为AIDL中所支持的是抽象的List,而List只是一个接口,因为虽然服务端返回的是CopyOnWriteArrayList
* 但是是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端。
* 所以在服务端采用CopyOnWriteArrayList时完全可以的。
*/
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
//创建AIDL的Binder对象
private Binder mBinder = new IBookManager.Stub() {
/**
* 获取书单列表
* @return
* @throws RemoteException
*/
@Override
public List<Book> getBooKList() throws RemoteException {
return mBookList;
}
/**
* 添加书单
* @param book
* @throws RemoteException
*/
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
@Override
public void onCreate() {
super.onCreate();
//添加两个测试书籍
mBookList.add(new Book(12, "android开发艺术探索"));
mBookList.add(new Book(13, "第一行代码"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
实现新书提醒功能
创建一个监听器
// IOnNewBookArrvedListener.aidl
package com.example.jinghuang.demo2020.ipc_demo3;
import com.example.jinghuang.demo2020.ipc_demo3.Book;
// Declare any non-default types here with import statements
interface IOnNewBookArrvedListener {
void onNewBookArived(in Book newBook);
}
添加注册和注销监听的方法
package com.example.jinghuang.demo2020.ipc_demo3;
import com.example.jinghuang.demo2020.ipc_demo3.Book;
import com.example.jinghuang.demo2020.ipc_demo3.IOnNewBookArrvedListener;
// Declare any non-default types here with import statements
interface IBookManager {
List<Book>getBooKList();
void addBook(in Book book);
void registerListener(IOnNewBookArrvedListener listener);
void unRegisterListener(IOnNewBookArrvedListener listener);
}
Service类
package com.example.jinghuang.demo2020.ipc_demo3;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created by jing.huang on 2020/5/5.
*/
public class BookManagerService extends Service {
/**
* 使用CopyOnWriteArrayList进行自动的线程同步
* AIDL支持的List数据只支持ArrayList类型,这里为什么可以使用CopyOnWriteArrayList呢?
* 因为AIDL中所支持的是抽象的List,而List只是一个接口,因为虽然服务端返回的是CopyOnWriteArrayList
* 但是是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端。
* 所以在服务端采用CopyOnWriteArrayList时完全可以的。
*/
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
/**
* 定义一个变量记录服务是否停止
*/
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
/**
* 定义一个集合存放监听器,因为可能不止一个客户端
*/
private CopyOnWriteArrayList<IOnNewBookArrvedListener> mListenerList = new CopyOnWriteArrayList<>();
//创建AIDL的Binder对象
private Binder mBinder = new IBookManager.Stub() {
/**
* 获取书单列表
* @return
* @throws RemoteException
*/
@Override
public List<Book> getBooKList() throws RemoteException {
return mBookList;
}
/**
* 添加书单
* @param book
* @throws RemoteException
*/
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrvedListener listener) throws RemoteException {
if (mListenerList.contains(listener)) {
Log.e("IPC 通信", "already exists,can not register ");
} else {
mListenerList.add(listener);
}
Log.e("IPC 通信", "添加了监听器 监听器个数: " + mListenerList.size());
}
@Override
public void unRegisterListener(IOnNewBookArrvedListener listener) throws RemoteException {
if (mListenerList.contains(listener)) {
mListenerList.remove(listener);
} else {
Log.e("IPC 通信", "not found,can not unRegister ");
}
Log.e("IPC 通信", "移除了监听器 监听器个数: " + mListenerList.size());
}
};
@Override
public void onCreate() {
super.onCreate();
//添加两个测试书籍
mBookList.add(new Book(12, "android开发艺术探索"));
mBookList.add(new Book(13, "第一行代码"));
//开启线程添加新书
new Thread(new ServiceWork()).start();
}
@Override
public void onDestroy() {
//修改标志
mIsServiceDestroyed.set(true);
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* 开启一个线程,每个3秒就添加一本新书
*/
private class ServiceWork implements Runnable {
@Override
public void run() {
//如果服务器没有关闭,每个一段时间添加一本新书,知道服务器关闭
while (!mIsServiceDestroyed.get()) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int count = mBookList.size() + 1;
Book book = new Book(count, "android_" + count);
//添加新书
onNewBookArrived(book);
}
}
}
/**
* 添加新书,并且通知客户端
*
* @param book
*/
private void onNewBookArrived(Book book) {
mBookList.add(book);
for (IOnNewBookArrvedListener listener : mListenerList) {
Log.e("IPC 通信", "notify listener: " + listener);
try {
listener.onNewBookArived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
Activity
public class ClientAndServiceDemoActivity extends Activity implements View.OnClickListener {
private ServiceConnection serviceConnection;
public IBookManager iBookManager;
private static final int CLIENT_MESSAGE_WHAT = 111;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case CLIENT_MESSAGE_WHAT:
Log.e("IPC 通信", "客户端 新书:" + msg.obj);
break;
default:
super.handleMessage(msg);
break;
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.client_and_service_demo);
findViewById(R.id.binder_service_tv).setOnClickListener(this);
findViewById(R.id.add_book).setOnClickListener(this);
findViewById(R.id.get_book_list).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.binder_service_tv:
bindMyService();
break;
case R.id.add_book:
addBook();
break;
case R.id.get_book_list:
getBookList();
break;
default:
break;
}
}
/**
* 绑定服务
*/
private void bindMyService() {
Intent serviceIntent = new Intent(this, BookManagerService.class);
serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBookManager = IBookManager.Stub.asInterface(service);
//获取书单列表
try {
//注册监听
iBookManager.registerListener(iOnNewBookArrvedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
iBookManager = null;
}
};
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
/**
* 添加书单
*/
private void addBook() {
if (iBookManager == null) {
return;
}
try {
iBookManager.addBook(new Book(14, "Android进阶"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 获取书单列表
*/
private void getBookList() {
if (iBookManager == null) {
return;
}
try {
List<Book> booKList = iBookManager.getBooKList();
printBookList(booKList);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void printBookList(List<Book> booKList) {
if (EmptyUtils.isEmpty(booKList)) {
return;
}
for (Book book : booKList) {
Log.e("IPC 通信", "书单:" + book);
}
}
@Override
protected void onDestroy() {
//注销监听
if (iBookManager != null && iBookManager.asBinder().isBinderAlive()) {
try {
iBookManager.unRegisterListener(iOnNewBookArrvedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
iBookManager = null;
if (serviceConnection != null) {
unbindService(serviceConnection);
serviceConnection = null;
}
super.onDestroy();
}
private IOnNewBookArrvedListener iOnNewBookArrvedListener = new IOnNewBookArrvedListener.Stub() {
@Override
public void onNewBookArived(Book newBook) throws RemoteException {
//发送消息到Handler
mHandler.obtainMessage(CLIENT_MESSAGE_WHAT, newBook).sendToTarget();
}
};
}
上面的功能就实现了新书提醒功能。但是当我们按返回键时,会发现监听器注销失败
无法注销监听的原因:
这种注销的处理方式在日常开发时常使用到,但是放到多线程中就无法凑效,因为Binder会把客户端传递过去的对象重新转换并生成一个新的对象。虽然我们在注册和注销过程中使用的是同一个客户端对象,但是通过Binder传递到服务端后,会产生两个全新的对象。对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么AIDL中的自定义对象都必须要实现Parcelable接口的原因。
怎样实现注销监听
我们用RemoteCallbackList代替CopyOnWriteArrayList。RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口。
我们修改Service的代码
public class BookManagerService extends Service {
/**
* 使用CopyOnWriteArrayList进行自动的线程同步
* AIDL支持的List数据只支持ArrayList类型,这里为什么可以使用CopyOnWriteArrayList呢?
* 因为AIDL中所支持的是抽象的List,而List只是一个接口,因为虽然服务端返回的是CopyOnWriteArrayList
* 但是是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端。
* 所以在服务端采用CopyOnWriteArrayList时完全可以的。
*/
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
/**
* 定义一个变量记录服务是否停止
*/
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
/**
* 定义一个集合存放监听器,因为可能不止一个客户端
*/
private RemoteCallbackList<IOnNewBookArrvedListener> mListenerList = new RemoteCallbackList<>();
//创建AIDL的Binder对象
private Binder mBinder = new IBookManager.Stub() {
/**
* 获取书单列表
* @return
* @throws RemoteException
*/
@Override
public List<Book> getBooKList() throws RemoteException {
return mBookList;
}
/**
* 添加书单
* @param book
* @throws RemoteException
*/
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrvedListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unRegisterListener(IOnNewBookArrvedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
};
@Override
public void onCreate() {
super.onCreate();
//添加两个测试书籍
mBookList.add(new Book(12, "android开发艺术探索"));
mBookList.add(new Book(13, "第一行代码"));
//开启线程添加新书
new Thread(new ServiceWork()).start();
}
@Override
public void onDestroy() {
//修改标志
mIsServiceDestroyed.set(true);
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* 开启一个线程,每个3秒就添加一本新书
*/
private class ServiceWork implements Runnable {
@Override
public void run() {
//如果服务器没有关闭,每个一段时间添加一本新书,知道服务器关闭
while (!mIsServiceDestroyed.get()) {
try {
Thread.sleep(3000);
int count = mBookList.size() + 1;
Book book = new Book(count, "android_" + count);
//添加新书
onNewBookArrived(book);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 添加新书,并且通知客户端
*
* @param book
*/
private void onNewBookArrived(Book book) {
mBookList.add(book);
int count = mListenerList.beginBroadcast();
for (int i = 0; i < count; i++) {
IOnNewBookArrvedListener listener = mListenerList.getBroadcastItem(i);
Log.e("IPC 通信", "notify listener: " + listener);
}
}
}
执行之后发现,程序会报如下异常:
java.lang.IllegalStateException: beginBroadcast() called while already in a broadcast
为什么会报异常呢?
是因为RemoteCallbackLsitener的beginBroadcast方法必须和finishBroadcast()方法配对使用。
修改后的代码:
/**
* 添加新书,并且通知客户端
*
* @param book
*/
private void onNewBookArrived(Book book) {
mBookList.add(book);
int count = mListenerList.beginBroadcast();
for (int i = 0; i < count; i++) {
IOnNewBookArrvedListener listener = mListenerList.getBroadcastItem(i);
Log.e("IPC 通信", "notify listener: " + listener);
}
mListenerList.finishBroadcast();
}
另外,需要注意的是,我们知道,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法运行在服务端方法执行比较耗时,就会导致客户端线程长时间的足晒,如果客户端是UI线程的话,就会导致客户端ANR,这当然不是我们想要看到的。因此,客户端调用服务端方法时,要开启子线程。另外,由于客户端的onServiceConnected()方法和onServiceDisconnected()方法都是运行在UI线程,所以如果需要在他们的方法内调用服务端的方法,也需要开启子线程。
再次,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要再服务端方法中开线程去进行异步任务。