[Android]你不知道的Android进程化(4)--进程通信AIDL框架

大家好,我系苍王。

以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。

[Android]如何做一个崩溃率少于千分之三噶应用app--章节列表

Google爸爸,听说要将一些插件化hook系统的变量属性禁用,Android P之后很可能将会不再有插件化、热更新、主题变换、资源加固等骚操作。试图hook,你将会看到 NoSuchFieldException 或者 NoSuchMethodException 等错误提示。
可见文章Android P 调用隐藏API限制原理中对api隐藏说明
具体通过@hide的注释让属性提示变量不存在。
这样就会要求app上线前测试更加严谨,而不是在上线后通过各种修复替换功能等方式,每周发版的日子,将不会出现了,不停歇的加班。
RN技术其原理涉及到view的渲染,暂时并未受到波及。
现在国内,有继续走RN的,各大厂有走类似小程序方向的快应用,都是使用js语法,写web还能拯救一堆程序猿啊。

接下来说一下进程通信,其实任何的进程通信方式,都可以在组件化开发中使用。
Android中进程间通信的方式
1.Aidl
2.Messager
3.Content provider
4.Socket
5.文件共享

前三个都是基于binder机制实现的。
本节想要介绍的是使用aidl做的进程通信,单单使用aidl进行通信其实并不难。原理也有很多文章介绍过,但是如何设计一个通用的aidl通信架构,就需要考究了。
这里介绍的是ModularizationArchitecture中使用的aidl的通信架构。


ModularizationArchitecture通信架构

这里ModularizationArchitecture架构使用了aidl作为路由传输的实现。

屏幕快照 2018-03-29 下午9.07.33.png

1.每个需要通信的module,都需要继承MaProvider类,然后在BaseApplicationLogic启动的时候注册。
2.MaAction作为触发的事件,继承出来改写其方法,invoke方法是事件实现。需要在MaProvider中注册事件。
3.MaActionResult是事件结果回调。
4.LocalRouter是当前进程调用MaAction中的invoke执行方法。
5.WideRouter是跨进程调用时使用,需要在MaApplication启动的时候注册将module中的的LocalRouterConnectService。

注册提供内容



注册进程路由信息到广域路由中

public class MyApplication extends MaApplication {
    
    //注册进程路由信息到广域路由
    @Override
    public void initializeAllProcessRouter() {
        WideRouter.registerLocalRouter("com.spinytech.maindemo",MainRouterConnectService.class);
        WideRouter.registerLocalRouter("com.spinytech.maindemo:music",MusicRouterConnectService.class);
        WideRouter.registerLocalRouter("com.spinytech.maindemo:pic",PicRouterConnectService.class);
    }

    //初始化进程启动
    @Override
    protected void initializeLogic() {
        registerApplicationLogic("com.spinytech.maindemo",999, MainApplicationLogic.class);
        registerApplicationLogic("com.spinytech.maindemo",998, WebApplicationLogic.class);
        registerApplicationLogic("com.spinytech.maindemo:music",999, MusicApplicationLogic.class);
        registerApplicationLogic("com.spinytech.maindemo:pic",999, PicApplicationLogic.class);
    }

    //是否使用多进程
    @Override
    public boolean needMultipleProcess() {
        return true;
    }
}

进程初始化的时候注册MaProvider到进程路由中

public class MainApplicationLogic extends BaseApplicationLogic {
    @Override
    public void onCreate() {
        super.onCreate();
        //注册Provider
        LocalRouter.getInstance(mApplication).registerProvider("main",new MainProvider());
    }
}

为每个Provider绑定可以触发的Action任务

public class MainProvider extends MaProvider {
    @Override
    protected void registerActions() {
        registerAction("sync",new SyncAction());
        registerAction("async",new AsyncAction());
        registerAction("attachment",new AttachObjectAction());
    }
}

下面是进程内同步通信


进程内同步通信

进程内调用

RouterResponse response = LocalRouter.getInstance(MaApplication.getMaApplication())  //进程中单例LocalRouter
                            .route(MainActivity.this, RouterRequest.obtain(MainActivity.this) //在缓存池中获取请求
                                    .provider("main")   //设定provider
                                    .action("sync")     //设定调用的action
                                    .data("1", "Hello")   //设定数据
                                    .data("2", "World"));

