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

Cang_Wang發表於2018-06-29

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

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


Android元件化架構熱賣中

元件化群1已經滿員,進來的可以加群2 763094035


近來看到愛奇藝釋出了多程式的架構框架Andromeda。研究一下其多程式的通訊方式。
具體github地址
通過此框架的初步分析
1.通過grade外掛完善AndroidManifest.xml配置檔案
2.通過ContentProvider傳輸binder物件
3.活動binder.stub物件
4.動態繫結程式service以及binder物件
5.多程式binder管理

其在利用自定義Gradle外掛來插入DispatchService和DipatchProvider。
自定義的Gradle外掛只會有app module上執行。
其會讀取Manifest裡面的內容並進行修改。


[Android]你不知道的Android程式化(6)--程式通訊Andromeda框架
外掛配置.png

1.查詢遍歷Manifest檔案

   void injectStubServiceToManifest(Project project) {

        println "injectStubServiceToManifest"
        //主目錄
        rootDirPath = project.rootDir.absolutePath

        def android = project.extensions.getByType(AppExtension)
        
        this.dispatcher = project.extensions.getByType(DispatcherExtension)

        project.afterEvaluate {
            android.applicationVariants.all { variant ->
                 
                if (pkgName == null) {
                    //獲取包名
                    pkgName = getPackageName(variant)
                    println "pkgName:" + pkgName
                }
               //查詢遍歷Mainfest檔案
                variant.outputs.each { output ->

                    output.processManifest.doLast {

                        println "manifestOutputDirectory:" + output.processManifest.manifestOutputDirectory.absolutePath

                        //output.getProcessManifest().manifestOutputDirectory
                        output.processManifest.outputs.files.each { File file ->
                            //在gradle plugin 3.0.0之前,file是檔案,且檔名為AndroidManifest.xml
                            //在gradle plugin 3.0.0之後,file是目錄,且不包含AndroidManifest.xml,需要自己拼接
                            //除了目錄和AndroidManifest.xml之外,還可能會包含manifest-merger-debug-report.txt等不相干的檔案,過濾它
                            if ((file.name.equalsIgnoreCase("AndroidManifest.xml") && !file.isDirectory()) || file.isDirectory()) {
                                if (file.isDirectory()) {
                                    //3.0.0之後,自己拼接AndroidManifest.xml
                                    injectManifestFile(new File(file, "AndroidManifest.xml"))
                                } else {
                                    //3.0.0之前,直接使用
                                    injectManifestFile(file)
                                }
                            }
                        }
                    }

                }
            }
        }
    }
複製程式碼

其內會寫入三種檔案

def static final STUB_SERVICE = 'org.qiyi.video.svg.stub.CommuStubService$CommuStubService'

