跨进程单例 | Andorid进程通信AIDL原理及应用

简介: 设计模式中的单例模式在多进程场景下会演变成多例,存在线程安全问题。本文通过跨进程通信机制让多进程共享单例。

设计模式中的单例保证类在当前进程的内存空间中只有一份实例。但在多进程环境下,不同进程运行在各自独立的内存空间中,多进程使单例失效,变为多例,即每个内存空间中有一个实例,存在线程安全问题。

本文试图通过跨进程通信的方式让多进程共享单例。

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()将通信接口的实现存放在BinderIInterface mOwner成员中。

当绑定服务成功后,客户端调用Stub.asInterface(),先查询是否存在通信接口本地实现(即当前线程中对通信接口的实现)。若存在,则返回,否则构建远程服务的本地代理。

如果构造StubasInterface()发生在同一个进程,则查询必然会命中通信接口的本地实现。否则本地代理将被创建用于代理跨进程通信:

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 接口自动生成的类,默默地做了很多事情,其中的StubProxy虽定义在同一个 java 文件中,但它们可能运行在不同的进程中。

至此只是分析了 Android 跨进程通信的两个桥墩,即StubProxy,而连接它们之间的桥梁(Binder驱动)属于更加低层的内容,下次有时间再研读源码。

目录
相关文章
|
存储 安全 算法
【C++智能指针 相关应用】深入探索C++智能指针:跨进程、动态库与最佳实践
【C++智能指针 相关应用】深入探索C++智能指针:跨进程、动态库与最佳实践
74 5
|
2月前
|
消息中间件 Unix Linux
Linux进程间通信(IPC)介绍:详细解析IPC的执行流程、状态和通信机制
Linux进程间通信(IPC)介绍:详细解析IPC的执行流程、状态和通信机制
75 1
|
13天前
|
弹性计算 Dubbo Serverless
Serverless 应用引擎操作报错合集之阿里函数计算中,生成图片时进程卡住如何解决
Serverless 应用引擎(SAE)是阿里云提供的Serverless PaaS平台,支持Spring Cloud、Dubbo、HSF等主流微服务框架,简化应用的部署、运维和弹性伸缩。在使用SAE过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
21 3
|
9天前
|
存储 Linux 程序员
【Linux-14】进程地址空间&虚拟空间&页表——原理&知识点详解
【Linux-14】进程地址空间&虚拟空间&页表——原理&知识点详解
|
9天前
|
Unix Linux
【Linux】一文了解【进程优先级相关知识点】&【PRI / NI值】背后的修正原理(13)
【Linux】一文了解【进程优先级相关知识点】&【PRI / NI值】背后的修正原理(13)
|
9天前
|
Linux Shell 调度
【Linux系列】fork( )函数原理与应用详解——了解【父子进程及其特性】(代码演示,画图帮助理解,思维导图,精简)(11)
【Linux系列】fork( )函数原理与应用详解——了解【父子进程及其特性】(代码演示,画图帮助理解,思维导图,精简)(11)
|
11天前
|
存储 算法 Linux
【计算机操作系统】深入探究CPU,PCB和进程工作原理
【计算机操作系统】深入探究CPU,PCB和进程工作原理
|
2月前
|
消息中间件 缓存 监控
【C++ 观察者模式的应用】跨进程观察者模式实战:结合ZeroMQ和传统方法
【C++ 观察者模式的应用】跨进程观察者模式实战:结合ZeroMQ和传统方法
93 1
|
2月前
|
前端开发 Android开发 iOS开发
应用研发平台EMAS使用 aliyun-react-native-push 库接入推送和辅助通道,推送都可以收到,但是在App切到后台或者杀掉进程之后就收不到推送了,是需要配置什么吗?
【2月更文挑战第31天】应用研发平台EMAS使用 aliyun-react-native-push 库接入推送和辅助通道,推送都可以收到,但是在App切到后台或者杀掉进程之后就收不到推送了,是需要配置什么吗?
32 2
|
2月前
|
存储 安全 Linux
深入Linux进程内核:揭开进程工作原理的神秘面纱
深入Linux进程内核:揭开进程工作原理的神秘面纱
59 0