美文网首页android基础知识
Andromeda:适用于多进程架构的组件通信框架(上)

Andromeda:适用于多进程架构的组件通信框架(上)

作者: Innocencellh | 来源:发表于2018-05-29 20:54 被阅读0次

    转载自:https://mp.weixin.qq.com/s/7SfRnt_jJLU0nrHPeuwomg

    引言

    其实Android的组件化由来已久,而且已经有了一些不错的方案,特别是在页面跳转这方面,比如阿里的ARouter, 天猫的统跳协议, Airbnb的DeepLinkDispatch, 借助注解来完成页面的注册,从而很巧妙地实现了路由跳转。

    但是,尽管像ARouter等方案其实也支持接口的路由,然而令人遗憾的是只支持单进程的接口路由

    Andromeda的功能

    • 本地服务路由,注册本地服务是registerLocalService(Class, Object), 获取本地服务是getLocalService(Class);
    • 远程服务路由,注册远程服务是registerRemoteService(Class, Object), 获取远程服务是getRemoteService(Class);
    • 全局(含所有进程)事件总线, 订阅事件为subscribe(String, EventListener), 发布事件为publish(Event);
    • 远程方法回调,如果某个业务接口需要远程回调,可以在定义aidl接口时使用IPCCallback;

    注: 这里的服务不是Android中四大组件的Service,而是指提供的接口与实现。为了表示区分,后面的服务均是这个含义,而Service则是指Android中的组件。

    为什么需要区分本地服务和远程服务
    最重要的一个原因是本地服务的参数和返回值类型不受限制,而远程服务则受binder通信的限制。Andromeda的出现为组件化完成了最后一块拼图。

    Andromeda和其他组件间通信方案的对比如下:

    易用性 IPC性能 支持IPC 支持跨进程事件总线 支持IPC Callback
    Andromeda yes yes yes
    DDComponentForAndroid 较差 - no no no
    ModularizationArchitecture 较差 yes no no

    接口依赖还是协议依赖

    有人觉得使用Event或ModuleBean来作为组件间通信载体的话,就不用每个业务模块定义自己的接口了,调用方式也很统一???
    但是也有如下缺点:

    • 虽然不用定义接口了,但是为了适应各自的业务需求,如果使用Event的话,需要定义许多Event; 如果使用ModuleBean的话,需要为每个ModuleBean定义许多字段,甚至于即使是让另一方调用一个空方法,也需要创建一个ModuleBean对象,这样的消耗是很大的; 而且随着业务增多,这个模块对应的ModuleBean中需要定义的字段会越来越多,消耗会越来越大
    • 代码可读性较差。定义Event/ModuleBean的方式不如接口调用那么直观,不利于项目的维护
    • 我们理解的协议通信,是指跨平台/序列化的通信方式,类似终端和服务器间的通信或restful这种。现在这种形式在终端内很常见了。协议通信具备一种很强力解耦能力,但是协议如果变化了,两端怎么同步就变得有点复杂,至少要配合一些框架来实现。在一个应用内,这样会不会有点复杂?协议通信用作组件间通信的话太重了,从而导致它应对业务变化时不够灵活。

    所以最终决定采用接口+数据结构的方式进行组件间通信,对于需要暴露的业务接口和数据结构,放到一个公共的module中。

    跨进程路由方案的实现

    本地服务的路由就不说了,一个Map就可以搞定。
    远程服务,需要解决一下难题:

    • 让任意两个组件都能够很方便地通信,即一个组件注册了自己的远程服务,任意一个组件都能轻易调用到
    • 让远程服务的注册和使用像本地服务一样简单,即要实现阻塞调用
    • 不能降低通信的效率

    最终方案:

    建立一个一个binder的管理器,如下图:

    核心流程.png
    详细分析如下:

    这个架构的核心就是Dispatcher和RemoteTransfer, Dispatcher负责管理所有进程的业务binder以及各进程中RemoteTransfer的binder; 而RemoteTransfer负责管理它所在进程所有Module的服务binder.

    • 每个进程有一个RemoteTransfer,它负责管理这个进程中所有Module的远程服务,包含远程服务的注册、注销以及获取,RemoteTransfer提供的远程服务接口为:
    interface IRemoteTransfer {
        oneway void registerDispatcher(IBinder dispatcherBinder);
       
        oneway void unregisterRemoteService(String serviceCanonicalName);
    
        oneway void notify(in Event event);}
    

    这个接口是给binder管理者Dispatcher使用的,其中registerDispatcher()是Dispatcher将自己的binder反向注册到RemoteTransfer中,之后RemoteTransfer就可以使用Dispatcher的代理进行服务的注册和注销了。

    • 在进程初始化时,RemoteTransfer将自己的信息(其实就是自身的binder)发送给与Dispatcher同进程的DispatcherService, DispatcherService收到之后通知Dispatcher, Dispatcher就通过RemoteTransfer的binder将自己反射注册过去,这样RemoteTransfer就获取到了Dispatcher的代理。流程图如下:


      调用流程简图.png

      这个注册过程一般发生在子进程初始化的时候,但是其实即使在子进程初始化时没有注册也不要紧,其实是可以推迟到需要将自己的远程服务提供出去,或者需要获取其他进程的Module的服务时再做这件事也可以。

    远程服务注册流程简图:

    远程服务注册流程简图.png
    Dispatcher则持有所有进程的RemoteTransfer的代理binder, 以及所有提供服务的业务binder, Dispatcher提供的远程服务接口是IDispatcher,其定义如下:
    interface IDispatcher {
    
       BinderBean getTargetBinder(String serviceCanonicalName);
       
       IBinder fetchTargetBinder(String uri);
    
       void registerRemoteTransfer(int pid,IBinder remoteTransferBinder);
    
       void registerRemoteService(String serviceCanonicalName,String processName,IBinder binder);
    
       void unregisterRemoteService(String serviceCanonicalName);
    
       void publish(in Event event);}
    

    同步获取binder的问题

    设想这样一个场景:在Dispatcher反向注册之前,就有一个Module想要调用另外一个进程中的某个服务(这个服务已经注册到Dispatcher中), 那么此时如何同步获取呢?

    其实是有办法的,那就是通过ContentProvider!

    利用ContentProviderClient直接获取IBinder,其调用方式如下:

     public static Bundle call(Context context, Uri uri, String method, String arg, Bundle extras) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
                return context.getContentResolver().call(uri, method, arg, extras);
            }
            ContentProviderClient client = tryGetContentProviderClient(context, uri);
            Bundle result = null;
            if (null == client) {
                Logger.i("Attention!ContentProviderClient is null");
            }
            try {
                result = client.call(method, arg, extras);
            } catch (RemoteException ex) {
                ex.printStackTrace();
            } finally {
                releaseQuietly(client);
            }
            return result;
        }
    
        private static ContentProviderClient tryGetContentProviderClient(Context context, Uri uri) {
            int retry = 0;
            ContentProviderClient client = null;
            while (retry <= RETRY_COUNT) {
                SystemClock.sleep(100);
                retry++;
                client = getContentProviderClient(context, uri);
                if (client != null) {
                    return client;
                }
                //SystemClock.sleep(100);
            }
            return client;
        }
    
        private static ContentProviderClient getContentProviderClient(Context context, Uri uri) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                return context.getContentResolver().acquireUnstableContentProviderClient(uri);
            }
            return context.getContentResolver().acquireContentProviderClient(uri);
        }
    }
    

    可以在调用结果的Bundle中携带IBinder即可,但是这个方案的问题在于ContentProviderClient兼容性较差,在有些手机上第一次运行时会crash,这样显然无法接受。另外一种方式则是借助ContentResolver的query()方法,将binder放在Cursor中DispatcherCursor的定义如下,其中,generateCursor()方法用于将binder放入Cursor中,而stripBinder()方法则用于将binder从Cursor中取出。

    public class DispatcherCursor extends MatrixCursor {
    
        public static final String KEY_BINDER_WRAPPER = "KeyBinderWrapper";
    
        private static Map<String, DispatcherCursor> cursorMap = new ConcurrentHashMap<>();
    
        public static final String[] DEFAULT_COLUMNS = {"col"};
    
        private Bundle binderExtras = new Bundle();
    
        public DispatcherCursor(String[] columnNames, IBinder binder) {
            super(columnNames);
            binderExtras.putParcelable(KEY_BINDER_WRAPPER, new BinderWrapper(binder));
        }
    
        @Override
        public Bundle getExtras() {
            return binderExtras;
        }
    
        public static DispatcherCursor generateCursor(IBinder binder) {
            try {
                DispatcherCursor cursor;
                cursor = cursorMap.get(binder.getInterfaceDescriptor());
                if (cursor != null) {
                    return cursor;
                }
                cursor = new DispatcherCursor(DEFAULT_COLUMNS, binder);
                cursorMap.put(binder.getInterfaceDescriptor(), cursor);
                return cursor;
            } catch (RemoteException ex) {
                return null;
            }
        }
    
        public static IBinder stripBinder(Cursor cursor) {
            if (null == cursor) {
                return null;
            }
            Bundle bundle = cursor.getExtras();
            bundle.setClassLoader(BinderWrapper.class.getClassLoader());
            BinderWrapper binderWrapper = bundle.getParcelable(KEY_BINDER_WRAPPER);
            return null != binderWrapper ? binderWrapper.getBinder() : null;
        }}
    

    其中BinderWrapper是binder的包装类,其定义如下:

    public class BinderWrapper implements Parcelable {
    
        private final IBinder binder;
    
        public BinderWrapper(IBinder binder) {
            this.binder = binder;
        }
    
        public BinderWrapper(Parcel in) {
            this.binder = in.readStrongBinder();
        }
    
        public IBinder getBinder() {
            return binder;
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeStrongBinder(binder);
        }
    
        public static final Creator<BinderWrapper> CREATOR = new Creator<BinderWrapper>() {
            @Override
            public BinderWrapper createFromParcel(Parcel source) {
                return new BinderWrapper(source);
            }
    
            @Override
            public BinderWrapper[] newArray(int size) {
                return new BinderWrapper[size];
            }
        };}
    

    再回到我们的问题,其实只需要设置一个与Dispatcher在同一个进程的ContentProvider,那么这个问题就解决了。

    Dispatcher的进程设置

    由于Dispatcher承担着管理各进程的binder的重任,所以不能让它轻易狗带。对于绝大多数App,主进程是存活时间最长的进程,将Dispatcher置于主进程就可以了。但是,有些App中存活时间最长的不一定是主进程,比如有的音乐App, 将主进程杀掉之后,播放进程仍然存活,此时显然将Dispatcher置于播放进程是一个更好的选择。
    为了让使用Andromeda这个方案的开发者能够根据自己的需求进行配置,提供了DispatcherExtension这个Extension, 开发者在apply plugin: ‘org.qiyi.svg.plugin'之后,可在gradle中进行配置:

    dispatcher{
        process ":downloader"}
    }
    

    当然,如果主进程就是存活时间最长的进程的话,则不需要做任何配置,只需要apply plugin: 'org.qiyi.svg.plugin'即可。

    相关文章

      网友评论

        本文标题:Andromeda:适用于多进程架构的组件通信框架(上)

        本文链接:https://www.haomeiwen.com/subject/gwvajftx.html