xml.application {

            int index = 0
            //每個程式都宣告使用這個CommuStubService檔案
            customProcessNames.each {
                //加上序號,$CommuStuService會替換成數字
                String serviceName = "${STUB_SERVICE}" + index.toString()

                service("${NAME}": serviceName,
                        "${ENABLED}": "${TRUE}",
                        "${EXPORTED}": "${FALSE}",
                        "${PROCESS}": it
                )

                if (matchedServices == null) {
                    matchedServices = new HashMap<>()
                }
                matchedServices.put(it, serviceName)

                ++index
            }
複製程式碼

這裡DipatcherService和DipatchProvider是binder的中轉管理控制元件,預設配置到主程式。

        //配置程式地址
        this.dispatcher = project.extensions.getByType(DispatcherExtension)

           //之後,寫入DispatcherService和DispatcherProvider
            def dispatcherProcess = dispatcher.process
            println "dispatcher.process:" + dispatcher.process
            if (dispatcherProcess != null && dispatcherProcess.length() > 0) {
                service("${NAME}": DISPATCHER_SERVICE,
                        "${ENABLED}": "${TRUE}",
                        "${EXPORTED}": "${FALSE}",
                        "${PROCESS}": dispatcherProcess
                )

                provider(
                        "${AUTHORITIES}": getAuthority(),
                        "${EXPORTED}": "${FALSE}",
                        "${NAME}": DISPTACHER_PROVIDER,
                        "${ENABLED}": "${TRUE}",
                        "${PROCESS}": dispatcherProcess
                )

            } else {
                service("${NAME}": DISPATCHER_SERVICE,
                        "${ENABLED}": "${TRUE}",
                        "${EXPORTED}": "${FALSE}"
                )

                provider(
                        "${AUTHORITIES}": getAuthority(),
                        "${EXPORTED}": "${FALSE}",
                        "${NAME}": DISPTACHER_PROVIDER,
                        "${ENABLED}": "${TRUE}"
                )

            }
複製程式碼

正如通訊所涉及的必要的幾個步驟
1.註冊
2.發其通訊
3.binder間的相互繫結
4.binder間傳輸資料

[Android]你不知道的Android程式化(6)--程式通訊Andromeda框架
註冊流程

首先註冊,通過公共的Andromeda入口註冊提供遠端溝通的服務

Andromeda.getInstance().registerRemoteService(IBuyApple.class, BuyAppleImpl.getInstance());

    //RemoteTransfer
    public static <T extends IBinder> void registerRemoteService(Class serviceClass, T stubBinder) {
        if (null == serviceClass || null == stubBinder) {
            return;
        }
        RemoteTransfer.getInstance().registerStubService(serviceClass.getCanonicalName(), stubBinder);
    }

    //RemoteServiceTransfer
    public void registerStubServiceLocked(String serviceCanonicalName, IBinder stubBinder,
                                          Context context, IDispatcher dispatcherProxy, IRemoteTransfer.Stub stub) {
        stubBinderCache.put(serviceCanonicalName, stubBinder);
        if (dispatcherProxy == null) {
            BinderWrapper wrapper = new BinderWrapper(stub.asBinder());
            Intent intent = new Intent(context, DispatcherService.class);
            intent.setAction(Constants.DISPATCH_REGISTER_SERVICE_ACTION);
            intent.putExtra(Constants.KEY_REMOTE_TRANSFER_WRAPPER, wrapper);
            intent.putExtra(Constants.KEY_BUSINESS_BINDER_WRAPPER, new BinderWrapper(stubBinder));
            intent.putExtra(Constants.KEY_SERVICE_NAME, serviceCanonicalName);
            setProcessInfo(intent, context);
            ServiceUtils.startServiceSafely(context, intent);
        } else {
            try {
                dispatcherProxy.registerRemoteService(serviceCanonicalName,
                        ProcessUtils.getProcessName(context), stubBinder);
            } catch (RemoteException ex) {
                ex.printStackTrace();
            }
        }
    }
複製程式碼

啟動Service註冊到DispatcherService當中

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent == null) {
            return super.onStartCommand(intent, flags, startId);
        }
        Logger.d("DispatcherService-->onStartCommand,action:" + intent.getAction());
        if (Constants.DISPATCH_REGISTER_SERVICE_ACTION.equals(intent.getAction())) {
            //註冊遠端服務
            registerRemoteService(intent);
        } else if (Constants.DISPATCH_UNREGISTER_SERVICE_ACTION.equals(intent.getAction())) {
            //登出遠端服務
            unregisterRemoteService(intent);
        } else if (Constants.DISPATCH_EVENT_ACTION.equals(intent.getAction())) {
            //傳遞資料
            publishEvent(intent);
        }
        return super.onStartCommand(intent, flags, startId);
    }

    private void registerRemoteService(Intent intent) {

        BinderWrapper wrapper = intent.getParcelableExtra(Constants.KEY_REMOTE_TRANSFER_WRAPPER);
        BinderWrapper businessWrapper = intent.getParcelableExtra(Constants.KEY_BUSINESS_BINDER_WRAPPER);
        String serviceCanonicalName = intent.getStringExtra(Constants.KEY_SERVICE_NAME);
        int pid = intent.getIntExtra(Constants.KEY_PID, -1);
        String processName = intent.getStringExtra(Constants.KEY_PROCESS_NAME);
        try {
            if (TextUtils.isEmpty(serviceCanonicalName)) {
                //注意:RemoteTransfer.sendRegisterInfo()時,serviceCanonicalName為null,這是正常的,此時主要目的是reigsterAndReverseRegister()
                Logger.e("service canonical name is null");
            } else {
             //註冊到分發器上管理
                Dispatcher.getInstance().registerRemoteService(serviceCanonicalName,
                        processName, businessWrapper.getBinder());
            }
        } catch (RemoteException ex) {
            ex.printStackTrace();
        } finally {
            if (wrapper != null) {
                registerAndReverseRegister(pid, wrapper.getBinder());
            }
        }

    }
複製程式碼

