今天是端午节放假第一天,祝各位小伙伴端午快乐!今天我也给大家送个“粽子”-AIDL之进程间共享单例。关于AIDL的简单使用本文不会涉及太多,前面我们有两篇陆续聊了Android AIDL的相关姿势,有需要的小伙伴可以回头看下:
AIDL与Binder浅析http://www.jianshu.com/p/4ebd4783d3d9
IPC之Messengerhttp://www.jianshu.com/p/328605278a93
本文会涉及到大概下面这些姿势:
1.进程间通信AIDL的使用步骤
2.AIDL的实现原理
3.单例模式
4.工厂模式
5.模板模式
为什么要写这样一个主题?有时候我们项目中会有这样一个需求,一个应用中有两个进程A和B,进程A中有一些配置放在单例Singleton,进程B需要去拿配置,有哪些方式可以实现呢?一种是序列化的方式,比如写到一个文件,或者写到数据库中;另外一种就是通过Binder的方式,也就是我们今天要说的这种方式。序列化的方式操作会繁琐一点,需要一堆的写入和读出的操作,Binder的方式会更灵活一点。
我们还是通过一个简单的栗子来看下怎么实现的。分成A和B两个进程,A进程和B进程分别有自己的单例Singleton,A进程可以通过B进程的单例调用单例的方法,反过来B进程也可以通过A进程的单例调用单例的方法,操作起来很方便,我们在进程单例中有两个方法和一个成员变量count,方法increment用于递增count,方法getcount用于获取count的值。
1.png 2.png 3.png
1.使用方式
使用起来很简单,先定义两个单例的AIDL文件,因为需要跨进程传输,所以需要AIDL文件。两个单例拥有同样的两个方法,increament中参数是调用方法的当前进程的名称。
interface ISingletonMain {
void increment(String currentPorcessName);
int getCount();
}
interface ISingletonB {
void increment(String currentPorcessName);
int getCount();
}
当然,为了拿到对方进程的单例,是需要去绑定服务的,在Main进程中绑定B进程的服务:
bindService(ServiceB.class);
B进程绑定Main进程:
bindService(MainService.class);
使用起来就直接使用单例的调用方式就可以,比如调用Main进程单例的方法:
SingletonMainImpl.getInstance().increment(Utils.currentProcessName());
调用B进程单例的方法也是类似:
SingletonBImpl.getInstance().increment(Utils.currentProcessName());
就是这样,使用起来就是一句代码搞定。
2. 原理解析
使用起来很方便,我们来看看背后是怎么实现的。首先应该有个共识,为了共享单例,进程间需要通过Binder通信来拿到对方的实例。为了拿到这个对方的实例,总结起来是这么几个步骤:
1.定义进程间通信的接口。调用这个接口的方法可以拿到对方的单例;
2.绑定服务。A绑定B进程的服务,B进程会生成这个单例,封装成一个Binder,通过Binder池回传一个Binder到A进程;
3.绑定服务成功。成功时,A可以拿到B传送过来的Binder,通过Binder可以取出这个单例;
4.调用单例方法。拿到单例后调用单例接口aidl文件中的方法时会发起IPC调用,在APP层就跟调用本进程单例一样。
我们对应着上面的步骤来看看幕后的实现。
1.定义进程间通信的接口
先来总体看下有多少AIDL接口,先大体介绍下每个接口:
1.IInstanceTransfer.aidl就是Main进程和B进程通信的接口,返回的是进程实例的工厂InstanceFactory
2.实例工厂InstanceFactory为了在AIDL中通过Binder传输也需要定义一个InstanceFactory.aidl文件,并且要实现Parcelable接口;
public class InstanceFactory implements Parcelable
3.剩下两个就是需要传输的进程实例的aidl文件,里面定义了提供跨进程调用的方法:
interface ISingletonMain {
void increment(String currentPorcessName);
int getCount();
}
4.png
我们定义一个获取单例的接口,返回的InstanceFactory是一个“工厂”,这又是什么套路?
interface IInstanceTransfer {
InstanceFactory transfer();
}
我们首先简单讲下工厂模式,比如我现在需要一辆轿车,但是我不可能自己去造一辆车,如果有这么一个车厂Factory,我直接给Factory下个指令“我需要一辆轿车”,Factory就直接造出一辆轿车返回给我,这样是不是很方便,同样另外一个小伙伴B需要SUV,直接给Factory下个指令“我需要一辆SUV”,就能拿到Factory的SUV。
代码直接看我们下面的栗子,我们现在需要Main进程和B进程的单例(至于什么是单例模式我们后面结合代码具体看),那么我们直接给InstanceFactory这个工厂下个指令就行,这样很方便客户的使用。
如果是Main进程,InstanceFactory就造一个Mian进程的单例;如果是B进程,InstanceFactory就造一个B进程的单例。writeStrongInterface方法就是将实例封装成Binder,用于AIDL。
if (Utils.isMainProcess()) {
dest.writeStrongInterface(SingletonMainImpl.getInstance());
} else if (Utils.isBProcess()) {
dest.writeStrongInterface(SingletonBImpl.getInstance());
}
2.绑定服务
定义好接口,接着就是绑定服务,APP层其实就是bindService。两个进程都有对应的Service,Main进程对应MainService,B进程对应ServiceB。要实现的东西其实都是一样的,就是返回IInstanceTransfer的实现即可,因此我们将使用另外一种设计模式,模板模式来抽取下代码的实现。我们先拐个弯看下模板模式,讲的会比较简单点,有需要的小伙伴可以去看下设计模式的相关资料。
模板模式其实和继承有很大的关系,就是我们将多个类共性的方法抽取出来放到父类中,每个子类需要个性化实现的就在父类中留个hook给子类自己去实现,但是方法的执行过程其实是在父类的控制中,简单举个栗子,在hookMethod方法的前后可以实现一些其他的工作,我们这里只是简单的打印log。在外面调用TemplateClass的TemplateMethod就可以实现调用同一个模板方法但是有不同实现的效果。
public abstract class TemplateClass {
public void TemplateMethod(){
Log.d(TemplateClass.class.toString(), "This is before hookMethod");
hookMethod();
Log.d(TemplateClass.class.toString(), "This is after hookMethod");
}
public abstract void hookMethod();
}
我们看下Main进程绑定服务的过程,两个进程共同的服务我们抽取出来一个BaseService。
bindService(ServiceB.class);//绑定B进程的服务
public class BaseService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new InstanceTransferImpl() ;
}
}
服务返回的就是AIDL接口IInstanceTransfer的实现,这个我们就不展开讲了,有需要的小伙伴自行参考:http://www.jianshu.com/p/4ebd4783d3d9
public class InstanceTransferImpl extends IInstanceTransfer.Stub {
@Override
public InstanceFactory transfer() throws RemoteException {
return new InstanceFactory();
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
try{
return super.onTransact(code, data, reply, flags);
}catch (RuntimeException e){
Log.i("InstanceTransferImpl", "Unexpected exception" + e);
throw e;
}
}
}
可以看出,调用transfer方法就是返回InstanceFactory。
3.绑定服务成功
绑定服务成功Main进程就能在ServiceConnection中拿到B进程的Binder,其实B进程在绑定Main进程成功时也是要这个步骤,而我们把实例Binder的工作都封装到了InstanceFactory中,因此我们可以用模板模式抽取一个ServiceConnection。
public class ServiceConnectionImpl implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
InstanceTransferImpl.asInterface(service).transfer();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
这样不管Main进程绑定B进程服务还是B进程绑定Main进程服务都可以使用这个ServiceConnection,还是很方便的。
我们来重点看下绑定服务成功后是怎么拿到单例的。全都浓缩在这句代码里了:
InstanceTransferImpl.asInterface(service).transfer();
跨进程所以我们是拿到InstanceTransferImpl的代理,调用的自然也是代理的transfer,我们分析下这个流程:
1.在代理的transfer()方法中会调用
mRemote.transact(Stub.TRANSACTION_transfer, _data, _reply, 0);
2.来到Stub的onTransact方法中:
com.example.juexingzhe.processshareinstance.InstanceFactory _result = this.transfer();
reply.writeNoException();
if ((_result != null)) {
reply.writeInt(1);
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
this当然指的就是B进程,所以调用到B进程的transfer方法,得到InstanceFactor。
3.调用InstanceFactory:
接着会调用 InstanceFactory.writeToParcel,判断是B进程,会在reply中写入信息,我们这边主要进行三个工作,写入B进程标识;写入B进程总的方法调用的统计数字;写入B进程的实例。后面这些信息都可以在Main进程中读出。
@Override
public void writeToParcel(Parcel dest, int flags) {
if (Utils.isMainProcess()) {
dest.writeInt(PROCESS_MAIN);
dest.writeInt(SingletonMainImpl.COUNT);
dest.writeStrongInterface(SingletonMainImpl.getInstance());
} else if (Utils.isBProcess()) {
dest.writeInt(PROCESS_B);
dest.writeInt(SingletonBImpl.COUNT);
dest.writeStrongInterface(SingletonBImpl.getInstance());
}
}
4.再回到代理的transfer方法中,会调用InstanceFactory.CREATOR
_result = com.example.juexingzhe.processshareinstance.InstanceFactory.CREATOR.createFromParcel(_reply);
5.InstanceFactory中读出B进程实例
根据进程标识赋值 SingletonBImpl.INSTANCE
public InstanceFactory createFromParcel(Parcel in) {
return new InstanceFactory(in);
}
public InstanceFactory(Parcel in) {
int processId = in.readInt();
switch (processId) {
case PROCESS_B:
SingletonBImpl.COUNT = in.readInt();
SingletonBImpl.INSTANCE = ISingletonB.Stub.asInterface(in.readStrongBinder());
break;
}
}
进过上面过程我们就可以在Main进程拿到B的实例了,但是要注意一点,Main进程拿到的SingletonBImpl.INSTANCE和B进程的实例其实不是真正的同一个Object,因为是跨进程通过Binder池进行传送,但是因为每个Binder都有标识符,所以底层能做出正确的识别。为什么说不是同一个Object,我们可以看个截图。在进程Main中获取单例就是ISingletonMainImpl,这个没什么疑问;但是在进程B中获取的进程Main的单例是ISingletonMainImpl$Stub$Proxy,也就是跨进程的单例代理。从两个实例的mObject也可以看出来不是同一个对象。
5.png 6.png4.调用单例方法
这个其实就很简单了,就跟这个单例就在Main进程一样。
SingletonBImpl.getInstance().increment(Utils.currentProcessName());
SingletonBImpl.getInstance().getCount()
原理也是通过代理跨进程进行调用,我们以getCount方法的调用为例:
1.通过代理调用到getCount方法:
mRemote.transact(Stub.TRANSACTION_getCount, _data, _reply, 0);
2.调用到B进程单例的getCount方法,在onTransact方法中, 将结果保存到reply中。
case TRANSACTION_getCount: {
data.enforceInterface(DESCRIPTOR);
int _result = this.getCount();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
3.再回到代理的transact方法中,从reply中读出结果并返回。
_result = _reply.readInt();
最后我们看看B进程实例的实现,使用的就是单例模式。关键点就是构造方法是私有的,不允许外面实例化这个单例,只能通过静态方法getInstance获取实例。SingletonBImpl两个方法实现都比较简单没什么可说的,主要是getInstance()方法。主要是B进程那么直接就返回SingletonBImpl,如果是A进程会自动重连。
public class SingletonBImpl extends ISingletonB.Stub {
public static ISingletonB INSTANCE;
public static int COUNT = 0;
private SingletonBImpl() {
}
public static ISingletonB getInstance() {
if (null == INSTANCE) {
synchronized (SingletonBImpl.class) {
if (null == INSTANCE) {
if (Utils.isBProcess()) {
INSTANCE = new SingletonBImpl();
} else {
Context context = MyApplication.getContext();
Intent intent = new Intent(context, ServiceB.class);
context.bindService(intent, new ServiceConnectionImpl(), Context.BIND_AUTO_CREATE);
}
}
}
}
return INSTANCE;
}
@Override
public void increment(String currentPorcessName) throws RemoteException {
COUNT++;
StringBuilder stringBuilder = new StringBuilder("进程:" + currentPorcessName + "调用" + Utils.currentProcessName() + "的方法increment");
Utils.showToast(stringBuilder.toString());
}
@Override
public int getCount() {
return COUNT;
}
}
2.总结
今天我们这篇文章的AIDL接口会比较多,主要是用到了几个简单的设计模式,可以方便的扩展。主要有进程通信的接口,还有“工厂”的接口,另外两个就是进程单例的接口。为了实现两个进程共享单例,我们是通过AIDL接口IInstanceTransfer获取一个“工厂”,工厂会自动根据当前的进程实例化进程实例,通过Binder传输这个单例,在跨进程拿到这个单例后就可以调用进程单例AIDL接口定义的方法来实现各种操作。要注意的一点就是拿到单例后也是通过跨进程AIDL进行类单例调用的,这就是为什么文章前面需要定义两个进程单例AIDL接口的原因。还是那句话,建议结合前面AIDL入门的文章看会比较好点。
文章链接:http://www.jianshu.com/p/4ebd4783d3d9
本文栗子也已经上传Github:https://github.com/juexingzhe/ProcessShareInstance
完结,谢谢!
参考链接:
http://www.jianshu.com/p/4ebd4783d3d9
https://toutiao.io/posts/6yvvo2/preview
欢迎关注公众号:JueCode
网友评论