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

你們好,我係蒼王。javascript

如下是我這個系列的相關文章,有興趣能夠參考一下,能夠給個喜歡或者關注個人文章。java

[Android]如何作一個崩潰率少於千分之三噶應用app--章節列表android

Google爸爸,據說要將一些插件化hook系統的變量屬性禁用,Android P以後極可能將會再也不有插件化、熱更新、主題變換、資源加固等騷操做。試圖hook,你將會看到 NoSuchFieldException 或者 NoSuchMethodException 等錯誤提示。
可見文章Android P 調用隱藏API限制原理中對api隱藏說明
具體經過@hide的註釋讓屬性提示變量不存在。
這樣就會要求app上線前測試更加嚴謹,而不是在上線後經過各類修復替換功能等方式,每週發版的日子,將不會出現了,不停歇的加班。
RN技術其原理涉及到view的渲染,暫時並未受到波及。
如今國內,有繼續走RN的,各大廠有走相似小程序方向的快應用,都是使用js語法,寫web還能拯救一堆程序猿啊。git

接下來講一下進程通訊,其實任何的進程通訊方式,均可以在組件化開發中使用。
Android中進程間通訊的方式
1.Aidl
2.Messenger
3.Content provider
4.Socket
5.文件共享github

前三個都是基於binder機制實現的。
本節想要介紹的是使用aidl作的進程通訊,單單使用aidl進行通訊其實並不難。原理也有不少文章介紹過,可是如何設計一個通用的aidl通訊架構,就須要考究了。
這裏介紹的是ModularizationArchitecture中使用的aidl的通訊架構。web


ModularizationArchitecture通訊架構

這裏ModularizationArchitecture架構使用了aidl做爲路由傳輸的實現。小程序

文件結構

1.每一個須要通訊的module,都須要繼承MaProvider類,而後在BaseApplicationLogic啓動的時候註冊。
2.MaAction做爲觸發的事件,繼承出來改寫其方法,invoke方法是事件實現。須要在MaProvider中註冊事件。
3.MaActionResult是事件結果回調。
4.LocalRouter是當前進程調用MaAction中的invoke執行方法。
5.WideRouter是跨進程調用時使用,須要在MaApplication啓動的時候註冊將module中的的LocalRouterConnectService。api

註冊提供內容緩存



註冊進程路由信息到廣域路由中安全

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有出了一個框架加上編譯時註解的優化版github.com/wutongke/Mo…

下一節將會繼續介紹Messenger進程通訊框架,敬請期待。


Android進程化學習
相關文章
相關標籤/搜索