ServiceDipatcher負責管理服務資訊

 @Override
    public void registerRemoteServiceLocked(final String serviceCanonicalName, String processName,
                                            IBinder binder) throws RemoteException {
        Log.d(TAG, "ServiceDispatcher-->registerStubServiceLocked,serviceCanonicalName:" + serviceCanonicalName + ",pid:" + android.os.Process.myPid() + ",thread:" + Thread.currentThread().getName());
        if (binder != null) {
            binder.linkToDeath(new IBinder.DeathRecipient() {
                @Override
                public void binderDied() {
                    Logger.d("ServiceDispatcher-->binderDied,serviceCanonicalName:" + serviceCanonicalName);
                    BinderBean bean = remoteBinderCache.remove(serviceCanonicalName);
                    //實際上這裡是還沒實現執行緒同步,但是並不會影響執行結果,所以其實下面這句就沒有同步的必要。
                    if (bean != null) {
                        emergencyHandler.handleBinderDied(Andromeda.getAppContext(), bean.getProcessName());
                    }
                }
            }, 0);
            remoteBinderCache.put(serviceCanonicalName, new BinderBean(binder, processName));
            Logger.d("ServiceDispatcher-->registerRemoteServiceLocked(),binder is not null");
        } else {
            Log.d(TAG, "ServiceDispatcher-->registerRemoteServiceLocked(),binder is null");
        }
    }
複製程式碼

最後需要完成反向繫結,意思是Dispatcher註冊可以傳輸的binder物件,傳輸中心也需要繫結Dispatcher物件

    /**
     * 註冊和反向註冊
     *
     * @param pid
     * @param transterBinder
     */
    private void registerAndReverseRegister(int pid, IBinder transterBinder) {
        Logger.d("DispatcherService-->registerAndReverseRegister,pid=" + pid + ",processName:" + ProcessUtils.getProcessName(pid));
        IRemoteTransfer remoteTransfer = IRemoteTransfer.Stub.asInterface(transterBinder);
        
        Dispatcher.getInstance().registerRemoteTransfer(pid, transterBinder);

        if (remoteTransfer != null) {
            Logger.d("now register to RemoteTransfer");
            try {
                remoteTransfer.registerDispatcher(Dispatcher.getInstance().asBinder());
            } catch (RemoteException ex) {
                ex.printStackTrace();
            }
        } else {
            Logger.d("IdspatcherRegister IBinder is null");
        }
    }
複製程式碼
[Android]你不知道的Android程式化(6)--程式通訊Andromeda框架
獲取遠端服務的binder.png

在呼叫遠端的時候getRemoteSevice獲取目標的binder物件

IBuyApple buyApple = IBuyApple.Stub.asInterface(Andromeda.with(this).getRemoteService(IBuyApple.class));
複製程式碼

遠端管理RemoteManager

    @Override
    public IBinder getRemoteService(Class<?> serviceClass) {
        if (null == serviceClass) {
            return null;
        }
        return getRemoteService(serviceClass.getCanonicalName());
    }

    @Override
    public synchronized IBinder getRemoteService(String serviceCanonicalName) {
        Logger.d(this.toString() + "-->getRemoteService,serviceName:" + serviceCanonicalName);
        if (TextUtils.isEmpty(serviceCanonicalName)) {
            return null;
        }
        BinderBean binderBean = RemoteTransfer.getInstance().getRemoteServiceBean(serviceCanonicalName);
        String commuStubServiceName = ConnectionManager.getInstance().bindAction(appContext, binderBean.getProcessName());
        commuStubServiceNames.add(commuStubServiceName);
        return binderBean.getBinder();
    }
複製程式碼

