设计模式中的单例保证类在当前进程的内存空间中只有一份实例。但在多进程环境下,不同进程运行在各自独立的内存空间中,多进程使单例失效,变为多例,即每个内存空间中有一个实例,存在线程安全问题。
本文试图通过跨进程通信的方式让多进程共享单例。
demo 使用 Kotlin 编写,相关系列教程可以点击这里
假设希望跨进程访问的单例长这样(该单例运行在应用主进程,新起进程需访问它):
object LocalSingleton { private var str = "zero" override fun setString(string: String?) { str = string ?: "" } override fun getString(): String { return str } } 复制代码
简单起见,单例中只包含一个 String 和它的读写函数。
Android 跨进程访问是请求/响应
模式,发起请求的称为客户端,响应请求的称为服务端。aidl
文件用于定义通信接口,ILocalServiceBoundByRemote.aidl
定义如下:
interface ILocalServiceBoundByRemote { void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); //'读接口' String getString(); //'写接口' void setString(String string); } 复制代码
将通信接口定义成和单例接口一摸一样。然后点击菜单栏Build---make Project
,系统会自动生成一个对应的ILocalServiceBoundByRemote.java
文件,打开瞧瞧:
//'aidl接口继承自IInterface,它是Binder通信的基础接口' public interface ILocalServiceBoundByRemote extends android.os.IInterface { //'桩' public static abstract class Stub extends android.os.Binder implements ILocalServiceBoundByRemote { ... //'代理' private static class Proxy implements ILocalServiceBoundByRemote { ... } } ... } 复制代码
抛开实现细节,整个ILocalServiceBoundByRemote.java
文件自动为我们构建了两个类,一个叫Stub桩
,一个叫Proxy代理
,它们是远程代理中成对出现的概念。
远程代理是一种通信方式,它让两个本无法触及彼此的对象相互通信,详细介绍可以点击这里。Android 使用远程代理模式建立起两个不同进程间通信的 桥墩 。
其中桩
是服务端端对通信接口的实现,代理
是客户端对于桩的代理,代理
和客户端运行在同一进程(代理也称为本地代理),客户端通过代理
向服务端发起请求。
就好比,你想给喜欢的女孩写信,但她无法触及,你不知道往哪寄。好在你认识她闺蜜,把信交给闺蜜(代理)就相当于把信交给了女孩,你无需关心闺蜜是把信读给她听,还是邮寄到她家里。闺蜜屏蔽了这些细节(代理屏蔽了跨进程通信细节)。
Android 中对桩的实现通常是在Service
中 new 一个 Stub
实例,并作为onBind()
返回值,当前 case 中,服务的提供者是单例,遂让单例直接实现桩接口:
object LocalSingleton : ILocalServiceBoundByRemote.Stub() { private var string2 = "zero" override fun setString(string: String?) { string2 = string ?: "" } override fun getString(): String { return string2 } override fun basicTypes(anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String?) { } } 复制代码
然后在Service.onBind()
中直接返回单例:
class LocalSingletonService: Service() { //'构建桩接口' private val binder = LocalSingleton override fun onBind(intent: Intent?): IBinder? { //'将远程服务返回给客户端' return binder } override fun onCreate() { super.onCreate() } } 复制代码
这样只需要在新进程中绑定该服务,就可以跨进程访问单例了,假设我们在新进程中起一个 Activity:
<activity android:name=".proxy.remote.RemoteActivity" android:process=":alienProcess" /> 复制代码
class RemoteActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.remote_activity) //'在新进程中绑定应用主进程的服务' bindLocalSingletonService() } //'绑定服务' private fun bindLocalSingletonService() { Intent(this, LocalSingletonService::class.java).also { bindService(it, serviceConnection, BIND_AUTO_CREATE) } } private var iLocalServiceBoundByRemote: ILocalServiceBoundByRemote? = null //'构建服务连接' private var serviceConnection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) { } override fun onServiceConnected(name: ComponentName?, service: IBinder?) { //'调用Stub.asInterface()获取通信接口' iLocalServiceBoundByRemote = ILocalServiceBoundByRemote.Stub.asInterface(service) //'调用服务' iLocalServiceBoundByRemote?.string iLocalServiceBoundByRemote?.string = “changed by remote activity” } } } 复制代码
绑定服务时需提供ServiceConnection
,在onServiceConnected()
回调中通过调用Stub.asInterface()
获得通信接口,看一下它的实现:
public interface ILocalServiceBoundByRemote extends android.os.IInterface { //'桩' public static abstract class Stub extends android.os.Binder implements ILocalServiceBoundByRemote { public Stub() { //'构造桩时,关联通信接口本地实现' this.attachInterface(this, DESCRIPTOR); } //'将IBinder对象转换成通信接口' public static ILocalServiceBoundByRemote asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } //'查询本地是否存在通信接口的实现' android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); //'若存在,则直接返回通信接口的本地实现' if (((iin != null) && (iin instanceof ILocalServiceBoundByRemote))) { return ((ILocalServiceBoundByRemote) iin); } //'若不存在,则构建远程服务的本地代理' return new ILocalServiceBoundByRemote.Stub.Proxy(obj); } } } public class Binder implements IBinder { //'aidl通信接口的本地实现' private IInterface mOwner; //'查询通信接口的本地实现,若存在在返回,否则返回空' public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) { if (mDescriptor != null && mDescriptor.equals(descriptor)) { return mOwner; } return null; } //'关联通信接口的本地实现' public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) { mOwner = owner; mDescriptor = descriptor; } } 复制代码
系统自动生成的Stub
中有两个供业务层调用的函数,一个是Stub
构造函数,另一个是asInterface()
。
在构造Stub
时会调用Binder.attachInterface()
将通信接口的实现存放在Binder
的IInterface mOwner
成员中。
当绑定服务成功后,客户端调用Stub.asInterface()
,先查询是否存在通信接口本地实现(即当前线程中对通信接口的实现)。若存在,则返回,否则构建远程服务的本地代理。
如果构造Stub
和asInterface()
发生在同一个进程,则查询必然会命中通信接口的本地实现。否则本地代理将被创建用于代理跨进程通信:
public interface ILocalServiceBoundByRemote extends android.os.IInterface { //'桩' public static abstract class Stub extends android.os.Binder implements ILocalServiceBoundByRemote { ... //'本地代理,它实现了服务接口' private static class Proxy implements ILocalServiceBoundByRemote { //'持有远程的引用' private android.os.IBinder mRemote; //'远程服务被注入' Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public java.lang.String getString() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.lang.String _result; try { _data.writeInterfaceToken(DESCRIPTOR); //'发起RPC 调用服务端接口' mRemote.transact(Stub.TRANSACTION_getString, _data, _reply, 0); _reply.readException(); _result = _reply.readString(); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void setString(java.lang.String string) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); //'将客户端参数封装在 Parcel 中' _data.writeString(string); //'发起RPC 调用服务端接口' mRemote.transact(Stub.TRANSACTION_setString, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } } } 复制代码
本地代理实现了服务接口,它将请求参数封装在 Parcel 中,并通过IBinder.transact()
从当前进程向远端进程发起调用请求(这是发生RPC的地方)。此时本地进程被挂起,Binder驱动唤醒远端进程并执行onTransact()
方法中的服务逻辑,这个函数就定义在Stub
中:
public interface ILocalServiceBoundByRemote extends android.os.IInterface { //'桩' public static abstract class Stub extends android.os.Binder implements ILocalServiceBoundByRemote { //'客户端请求处理函数,该函数由IBinder.transact()触发' //'@param code 请求类型' //'@param data 请求输入参数' //'@param reply 请求响应结果' @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { java.lang.String descriptor = DESCRIPTOR; switch (code) { ... //'处理读请求' case TRANSACTION_getString: { data.enforceInterface(descriptor); //'服务端调用预先定义在 aidl 中的服务接口' java.lang.String _result = this.getString(); reply.writeNoException(); reply.writeString(_result); return true; } //'处理写请求' case TRANSACTION_setString: { data.enforceInterface(descriptor); java.lang.String _arg0; _arg0 = data.readString(); //'服务端调用预先定义在 aidl 中的服务接口' this.setString(_arg0); reply.writeNoException(); return true; } default: { return super.onTransact(code, data, reply, flags); } } } ... } //'供服务端实现的业务接口' public java.lang.String getString() throws android.os.RemoteException; public void setString(java.lang.String string) throws android.os.RemoteException; } 复制代码
直到onTransact()
执行完毕,客户进程才会被唤醒以获取服务返回结果。
系统根据 aidl 接口自动生成的类,默默地做了很多事情,其中的Stub
和Proxy
虽定义在同一个 java 文件中,但它们可能运行在不同的进程中。
至此只是分析了 Android 跨进程通信的两个桥墩,即Stub
和Proxy
,而连接它们之间的桥梁(Binder驱动)属于更加低层的内容,下次有时间再研读源码。