使用AIDL
AIDL可以处理并发请求并且可以实现跨进程调用服务端的方法。
实现步骤
服务端
- 创建一个Service用来接受客户端的连接。
- 创建一个AIDL文件,在文件中声明暴露给客户端的接口。
- 在Service中实现这个AIDL。
客户端
- 绑定服务端的Service。
- 将服务端返回的Binder转换成对应的AIDL类型。
- 调用AIDL中声明的方法。
AIDL注意事项
1. AIDL支持的数据类型
- 基本类型。
- String、CharSequence。
- List,只支持ArrayList,其中元素必须是AIDL支持的。
- Map,只支持HashMap,key和value都必须是AIDL支持的。
- 所有实现了Parcelable的类。
- AIDL接口本身。
2. AIDL接口和实现了Parcelable的类一定要显式import进来。
3. 如果AIDL中用到了Parcelable类,那么必须创建一个和类名相同名字的aidl文件并声明这个类为parcelable类型。
package com.utte.aidltest;
parcelable Book;
4. AIDL中除了基本类型,其他类型都必须声明in、out、inout表示参数是输入型、输出型还是输入输出型的。
void addBook(in Book book);
应该根据实际来指定参数类型,不能一概使用out或者inout,因为这在底层是由开销的。
5. AIDL接口中只支持声明方法,不支持声明静态常量。
6. 建议把所有的AIDL相关的类和文件都放入同一个包中
当客户端是另外一个程序时,我们可以把整个包复制到客户端工程中,比较方便AIDL的开发。AIDL的包结构应该保持服务端和客户端一致,否则会出错。因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果路径不一样的话,就无法成功反序列化。
例子中都是在同一个项目中的不同进程,所以不需要复制AIDL文件。
具体步骤
1. 创建AIDL接口
- 创建一个后缀名为aidl的接口文件。
- 在文件中声明一个接口和所需要的接口方法。
// IBookManager.aidl
package com.utte.aidltest;
import com.utte.aidltest.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
- 创建声明类的aidl文件
//Book.aidl
package com.utte.aidltest;
parcelable Book;
- buid一下,自动生成AIDL接口的Binder类。
2. 实现远程服务端Service
- 创建一个Service,并在Menifest中注册。
<service android:name=".aidl.BookManagerService"
android:process=":rremote"/>
- onCreate中初始化数据。
- 创建一个AIDL的Binder对象并实现其接口方法。
- onBind()中返回这个Binder对象。
public class BookManagerService extends Service {
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);
}
};
CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(0, "book"));
mBookList.add(new Book(1, "bbook"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
因为AIDL方法是在服务端线程池中进行的,存在线程同步的问题,可以直接使用CopyOnWriteArrayList来进行自动的线程同步,它支持并发读写。
注意点中说AIDL只支持ArrayList,虽然这里服务端中声明集合为CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据,最终形成一个ArrayList返回给客户端,并不影响结论。
3. 实现客户端
- 绑定远程Service。
- 将Service返回的IBinder转换成AIDL接口类型。
- 直接使用转换后的Binder调用远程方法。
public class BookManagerActivity extends AppCompatActivity {
private static final String TAG = "BookManagerActivity";
IBookManager mBinder;
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = IBookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
Button button = findViewById(R.id.bt);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
List<Book> bookList = mBinder.getBookList();
Log.d(TAG, "onClick: " + bookList.getClass().getCanonicalName());
for (Book book : bookList) {
Log.d(TAG, "onClick: " + book.bookId + " " + book.bookName);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
其实直接在UI线程去调用远程方法的写法是不好的,因为调用远程方法是耗时的,可能会导致ANR。
上面我们还打印了服务端返回的BookList的类型,发现是ArrayList。证实了上面的结论。
实现观察者模式
实现这样一个需求,每当有新书添加到服务端,服务端就通知客户端,并且客户端可以订阅也可以取消订阅。
1. 修改AIDL文件
- 新建监听aidl接口
// IOnNewBookArrivedListener.aidl
package com.utte.aidltest;
import com.utte.aidltest.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
- 在IBookManager中增加注册和解绑监听的两个接口方法
// IBookManager.aidl
package com.utte.aidltest;
import com.utte.aidltest.Book;
import com.utte.aidltest.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
2. 修改服务端
- 增加一个监听集合
RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
RemoteCallbackList是一个接口,支持管理任意AIDL接口。
public class RemoteCallbackList<E extends IInterface>
它的内部有一个map,值是Callback,键是IBinder。Callback就是监听接口,
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
虽然说跨进程传输客户端的通过一个对象会在服务端生成不同的对象,但是这些不同的对象有一个共同的特点就是它们底层的Binder对象是同一个。RemoteCallback就利用了这一点。另外RemoteCallback还自动实现了线程同步。
- 遍历监听去通知
private void onNewBookArrived(Book book) {
mBookList.add(book);
int size = mListenerList.beginBroadcast();
for (int i = 0; i < size; i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
if (listener != null) {
try {
listener.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast();
}
注意RemoteCallback的beginBroadcast()和finishBroadcast()必须配对使用。
- 修改接口方法的实现
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);
onNewBookArrived(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
};
RemoteCallback的register()和unregister()方法帮我们封装了判断进程是否终止、对象是否存在、线程同步的工作,我们调用起来特别简单。
3. 修改客户端
- 实现监听接口
IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(final Book newBook) throws RemoteException {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "onNewBookArrived: " + newBook.bookId + " " + newBook.bookName);
}
});
}
};
服务端调用客户端的onNewBookArrived(),这个方法会在客户端的Binder线程池中执行,如果需要进行更新UI的操作,那么必须要切换线程。
- 调用订阅和取消订阅
case R.id.bt_registe_listener:
mBinder.registerListener(mListener);
break;
case R.id.bt_unregiste_listener:
mBinder.unregisterListener(mListener);
break;
调用远程方法耗时
- 客户端调用远程方法时,客户端线程会被挂起,如果此服务端的方法是耗时的,且此时客户端的调用线程是UI线程,就会导致客户端ANR。
- onServiceConnection()和onServiceDisconnection()都是运行在主线程的,所以不要在它们中直接调用远程方法。
- 服务端的方法本身就运行在服务端的Binder线程池中,就算方法耗时,也不需要再开线程。
- 服务端调用客户端的listener方法时也是一样的,如果客户端的方法耗时,就不要在主线程中调用,否则会造成服务端无法响应。
总的来说就是调用方法的那一端会将当前线程挂起,所以如果方法耗时,就不要在主线程中调用。运行调用方法的那一端不需要新开线程去运行,因为方法运行在那一端的Binder线程池中。
处理Binder死亡
常见有以下两种方法:
1. 给Binder设置DeathRecipient
先创建一个DeathRecipient对象。当Binder死亡时,系统就会调用binderDied(),我们可以移除之前绑定的死亡代理对象,并绑定新的服务。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mBinder == null) {
return;
}
mBinder.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBinder = null;
Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
};
2. 在onServiceDisconnected()中重连服务
@Override
public void onServiceDisconnected(ComponentName name) {
Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
两者区别在于binderDied()运行在客户端的Binder线程池,更新UI需要切换线程池。而onServiceDisconnected()是在UI线程运行的,可以直接更新UI。
加入权限
AIDL中加入权限验证有两种方法,但是不只这两种。
1. 在onBind中进行验证
可以使用permission验证。
- 在Manifest中声明所需的权限。
<permission android:name="com.utte.aidltest.aidl.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
- 在服务端的onBind中验证
@Nullable
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.utte.aidltest.aidl.ACCESS_BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}
- 需要权限的客户端在Manifest中添加权限
<uses-permission android:name="com.utte.aidltest.aidl.ACCESS_BOOK_SERVICE"/>
2. 在服务端的onTransact()中验证
记得在分析自动生成的Binder代码时,有说过onTransact()只有返回true才真正的调用成功,所以我们可以在这里判断,如果没有权限就返回false。系统为我们生成的Binder文件是不能改的,我们可以在服务端重写这个方法。
除了permission还可以使用其它很多方法,比如包名验证。
Binder mBinder = new IBookManager.Stub() {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
if (!packageName.startsWith("com.utte")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
//其他方法......
};
getCallingUid()可以拿到当前客户端所属的应用的uid,通过uid获取到包名从而进行验证。
之前一直纠结onTransact()是在服务端的Binder线程池中运行的,怎么能拿到客户端的包名。一看才发现getCallingUid()是Binder的方法。
/**
* Return the Linux uid assigned to the process that sent you the
* current transaction that is being processed. This uid can be used with
* higher-level system services to determine its identity and check
* permissions. If the current thread is not currently executing an
* incoming transaction, then its own uid is returned.
*/
public static final native int getCallingUid();
对着翻译和原文看了半天,大概就是会返回发送给你正在处理事务的进程的uid,如果当前线程没有正在处理进来的事物,那就返回它自己的uid。
Binder连接池
如果需要多个AIDL接口,不可能也像之前一样一个AIDL对应创建一个Service并在onBind()中返回,有几个AIDL就创建几个Service不现实。我们应该把所有的AIDL放在同一个Service中管理。
- 每个业务模块创建自己的AIDL接口并实现此接口。
- 向服务端提供自己的唯一标识和对应的Binder对象。
- 服务端只需要一个Service提供一个queryBinder接口返回相应的Binder对象给客户端。
- 客户端拿到所需的Bindr对象后就可以远程调用方法了。
具体实现
1. 创建两个AIDL接口,并分别单独实现
ICombine AIDL接口。
package com.utte.aidltest.pool;
interface ICombine {
String combine(String strA, String strB);
}
实现CombineImpl的抽象方法。
package com.utte.aidltest.pool;
import android.os.RemoteException;
public class CombineImpl extends ICombine.Stub {
@Override
public String combine(String strA, String strB) throws RemoteException {
return new String(strA + strB);
}
}
ICompute.aidl
package com.utte.aidltest.pool;
interface ICompute {
int add(int a, int b);
}
package com.utte.aidltest.pool;
import android.os.RemoteException;
public class ComputeImpl extends ICompute.Stub {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
}
2. 提供一个Binder连接池的AIDL接口
package com.utte.aidltest.pool;
interface IBinderPool {
IBinder queryBinder(int binderCode);
}
Binder连接池的AIDL接口,接受具体Binder的code,返回具体的Binder。
3. 编写BinderPool类
BinderPool主要需要实现如下功能:
- 绑定远程Service。
- 实现IBinderPool这个AIDL接口。
- 向外暴露queryBinder()方法。
public class BinderPool {
private static final String TAG = "BinderPool";
public static final int BINDER_COMBINE = 1;
public static final int BINDER_COMPUTE = 2;
private Context mContext;
private IBinderPool mBinderPool;
private static volatile BinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;
private BinderPool(Context context) {
mContext = context.getApplicationContext();//获取context,用于连接Service的
connectBinderPoolService();//连接Service
}
//提供单例的BinderPool
public static BinderPool getInstance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}
//连接远程Service
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent service = new Intent(mContext, BinderPoolService.class);
mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
try {
//将上面bindService()变成同步方法。
//等待,跳至onServiceConnected(),实现同步获取连接池的Binder对象
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//从Service拿到Binder连接池的Binder对象
mBinderPool = IBinderPool.Stub.asInterface(service);
try {
//处理断链
mBinderPool.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
//同步结束,跳回connectBinderPoolService()
mConnectBinderPoolCountDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.d(TAG, "binderDied: ");
mBinderPool.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};
//给客户端的queryBinder方法
public IBinder queryBinder(int binderCode) {
IBinder binder = null;
try {
if (mBinderPool != null) {
//调用连接池Binder对象的queryBinder()
binder = mBinderPool.queryBinder(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
//IBinderPool AIDL接口实现
public static class BinderPoolImpl extends IBinderPool.Stub {
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
//根据不同的code返回不同的具体业务Binder实现类
switch (binderCode) {
case BINDER_COMBINE:
binder = new CombineImpl();
break;
case BINDER_COMPUTE:
binder = new ComputeImpl();
break;
}
return binder;
}
}
}
中间有使用CountDownLatch来让bindService()变成同步方法,目的其实就是让这个BinderPool类构造器调用时就获取好连接池的Binder对象。
4. 为IBinderPool编写Service
public class BinderPoolService extends Service {
private Binder mBinder = new BinderPool.BinderPoolImpl();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
能看到这里的Service非常的简洁,只需要返回Binder连接池的Binder对象就可以了。因为Binder的实现分离出去了,并且这个Service不需要和具体的业务Binder直接接触。
5. 客户端
客户端使用BinderPool对象来获取具体的Binder并调用方法。
public class BinderPoolActivity extends AppCompatActivity {
private static final String TAG = "BinderPoolActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_binder_pool);
//另开线程运行,因为是耗时的
new Thread(new Work()).start();
}
private class Work implements Runnable {
@Override
public void run() {
//获取连接池对象
BinderPool binderPool = BinderPool.getInstance(BinderPoolActivity.this);
ICombine combine = CombineImpl.asInterface(binderPool.queryBinder(BinderPool.BINDER_COMBINE));
ICompute compute = ComputeImpl.asInterface(binderPool.queryBinder(BinderPool.BINDER_COMPUTE));
try {
String combineStr = combine.combine("jtt", "zsz");
int computeInt = compute.add(1, 2);
Log.d(TAG, "doWork: " + combineStr + " " + computeInt);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
如果需要增加新的具体业务Binder,只需要新增AIDL,新增实现类,之后在在BinderPoolImpl中多加一个case返回对应Binder实现类就可以了。
BinderPool极大提高了AIDL的开发效率,不需要创建新的Service,所以建议在开发工作中多使用BinderPool。
网友评论