RemoteDispatcher會呼叫DipatchProvider來獲取遠端的binder物件

    @Override
    public synchronized BinderBean getRemoteServiceBean(String serviceCanonicalName) {
        Logger.d("RemoteTransfer-->getRemoteServiceBean,pid=" + android.os.Process.myPid() + ",thread:" + Thread.currentThread().getName());
        //獲取binder包裝的物件
        BinderBean cacheBinderBean = serviceTransfer.getIBinderFromCache(context, serviceCanonicalName);
        if (cacheBinderBean != null) {
            return cacheBinderBean;
        }
        if (null == dispatcherProxy) {
            IBinder dispatcherBinder = getIBinderFromProvider();
            if (null != dispatcherBinder) {
                Logger.d("the binder from provider is not null");
                dispatcherProxy = IDispatcher.Stub.asInterface(dispatcherBinder);
                registerCurrentTransfer();
            }
        }
        if (null == dispatcherProxy) {
            sendRegisterInfo();
            try {
                wait(MAX_WAIT_TIME);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
        if (serviceTransfer == null || dispatcherProxy == null) {
            return null;
        }
        return serviceTransfer.getAndSaveIBinder(serviceCanonicalName, dispatcherProxy);
    }

private IBinder getIBinderFromProvider() {
        Logger.d("RemoteTransfer-->getIBinderFromProvider()");
        Cursor cursor = null;
        try {
            //查詢出cursor物件
            cursor = context.getContentResolver().query(getDispatcherProviderUri(), DispatcherProvider.PROJECTION_MAIN,
                    null, null, null);
            if (cursor == null) {
                return null;
            }
            //使用DispatcherCursor解包
            return DispatcherCursor.stripBinder(cursor);
        } finally {
            IOUtils.closeQuietly(cursor);
        }
    }
複製程式碼

DispatcherProvider使用的查詢返回

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Logger.d("DispatcherProvider-->query,uri:" + uri.getAuthority());
        //DispatcherCursor封裝binder
        return DispatcherCursor.generateCursor(Dispatcher.getInstance().asBinder());
    }

    //通過
    public static DispatcherCursor generateCursor(IBinder binder) {
        try {
            DispatcherCursor cursor;
            cursor = cursorCache.get(binder.getInterfaceDescriptor());
            if (cursor != null) {
                return cursor;
            }
            cursor = new DispatcherCursor(DEFAULT_COLUMNS, binder);
            cursorCache.put(binder.getInterfaceDescriptor(), cursor);
            return cursor;
        } catch (RemoteException ex) {
            return null;
        }
    }
    //通過BinderWrapper來封裝binder
    public DispatcherCursor(String[] columnNames, IBinder binder) {
        super(columnNames);
        binderExtras.putParcelable(KEY_BINDER_WRAPPER, new BinderWrapper(binder));
    }
複製程式碼
  @Override
    public synchronized IBinder getRemoteService(String serviceCanonicalName) {
        Logger.d(this.toString() + "-->getRemoteService,serviceName:" + serviceCanonicalName);
        if (TextUtils.isEmpty(serviceCanonicalName)) {
            return null;
        }
        //獲取binder資訊
        BinderBean binderBean = RemoteTransfer.getInstance().getRemoteServiceBean(serviceCanonicalName);
        //在binder繫結啟動的程式service
        String commuStubServiceName = ConnectionManager.getInstance().bindAction(appContext, binderBean.getProcessName());
        commuStubServiceNames.add(commuStubServiceName);
        return binderBean.getBinder();
    }
複製程式碼

一開始介紹插入了CommuStubService物件,這裡通過StubServiceMatcher通過程式名來找到啟動的程式的service,然後將binder和service繫結到一起

 //這裡不能按照serviceCanonicalName來區分,而是要按照target service來劃分,如果targetService一樣,那就沒必要再繫結
    public synchronized String bindAction(Context context, String serverProcessName) {
        Logger.d("ConnectionManager-->bindAction,serverProcessName:" + serverProcessName);
        //匹配對應的binder對應的Service(CommuStubService$0)
        Intent intent = StubServiceMatcher.matchIntent(context, serverProcessName);
        if (null == intent) {
            Logger.d("match intent is null");
            return null;
        }

        String commuStubServiceName = getCommuStubServiceName(intent);
        ConnectionBean bean = connectionCache.get(commuStubServiceName);
        if (null == bean) {
            //服務連線
            ServiceConnection connection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    Logger.d("onServiceConnected,name:" + name.getShortClassName());
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                    Logger.d("onServiceDisconnected,name:" + name.getShortClassName());
                }
            };
            bean = new ConnectionBean(connection);
            connectionCache.put(commuStubServiceName, bean);
            Logger.d("really start to bind");
            //啟動服務
            context.bindService(intent, connection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
        } else {
            bean.increaseRef();
        }
        return commuStubServiceName;
    }
複製程式碼

1.通過gradle外掛來動態新增配置的四大元件資料
2.通過分發DispatchService服務來管理binder物件
3.通過Parcelable序列化傳輸binder
4.活用IBinder.Stub物件

[Android]你不知道的Android程式化(6)--程式通訊Andromeda框架
Android程式化學習


相關文章