设计模式中的单例保证类在当前进程的内存空间中只有一份实例。但在多进程环境下,不同进程运行在各自独立的内存空间中,多进程使单例失效,变为多例,即每个内存空间中有一个实例,存在线程安全问题。
本文试图通过跨进程通信的方式让多进程共享单例。
demo 使用 Kotlin 编写,相关系列教程可以点击这里
假设希望跨进程访问的单例长这样(该单例运行在应用主进程,新起进程需访问它):
object LocalSingleton {
private var str = "zero"
override fun setString(string: String?) {
str = string ?: ""
}
override fun getString(): String {
return str
}
}
简单起见,单例中只包含一个 String 和它的读写函数。
Android 跨进程访问是请求/响应
模式,发起请求的称为客户端,响应请求的称为服务端。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() {
bindService(
Intent(this, LocalSingletonService::class.java),
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驱动)属于更加低层的内容,下次有时间再研读源码。