一、作用
二、概念
1. 多进程带来的问题
- 静态成员和单例模式完全失效
- 线程同步机制失效
- SharedPreferences 可靠性下降
- Application 多次创建
多进程间不能通过内存来共享数据。
2. Binder
Binder 是 Android 中的一个类,它实现了IBinder
接口。
3. AIDL 接口
一个 AIDL 性质的接口,只需要继承IInterface
接口即可。在 Binder 中传输的接口都需要继承IInterface
接口。
4. 忠告
当客户端发起远程请求时,当前线程会被挂起,知道服务端进程返回数据,所以如果一个远程方法很耗时,那么不能在 UI 线程中发起远程请求。
服务端的 Binder 方法都运行在服务端的 Binder 线程池中,所以 Binder 方法不管是否耗时都应采用同步的方式去实现。
服务端的 Binder 方法本身就运行在 服务端的 Binder 线程池中,所以服务端本身就可进行耗时操作,这时不要在服务端方法中开线程去进行异步任务,除非你明确知道自己在干什么,否则不建议这么做。
远程服务端调用客户端 listener 中的方法时,被调用的方法运行客户端的 Binder 线程池中。不可以在服务端调用客户端中的耗时方法。
三、使用
1. Android 中如何开启多进程
只有一个方法:在 AndroidManifest 文件中为四大组件(Activity、Service、Receiver、ContentProvider)配置android:process
属性。也就是说,我们无法给一个线程或一个实体类指定其运行时所在的进程。
不同的进程名配置方法。假设包名是com.example.myapplication
android:process="com.example.myapplication.remote"
该方法中指定的进程名为完整的命名方式。不以:
开头的进程属于全局进程,其他应用可以通过ShareUID
的方式和它跑在同一进程中。android:process=":remote"
:
是一种简写,是指要在当前进程名前面加上报名,它的完整进程名为com.example.myapplication:remote
。以:
开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一进程。
2. 创建 AIDL 文件
(1)创建 AIDL 文件,声明服务端要暴露给客户端的接口
main-new-Directory,新建『aidl』包
为了方便开发,一般把 AIDL 相关文件放在同一包中,这样当客户端是另一个应用时可方便地把整个包复制到客户端工程中。
最终的 AIDL 文件包如下:
(2)各个文件
// IBookManager.aidl
package com.example.myapplication;
// Declare any non-default types here with import statements
import com.example.myapplication.Book;
import com.example.myapplication.OnNewBookArrivedListener;
interface IBookManager {
// 图书列表查询
List < Book > getBookList();
// 添加图书
void addBook( in Book book);
// 注册
void registerListener(OnNewBookArrivedListener listener);
// 取消注册
void unregisterListener(OnNewBookArrivedListener listener);
}
// Book.aidl
package com.example.myapplication;
// Declare any non-default types here with import statements
parcelable Book;
// OnNewBookArrivedListener.aidl
package com.example.myapplication;
// Declare any non-default types here with import statements
import com.example.myapplication.Book;
interface OnNewBookArrivedListener {
// 新书到达
void onNewBookArrived( in Book book);
}
(3)IBookManager.aidl
对应的生成类
- IBookManager
本身是一个 Binder 接口
public interface IBookManger extends IInterface{}
- Stub
是一个 Binder
public class Stub extends Binder implements IBookManager {
// 将服务端的 Binder 对象转化成客户端所需 AIDL 接口类型的对象。
// 这种转换是区分进程的。如果服务端和客户端在同一进程:返回服务端的 Stub 对象本身;不在同一进程:返回系统封装后的Stub.Proxy对象
public static IBookManager asInterface(IBinder obj) {
}
// 返回当前 Binder 对象
// IBookManager extends IInterface,IIterface接口中的抽象方法 IInterface::asBinder()
@Override
public IBinder asBinder() {
}
// 此方法运行在服务端的 Binder 线程池中,当客户端发起跨进程请求时,远程请求通过系统底层封装后交由此方法处理。
// code:服务端通过code判断执行目标方法
// data:携带目标方法所需参数
// reply:将返回值写入 reply 中。
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
}
// 此方法运行在客户端
@Override
public List < Book > getBookList() {
}
// 此方法运行在客户端
@Override
public void addBook(Book book) {}
}
- Proxy 是 Stub 的代理类
private static class Proxy implements IBookManager{}
(4)注意事项
使用Android studio创建的AIDL编译时找不到自定义类的解决办法
3. 服务端
先创建一个 AIDL(IBookManager.aidl
)接口,声明服务端要暴露给客户端的接口,然后创建一个 Service 监听客户端的连接请求。
public class LibraryManagerService extends Service {
private static final String TAG = "LibraryManagerService";
/**
* 图书列表
*/
private CopyOnWriteArrayList < Book > mBookList = new CopyOnWriteArrayList < > ();
/**
* 观察者列表
*/
private RemoteCallbackList < com.example.myapplication.OnNewBookArrivedListener > mListenerList =
new RemoteCallbackList < > ();
/**
* service是否被销毁
*/
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
// Stub就是一个Binder类
private Binder mBinder = new com.example.myapplication.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(com.example.myapplication.OnNewBookArrivedListener listener)
throws RemoteException {
// if (!mListenerList.contains(listener)) {
// mListenerList.add(listener);
// } else {
// Log.d(TAG, "registerListener: aleady exits");
// }
mListenerList.register(listener);
}
@Override
public void unregisterListener(com.example.myapplication.OnNewBookArrivedListener listener)
throws RemoteException {
// if (mListenerList.contains(listener)) {
// mListenerList.remove(listener);
// Log.d(TAG, "unregisterListener: success");
// } else {
// Log.d(TAG, "unregisterListener: can not register");
// }
// Log.d(TAG, "unregisterListener: current size=" + mListenerList.size());
Log.d(TAG, "unregisterListener: begin unreginster listener " + listener);
int old = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
Log.d(TAG, "unregisterListener: current listenerList size=" + old);
mListenerList.unregister(listener);
Log.d(TAG, "unregisterListener: after unreginster listener " + listener);
int fresh = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
Log.d(TAG, "unregisterListener: current listenerList size=" + fresh);
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "java"));
new Thread(new ServiceWorker()).start();
}
@Override
public void onDestroy() {
super.onDestroy();
mIsServiceDestroyed.set(true);
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
while (!mIsServiceDestroyed.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBookList.size() + 1;
Book newBook = new Book(bookId, "new Book#" + bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
/**
* 新书到达时
*
* @param newBook
*/
private void onNewBookArrived(Book newBook) throws RemoteException {
mBookList.add(newBook);
Log.d(TAG, "onNewBookArrived: notify Listeners:");
// for (int i = 0; i < mListenerList.size(); i++) {
// com.example.myapplication.OnNewBookArrivedListener onNewBookArrivedListener = mListenerList.get(i);
// if (onNewBookArrivedListener != null) {
// onNewBookArrivedListener.onNewBookArrived(newBook);
// }
// }
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
com.example.myapplication.OnNewBookArrivedListener broadcastItem = mListenerList.getBroadcastItem(i);
if (broadcastItem != null) {
broadcastItem.onNewBookArrived(newBook);
}
}
mListenerList.finishBroadcast();
}
}
4. 客户端
- 客户端启动后与服务端绑定
onCreate()
中bindService
- 绑定成功后
将服务端返回的 Binder 对象转化成 AIDL 接口,然后通过接口去调用服务端的远程方法。 - 客户端销毁时,与服务端解绑
public class AIDLActivity extends AppCompatActivity {
private static final String TAG = "AIDLActivity";
private static final int MSG_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoteManager;
private Myhandler mHandler = new Myhandler();
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager iBookManager = IBookManager.Stub.asInterface(service);
mRemoteManager = iBookManager;
// 为Binder设置死亡监听
try {
service.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
try {
// 图书列表
List < Book > bookList = iBookManager.getBookList();
Log.d(TAG, "onServiceConnected: 添加前: bookList=" + bookList);
// 添加图书
iBookManager.addBook(new Book(3, "语文"));
// 图书列表
bookList = iBookManager.getBookList();
Log.d(TAG, "onServiceConnected: 添加后 bookList=" + bookList);
// 注册
iBookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 此处重连远程服务
Log.d(TAG, "onServiceDisconnected: ");
}
};
private OnNewBookArrivedListener mOnNewBookArrivedListener = new OnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book book) throws RemoteException {
mHandler.obtainMessage(MSG_NEW_BOOK_ARRIVED, book).sendToTarget();
}
};
// 死亡监听
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mRemoteManager == null) {
return;
}
mRemoteManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteManager = null;
// 重新绑定 Service
}
};
private static class Myhandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_NEW_BOOK_ARRIVED:
Log.d(TAG, "handleMessage: receive new book " + msg.obj);
break;
default:
break;
}
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl_layout);
// 客户端一启动就绑定服务端,方便调用服务端提供的服务
bindNewService();
}
private void bindNewService() {
Intent intent = new Intent(AIDLActivity.this, LibraryManagerService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 取消注册
if (mRemoteManager != null && mRemoteManager.asBinder().isBinderAlive()) {
try {
mRemoteManager.unregisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
// 取消绑定
unbindService(mServiceConnection);
}
}
三、重点
1. 新书订阅功能
假设有新的需求:用户不想自己时不时地去查询图书列表,而是由图书馆在有新书时把书的信息通知给客户端。这是一种典型的观察者模式。
提供 AIDL 接口,让每个观察者都实现
之所以选择 AIDL 接口而不是普通接口,是因为 AIDL 中无法使用普通接口。客户端实现接口提供新书到达时的逻辑
private OnNewBookArrivedListener mOnNewBookArrivedListener = new OnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book book) throws RemoteException {
mHandler.obtainMessage(MSG_NEW_BOOK_ARRIVED, book).sendToTarget();
}
};
客户端的
onNewBookArrived()
被回调时,用 Handler 切换到 UI 线程执行
当服务端有新书到达回调客户端的onNewBookArrived()
时,由于是跨进程通信,onNewBookArrived()
在客户端的 Binder 线程池中执行,所以如果客户端需操作 UI 的话,需要使用 Handler。服务端用
RemoteCallbackList
保存观察者集合
用RemoteCallbackList
保存,新书到达时通知给观察者。该集合的用法如下。mListenerList
// 添加元素
mListenerList.register(listener);
// 删除元素
mListenerList.unregister(listener);
// 遍历元素
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
com.example.myapplication.OnNewBookArrivedListener broadcastItem = mListenerList.getBroadcastItem(i);
if (broadcastItem != null) {
broadcastItem.onNewBookArrived(newBook);
}
}
mListenerList.finishBroadcast();
2. 客户端取消在服务端的注册
如果服务端用普通的 List 保存观察者的话,观察者取消注册失败。需要用RemoteCallbackList
保存观察者,这是系统专门提供的用于删除跨进程 listener 的接口。
3. 为保证连接的健壮性,客户端在与服务端断开时重连
客户端关心与服务端的连接是否在稳固的建立着,如果断开的话要有相应的重连机制。
(1)为服务端设置死亡监听
客户端绑定服务端成功后为服务端设置死亡监听,收到消息后就进行重连
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager iBookManager = IBookManager.Stub.asInterface(service);
mRemoteManager = iBookManager;
// 为Binder设置死亡监听
try {
service.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
}
// .....
}
}
(2)在onServiceDisconnect()中重连
4. 安全性:权限验证
参考文献
SheHuan-Android多进程通信
Android开发艺术探索