跨进程单例 | 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驱动)属于更加低层的内容,下次有时间再研读源码。

目录
相关文章
|
18天前
|
存储 Unix Linux
进程间通信方式-----管道通信
【10月更文挑战第29天】管道通信是一种重要的进程间通信机制,它为进程间的数据传输和同步提供了一种简单有效的方法。通过合理地使用管道通信,可以实现不同进程之间的协作,提高系统的整体性能和效率。
|
18天前
|
消息中间件 存储 供应链
进程间通信方式-----消息队列通信
【10月更文挑战第29天】消息队列通信是一种强大而灵活的进程间通信机制,它通过异步通信、解耦和缓冲等特性,为分布式系统和多进程应用提供了高效的通信方式。在实际应用中,需要根据具体的需求和场景,合理地选择和使用消息队列,以充分发挥其优势,同时注意其可能带来的复杂性和性能开销等问题。
|
1月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
31 3
|
2月前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
60 3
|
1月前
|
存储 Python
Python中的多进程通信实践指南
Python中的多进程通信实践指南
23 0
|
2月前
|
Java Android开发 数据安全/隐私保护
Android中多进程通信有几种方式?需要注意哪些问题?
本文介绍了Android中的多进程通信(IPC),探讨了IPC的重要性及其实现方式,如Intent、Binder、AIDL等,并通过一个使用Binder机制的示例详细说明了其实现过程。
326 4
|
2月前
|
数据采集 消息中间件 并行计算
进程、线程与协程:并发执行的三种重要概念与应用
进程、线程与协程:并发执行的三种重要概念与应用
60 0
|
2月前
|
消息中间件 程序员 数据处理
探究操作系统中的进程间通信(IPC)机制及其在现代软件开发中的应用
本文深入探讨了操作系统中的核心概念——进程间通信(IPC),揭示了其在现代软件开发中的关键作用。通过对各种IPC机制如管道、消息队列、共享内存等的详细分析,本文旨在为读者提供一个清晰的理解框架,帮助他们掌握如何在实际应用中有效利用这些技术以实现进程间的协同工作。此外,文章还将探讨IPC在高并发环境下的性能优化策略,以及如何避免常见的IPC编程错误。通过结合理论与实践,本文不仅适合希望深入了解操作系统原理的技术人员阅读,也对那些致力于提升软件质量和开发效率的程序员具有重要参考价值。
48 0
|
4月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
4月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
171 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)

热门文章

最新文章

相关实验场景

更多
下一篇
无影云桌面