Android IPC 机制
本文为《Android 开发艺术探索》 第二章IPC机制 笔记及实践
文章中涉及的代码:https://github.com/pqpo/ServiceAndIpcDemo 建议先clone一份到本地,运行起来之后再继续阅读。
其中项目中包括Service生命周期的部分不在本文 讨论范围之内,并且只演示了使用Binder进行进程间通讯,其他方式比较简单。
IPC:Inter-Process Communication,意为进程间通讯。思考一下什么是线程,什么是进程,线程间通讯是怎么完成的,需要注意什么?
Android 中的IPC方式:
- 使用Bundle:通过Intent传递数据,包括基本数据类型,序列化类型(Parcellable、Serializable),安卓支持的特殊对象。具体查看Bundle类对外提供的支持类型。
- 使用文件共享:两个进程通过读写一个文件来交换数据。并发读可能读到的不是最新的,并发写很可能数据会乱套。SharedPreferences(带有缓存的文件读写)在进程间通信中不介意使用。
- 使用Socket:通过TCP或者UDP协议进行进程间通信,比较消耗资源。
- 使用Binder:Android 底层提供的IPC方式,包括使用简单的Messenger,ContentProvider都可以进行进程间通信,底层也是由Binder实现。
本文主要介绍使用Binder进行进程间通信,下面先演示一下如何使用aidl来实现IPC:
1.新建三个文件 Program.java, Program.aidl(用到的Parcelable实体必须声明一个aidl文件), IProgramManager.aidl, IOnProgramListChangedListener.aidl:
//Program.java
public class Program implements Parcelable {
public int programId;
public String programName;
public Program(int programId, String programName) {
this.programId = programId;
this.programName = programName;
}
//Parcelable 相关代码省略
}
//Program.aidl
package pw.qlm.ipctest;
parcelable Program;
//IProgramManager.aidl
package pw.qlm.ipctest;
import pw.qlm.ipctest.Program;
import pw.qlm.ipctest.IOnProgramListChangedListener;
interface IProgramManager {
List getProgramList();
void addProgram(String program);
void removeProgram(String program);
void registerOnProgramListChangedListener(IOnProgramListChangedListener listener);
void unregisterOnProgramListChangedListener(IOnProgramListChangedListener listener);
}
package pw.qlm.ipctest;
import pw.qlm.ipctest.Program;
interface IOnProgramListChangedListener {
void onChanged(String method, in Program list);
}
2.build 一下,使IDE根据aidl文件自动生成代码
3.创建实现类,ProgramManagerImpl.java,继承于IProgramManager.Stub,该类为IDE根据aidl文件生成的类,并实现方法:
//ProgramManagerImpl.java
public class ProgramManagerImpl extends IProgramManager.Stub {
private final HashSet mProgramList = new HashSet<>();
private AtomicInteger ids = new AtomicInteger(0);
//夸进程监听器不能简单的使用List,因为不同的进程,list内保存的对象不是同一个
private RemoteCallbackList callbacks = new RemoteCallbackList<>();
@Override
public List getProgramList() throws RemoteException {
synchronized (mProgramList) {
return Arrays.asList(mProgramList.toArray(new Program[]{}));
}
}
//远程调用是运行与Binder线程池,故可以进行耗时操作;
@Override
public void addProgram(String program) throws RemoteException {
//模拟耗时操作
SystemClock.sleep(1000);
synchronized (mProgramList) {
Program program1 = new Program(ids.incrementAndGet(), program);
if (mProgramList.add(program1)){
onNotifyProgramListChanged("add", program1);
}
}
}
@Override
public void removeProgram(String program) throws RemoteException {
//略
}
@Override
public void registerOnProgramListChangedListener(IOnProgramListChangedListener listener) throws RemoteException {
if(listener != null) {
callbacks.register(listener);
}
}
@Override
public void unregisterOnProgramListChangedListener(IOnProgramListChangedListener listener) throws RemoteException {
if(listener != null) {
callbacks.unregister(listener);
}
}
private void onNotifyProgramListChanged(String method, Program program1) throws RemoteException {
//beginBroadcast(),callbacks.finishBroadcast()必须成对出现
int N = callbacks.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnProgramListChangedListener broadcastItem = callbacks.getBroadcastItem(i);
broadcastItem.onChanged(method, program1);
}
callbacks.finishBroadcast();
}
}
4.创建Service,给其他进程提供服务,ProgramManagerService.java:
//ProgramManagerService.java
public class ProgramManagerService extends Service {
private static final String TAG = "IPC/ProgramService";
IProgramManager programManager = new ProgramManagerImpl();
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind");
//鉴权,防止任意客户端调用
if (checkCallingOrSelfPermission("qw.qlm.ipctest.PERMISSION_CALL_REMOTE_SERVICE") == PackageManager.PERMISSION_DENIED) {
return null;
}
return programManager.asBinder();
}
}
5.远程连接服务:
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.i(TAG, "binderDied : disconnected!" + " Thread:" + Thread.currentThread().getName());
if (mProgramManager != null) {
mProgramManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mProgramManager = null;
}
mHandler.sendEmptyMessage(MSG_CONNECT_TO_SERVICE);
}
};
//夸进程监听器也是一个Binder,简单的接口没有夸进程的能力
private IOnProgramListChangedListener mListener = new IOnProgramListChangedListener.Stub() {
@Override
public void onChanged(String method, Program program) throws RemoteException {
Message.obtain(mHandler, MSG_CHANGED, method + " " + program + " success").sendToTarget();
}
};
Intent intent = new Intent(this, ProgramManagerService.class);
mProgramServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (service == null) {
toast("permission denied!");
return;
}
mProgramManager = IProgramManager.Stub.asInterface(service);
Log.i(TAG, "connect success!");
toast("connect success!");
try {
mProgramManager.asBinder().linkToDeath(mDeathRecipient, 0);
mProgramManager.registerOnProgramListChangedListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected : disconnected!" + " Thread:" + Thread.currentThread().getName());
mProgramManager = null;
bindToService();
}
};
bindService(intent, mProgramServiceConnection, Context.BIND_AUTO_CREATE);
6.使用远程接口:
//如果远程调用为耗时操作避免在UI线程中调用
mProgramManager.addProgram(programName);
注意点,对应以上各个步骤:
- Parcelable实体需要在aidl文件中声明,IProgramManager.aidl中import语句不能少,不管在不在同一个包中; 如要注册监听,不能简单声明一个接口,而是要声明aidl文件 IOnProgramListChangedListener.aidl, 简单接口不具备远程通信的能力;
- 新建aidl文件之后必须build;
- 实现类运行与Binder线程池中,需要注意线程安全,并且允许进行耗时操作;夸进程监听器不能简单的使用List,因为不同的进程,list内保存的对象不是同一个,必须使用RemoteCallbackList;
- Service 中可以在onBind中进行鉴权;
- 可以设置死亡监听器 IBinder.DeathRecipient 用于恢复Service,该回调运行于Binder线程池;ServiceConnection 中的 onServiceDisconnected 运行于UI线程。夸进程接听器必须也是一个Binder对象:IOnProgramListChangedListener mListener = new IOnProgramListChangedListener.Stub();
- 远程调用可能是一个耗时操作,如果是耗时操作需要运行在后台线程;
为了更好的理解Binder机制,下面不使用aidl来实现IPC:
1. 新建远程调用接口,IConfigManager.java,继承IInterface:
public interface IConfigManager extends IInterface {
void setValue(String value) throws RemoteException;
String getValue() throws RemoteException;
}
2. 新建实现类,ConfigManagerImpl.java:
public class ConfigManagerImpl extends Binder implements IConfigManager {
private static final java.lang.String DESCRIPTOR = "pw.qlm.ipctest.ipc.ConfigManagerImpl";
private String value;
private Context mContext;
public ConfigManagerImpl(Context context) {
this.attachInterface(this, DESCRIPTOR);
mContext = context;
}
@Override
public synchronized void setValue(String value) throws RemoteException{
this.value = value;
}
@Override
public synchronized String getValue() throws RemoteException {
return value;
}
@Override
public IBinder asBinder() {
return this;
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
//鉴权,同时判断权限与package name,返回FALSE,则该次远程调用会失败
if (mContext.checkCallingOrSelfPermission("qw.qlm.ipctest.PERMISSION_CALL_REMOTE_SERVICE") == PackageManager.PERMISSION_DENIED) {
return false;
}
String packageName = "";
String[] packagesForUid = mContext.getPackageManager().getPackagesForUid(getCallingUid());
if (packagesForUid != null && packagesForUid.length > 0) {
packageName = packagesForUid[0];
}
if (packageName == null || !packageName.startsWith("pw.qlm")) {
return false;
}
switch (code) {
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case ConfigManagerProxy.TRANSACT_getValue:
data.enforceInterface(DESCRIPTOR);
String result = getValue();
reply.writeNoException();
reply.writeString(result);
return true;
case ConfigManagerProxy.TRANSACT_setValue:
data.enforceInterface(DESCRIPTOR);
String value = data.readString();
setValue(value);
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}
public static IConfigManager asInterface(IBinder binder) {
if (binder == null) {
return null;
}
IInterface iInterface = binder.queryLocalInterface(DESCRIPTOR);
if (iInterface != null && iInterface instanceof IConfigManager) {
return (IConfigManager) iInterface;
}
return new ConfigManagerProxy(binder);
}
private static class ConfigManagerProxy implements IConfigManager {
private IBinder remote;
public ConfigManagerProxy(IBinder remote) {
this.remote = remote;
}
@Override
public void setValue(String value) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
data.writeString(value);
remote.transact(TRANSACT_setValue, data, reply, 0);
reply.readException();
} finally {
data.recycle();
reply.recycle();
}
}
@Override
public String getValue() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
String result = null;
try {
data.writeInterfaceToken(DESCRIPTOR);
remote.transact(TRANSACT_getValue, data, reply, 0);
reply.readException();
result = reply.readString();
} finally {
data.recycle();
reply.recycle();
}
return result;
}
@Override
public IBinder asBinder() {
return remote;
}
static final int TRANSACT_setValue = IBinder.FIRST_CALL_TRANSACTION + 0;
static final int TRANSACT_getValue = IBinder.FIRST_CALL_TRANSACTION + 1;
}
}
接下来的连接和调用就和使用aidl步骤中的4,5,6一致了,这里再重点分析一下Binder的实现类ConfigManagerImpl。
可以看到,该类是继承于Binder,表明该类具备远程调用的能力,并且实现了IConfigManager,说明该类拥有特点的业务能力,结合起来看也就是说ConfigManagerImpl具备远程调用业务接口的能力。那么这种能力是怎么实现的呢,主要都是Binder的功劳。
实现业务接口不用多说了,要注意方法调用在Binder线程池中,特别关注多线程调用的安全。
@Override
public synchronized void setValue(String value) throws RemoteException{
this.value = value;
}
@Override
public synchronized String getValue() throws RemoteException {
return value;
}
声明一个唯一标识量,一般用当前类名:
String DESCRIPTOR = "pw.qlm.ipctest.ipc.ConfigManagerImpl";
构造方法中调用以下方法,后面binder.queryLocalInterface(DESCRIPTOR)中会用DESCRIPTOR 标准量查询本地接口:
this.attachInterface(this, DESCRIPTOR);
我们通过下面的方法将一个binder对象转换为可以进行业务调用的对象:
public static IConfigManager asInterface(IBinder binder) {
if (binder == null) {
return null;
}
IInterface iInterface = binder.queryLocalInterface(DESCRIPTOR);
if (iInterface != null && iInterface instanceof IConfigManager) {
return (IConfigManager) iInterface;
}
return new ConfigManagerProxy(binder);
}
如果是本地进程,binder就是我们新建的ConfigManagerImpl,那么调用binder.queryLocalInterface(DESCRIPTOR)就会返回本身,可以查看Binder中的代码:
public IInterface queryLocalInterface(String descriptor) {
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
其中mDescriptor, mOwner便是构造方法中设置进去的this.attachInterface(this, DESCRIPTOR);
如果是远程调用,那么该binder对象不是本身,实际上是BinderProxy对象,这个时候会返回一个代理对象ConfigManagerProxy,并将这个BinderProxy对象传给它的内部进行远程通信。例如:
@Override
public void setValue(String value) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
data.writeString(value);
remote.transact(TRANSACT_setValue, data, reply, 0);
reply.readException();
} finally {
data.recycle();
reply.recycle();
}
}
可以看到支持远程通信的对象是有限的,也就是Parcel.write支持的对象。
这时候会将数据传递到Binder所在进程中回调执行onTransact(int code, Parcel data, Parcel reply, int flags);
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case ConfigManagerProxy.TRANSACT_getValue:
data.enforceInterface(DESCRIPTOR);
String result = getValue();
reply.writeNoException();
reply.writeString(result);
return true;
case ConfigManagerProxy.TRANSACT_setValue:
data.enforceInterface(DESCRIPTOR);
String value = data.readString();
setValue(value);
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}
data代表输入参数,reply代表输出参数,开始时必须调用data.enforceInterface(DESCRIPTOR);结束时必须调用
reply.writeNoException();如果返回true则代表该次远程调用成功,否则失败,这里可以做鉴权:
//鉴权,同时判断权限与package name,返回FALSE,则该次远程调用会失败
if (mContext.checkCallingOrSelfPermission("qw.qlm.ipctest.PERMISSION_CALL_REMOTE_SERVICE") == PackageManager.PERMISSION_DENIED) {
return false;
}
String packageName = "";
String[] packagesForUid = mContext.getPackageManager().getPackagesForUid(getCallingUid());
if (packagesForUid != null && packagesForUid.length > 0) {
packageName = packagesForUid[0];
}
if (packageName == null || !packageName.startsWith("pw.qlm")) {
return false;
}
以上是关于Binder使用的所有内容,有兴趣的同学还可以继续深入底层了解Binder驱动是如何进行进程间通信的。另外还需要思考一个问题,如果每个业务都新建一个Service肯定是不妥的,那么如何使用一个Service管理客户端中的多个Binder呢?具体实现查看项目中的BinderPoolService相关代码。
>> 转载请注明来源:Android IPC 机制免费分享,随意打赏
发表评论