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

Cang_Wang發表於2018-03-31

大家好,我係蒼王。

以下是我這個系列的相關文章,有興趣可以參考一下,可以給個喜歡或者關注我的文章。

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

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

接下來說一下程式通訊,其實任何的程式通訊方式,都可以在元件化開發中使用。
Android中程式間通訊的方式
1.Aidl
2.Messenger
3.Content provider
4.Socket
5.檔案共享

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


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

這裡ModularizationArchitecture架構使用了aidl作為路由傳輸的實現。

[Android]你不知道的Android程式化(4)--程式通訊AIDL框架
檔案結構

1.每個需要通訊的module,都需要繼承MaProvider類,然後在BaseApplicationLogic啟動的時候註冊。
2.MaAction作為觸發的事件,繼承出來改寫其方法,invoke方法是事件實現。需要在MaProvider中註冊事件。
3.MaActionResult是事件結果回撥。
4.LocalRouter是當前程式呼叫MaAction中的invoke執行方法。
5.WideRouter是跨程式呼叫時使用,需要在MaApplication啟動的時候註冊將module中的的LocalRouterConnectService。

註冊提供內容


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

註冊程式路由資訊到廣域路由中

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());
    }
}
複製程式碼

下面是程式內同步通訊


[Android]你不知道的Android程式化(4)--程式通訊AIDL框架
程式內同步通訊

程式內呼叫

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());
            }
複製程式碼

下面是程式內非同步通訊


[Android]你不知道的Android程式化(4)--程式通訊AIDL框架
程式內非同步通訊[圖片上傳中...(螢幕快照 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();
        }
    }
複製程式碼

下面是跨程式通訊


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

使用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]你不知道的Android程式化(4)--程式通訊AIDL框架
Android程式化學習


相關文章