通过注册的内容找到相应的action,然后调用action中的invoke方法

public RouterResponse route(Context context, @NonNull RouterRequest routerRequest) throws Exception {
        Logger.d(TAG, "Process:" + mProcessName + "\nLocal route start: " + System.currentTimeMillis());
        RouterResponse routerResponse = new RouterResponse();
        // Local request
        //检查domain是不是在同一个进程
        if (mProcessName.equals(routerRequest.getDomain())) {
            HashMap<String, String> params = new HashMap<>();
            Object attachment = routerRequest.getAndClearObject();
            params.putAll(routerRequest.getData());
            Logger.d(TAG, "Process:" + mProcessName + "\nLocal find action start: " + System.currentTimeMillis());
            //通过provider索引到action
            MaAction targetAction = findRequestAction(routerRequest);
            routerRequest.isIdle.set(true);
            Logger.d(TAG, "Process:" + mProcessName + "\nLocal find action end: " + System.currentTimeMillis());
            routerResponse.mIsAsync = attachment == null ? targetAction.isAsync(context, params) : targetAction.isAsync(context, params, attachment);
            // Sync result, return the result immediately
            // 同步调用.
            if (!routerResponse.mIsAsync) {
                //调用action的实现
                MaActionResult result = attachment == null ? targetAction.invoke(context, params) : targetAction.invoke(context, params, attachment);
                //包装response
                routerResponse.mResultString = result.toString();
                routerResponse.mObject = result.getObject();
                Logger.d(TAG, "Process:" + mProcessName + "\nLocal sync end: " + System.currentTimeMillis());
            }

下面是进程内异步通信


进程内异步通信[图片上传中...(屏幕快照 2018-03-30 下午12.36.03.png-c9d7e7-1522384627443-0)]

route方法中在mIsAsync设定是否异步

public RouterResponse route(Context context, @NonNull RouterRequest routerRequest) throws Exception {
        Logger.d(TAG, "Process:" + mProcessName + "\nLocal route start: " + System.currentTimeMillis());
        RouterResponse routerResponse = new RouterResponse();
        // Local request
        //检查domain是不是在同一个进程
        if (mProcessName.equals(routerRequest.getDomain())) {
            ...
            // Sync result, return the result immediately
            // 同步调用.
            if (!routerResponse.mIsAsync) {
                ...
            }
            // Async result, use the thread pool to execute the task.
            //异步调用
            else {
                //创建异步任务
                LocalTask task = new LocalTask(routerResponse, params,attachment, context, targetAction);
                //通过线程池调用
                routerResponse.mAsyncResponse = getThreadPool().submit(task);
            }

异步调用任务是使用Callback任务

    //使用Future Callable的方式使用线程池
    private class LocalTask implements Callable<String> {
        private RouterResponse mResponse;
        private HashMap<String, String> mRequestData;
        private Context mContext;
        private MaAction mAction;
        private Object mObject;
        public LocalTask(RouterResponse routerResponse, HashMap<String, String> requestData,Object object, Context context, MaAction maAction) {
            this.mContext = context;
            this.mResponse = routerResponse;
            this.mRequestData = requestData;
            this.mAction = maAction;
            this.mObject = object;
        }

        @Override
        public String call() throws Exception {
            //调用action中的invoke方法
            MaActionResult result = mObject == null ? mAction.invoke(mContext, mRequestData) : mAction.invoke(mContext, mRequestData, mObject);
            mResponse.mObject = result.getObject();
            Logger.d(TAG, "Process:" + mProcessName + "\nLocal async end: " + System.currentTimeMillis());
            return result.toString();
        }
    }

下面是跨进程通信


跨进程通信

使用aidl调用广域的WideRouter

//检查domain是不是在同一个进程
        if (mProcessName.equals(routerRequest.getDomain())) {
        ...
        }
        // IPC request
        else {
            //获取进程domain
            String domain = routerRequest.getDomain();
            String routerRequestString = routerRequest.toString();
            routerRequest.isIdle.set(true);
            //检查是不已经绑定了广域路由WideRouter
            if (checkWideRouterConnection()) {
                Logger.d(TAG, "Process:" + mProcessName + "\nWide async check start: " + System.currentTimeMillis());
                //If you don't need wide async check, use "routerResponse.mIsAsync = false;" replace the next line to improve performance.
                //检查是同步还是异步
                routerResponse.mIsAsync = mWideRouterAIDL.checkResponseAsync(domain, routerRequestString);
                Logger.d(TAG, "Process:" + mProcessName + "\nWide async check end: " + System.currentTimeMillis());
            }
            // Has not connected with the wide router.
            else {
                //调用连接广域路由WideRouter
                routerResponse.mIsAsync = true;
                ConnectWideTask task = new ConnectWideTask(routerResponse, domain, routerRequestString);
                routerResponse.mAsyncResponse = getThreadPool().submit(task);
                return routerResponse;
            }
            //同步调用
            if (!routerResponse.mIsAsync) {
                //aidl传输给相关进程的LocalRouterConnectService
                routerResponse.mResultString = mWideRouterAIDL.route(domain, routerRequestString);
                Logger.d(TAG, "Process:" + mProcessName + "\nWide sync end: " + System.currentTimeMillis());
            }
            // Async result, use the thread pool to execute the task.
            //异步调用
            else {
                //设定广域调用任务
                WideTask task = new WideTask(domain, routerRequestString);
                routerResponse.mAsyncResponse = getThreadPool().submit(task);
            }
        }
        //返回ReouterResponse
        return routerResponse;

广域路由连接检测,调用aidl连接到WideRouterConnectService

    private class ConnectWideTask implements Callable<String> {
        private RouterResponse mResponse;
        private String mDomain;
        private String mRequestString;

        public ConnectWideTask(RouterResponse routerResponse, String domain, String requestString) {
            this.mResponse = routerResponse;
            this.mDomain = domain;
            this.mRequestString = requestString;
        }

        @Override
        public String call() throws Exception {
            Logger.d(TAG, "Process:" + mProcessName + "\nBind wide router start: " + System.currentTimeMillis());
            //绑定WideRouterConnectService
            connectWideRouter();
            int time = 0;
            while (true) {
                //等待广域路由绑定完成
                if (null == mWideRouterAIDL) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    time++;
                } else {
                    break;
                }
                //超过30秒就放弃,抛出错误
                if (time >= 600) {
                    ErrorAction defaultNotFoundAction = new ErrorAction(true, MaActionResult.CODE_CANNOT_BIND_WIDE, "Bind wide router time out. Can not bind wide router.");
                    MaActionResult result = defaultNotFoundAction.invoke(mApplication, new HashMap<String, String>());
                    mResponse.mResultString = result.toString();
                    return result.toString();
                }
            }
            Logger.d(TAG, "Process:" + mProcessName + "\nBind wide router end: " + System.currentTimeMillis());
            //调用关于路由传输到对应的进程路由
            String result = mWideRouterAIDL.route(mDomain, mRequestString);
            Logger.d(TAG, "Process:" + mProcessName + "\nWide async end: " + System.currentTimeMillis());
            return result;
        }
    }

WideRouterConnectService对对应的进程路由分发通信,监听返回。

//广域路由处理
    IWideRouterAIDL.Stub stub = new IWideRouterAIDL.Stub() {

        @Override
        public boolean checkResponseAsync(String domain, String routerRequest) throws RemoteException {
            //检查是否异步调动
            return WideRouter.getInstance(MaApplication.getMaApplication())
                            .answerLocalAsync(domain, routerRequest);
        }

        @Override
        public String route(String domain, String routerRequest) {
            try {
                //广域路由分发到对应的进程路由中
                return WideRouter.getInstance(MaApplication.getMaApplication())
                        .route(domain, routerRequest)
                        .mResultString;
            } catch (Exception e) {
                e.printStackTrace();
                return new MaActionResult.Builder()
                        .code(MaActionResult.CODE_ERROR)
                        .msg(e.getMessage())
                        .build()
                        .toString();
            }
        }

        @Override
        public boolean stopRouter(String domain) throws RemoteException {
            //停止连接进程路由
            return WideRouter.getInstance(MaApplication.getMaApplication())
                    .disconnectLocalRouter(domain);
        }

    };

根据domain分发到对应进程的ILocalRouterAIDL

public RouterResponse route(String domain, String routerRequest) {
        Logger.d(TAG, "Process:" + PROCESS_NAME + "\nWide route start: " + System.currentTimeMillis());
        RouterResponse routerResponse = new RouterResponse();
        //是否已经被要求停止任务
        if (mIsStopping) {
            MaActionResult result = new MaActionResult.Builder()
                    .code(MaActionResult.CODE_WIDE_STOPPING)
                    .msg("Wide router is stopping.")
                    .build();
            routerResponse.mIsAsync = true;
            routerResponse.mResultString = result.toString();
            return routerResponse;
        }
        //广域路由不能作为调用对象
        if (PROCESS_NAME.equals(domain)) {
            MaActionResult result = new MaActionResult.Builder()
                    .code(MaActionResult.CODE_TARGET_IS_WIDE)
                    .msg("Domain can not be " + PROCESS_NAME + ".")
                    .build();
            routerResponse.mIsAsync = true;
            routerResponse.mResultString = result.toString();
            return routerResponse;
        }
        //获取对应进程路由的对象
        ILocalRouterAIDL target = mLocalRouterAIDLMap.get(domain);
        if (null == target) {
            //是否已经绑定了本地路由,没有就启动绑定
            if (!connectLocalRouter(domain)) {
                MaActionResult result = new MaActionResult.Builder()
                        .code(MaActionResult.CODE_ROUTER_NOT_REGISTER)
                        .msg("The " + domain + " has not registered.")
                        .build();
                routerResponse.mIsAsync = false;
                routerResponse.mResultString = result.toString();
                Logger.d(TAG, "Process:" + PROCESS_NAME + "\nLocal not register end: " + System.currentTimeMillis());
                return routerResponse;
            } else {
                // Wait to bind the target process connect service, timeout is 30s.
                Logger.d(TAG, "Process:" + PROCESS_NAME + "\nBind local router start: " + System.currentTimeMillis());
                int time = 0;
                //等待完成绑定进程连接
                while (true) {
                    target = mLocalRouterAIDLMap.get(domain);
                    if (null == target) {
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        time++;
                    } else {
                        Logger.d(TAG, "Process:" + PROCESS_NAME + "\nBind local router end: " + System.currentTimeMillis());
                        break;
                    }
                    //设定30s超时
                    if (time >= 600) {
                        MaActionResult result = new MaActionResult.Builder()
                                .code(MaActionResult.CODE_CANNOT_BIND_LOCAL)
                                .msg("Can not bind " + domain + ", time out.")
                                .build();
                        routerResponse.mResultString = result.toString();
                        return routerResponse;
                    }
                }
            }
        }
        try {
            Logger.d(TAG, "Process:" + PROCESS_NAME + "\nWide target start: " + System.currentTimeMillis());
            //对应进程调用返回
            String resultString = target.route(routerRequest);
            routerResponse.mResultString = resultString;
            Logger.d(TAG, "Process:" + PROCESS_NAME + "\nWide route end: " + System.currentTimeMillis());
        } catch (RemoteException e) {
            e.printStackTrace();
            MaActionResult result = new MaActionResult.Builder()
                    .code(MaActionResult.CODE_REMOTE_EXCEPTION)
                    .msg(e.getMessage())
                    .build();
            routerResponse.mResultString = result.toString();
            return routerResponse;
        }
        return routerResponse;
    }

基本原理就介绍到这里了。
1.aidl是google为Android进程通信提供的方式,使用了代理模式,其内部集成了IBinder,使用了Binder的方式通信,已经成为套路的规则,维护成本低。
2.当序列化后的数据单元过大时,就会出问题,报出android.os.TransactionTooLargeException。其数据量限制为1M
3.原理上说就是binder只拷贝一次,使用虚拟内存和物理内存页映射,比socket高效,也安全。
4.这里介绍的框架,其中是通过本地路由和广域路由间的传送和切换来完成。只能交流变量和调用方法,无法通过aidl获取资源。本地路由能力并未有ARouter使用的方便,进程内对无法提供获取Fragment View等资源获取,可以考虑拓展。但是此本地和广域路由设计非常优秀。
5.wutongke有出了一个框架加上编译时注解的优化版https://github.com/wutongke/ModularizationArchitecture

下一节将会继续介绍Messager进程通信框架,敬请期待。


Android进程化学习
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容