Andromeda source code parsing (synchronous access to services)

Posted by Devon11 on Tue, 04 Feb 2020 18:31:52 +0100

Andromeda

Andromeda is iqiyi's open-source component communication framework for multi process architecture.

github Address: https://github.com/iqiyi/Andromeda

Its characteristics are as follows:

  • There is no need for the developer to perform bindService() operation or define the Service, just define the aidl interface and implementation
  • Get services synchronously. Instead of the asynchronous way of obtaining bindService(), it is transformed into synchronous acquisition
  • Lifecycle automation. Improve or reduce the service provider process according to the life cycle of Fragment or Activity
  • Support the Callback of IPC and the event bus across processes
  • The way of "interface + data structure" is adopted to realize the communication between components. Compared with the way of protocol, this way is simpler to realize and easier to maintain

Summary

As we all know, if we use the binder to achieve inter process communication, we usually need to implement the communication logic in the onServiceConnected method by customizing ServiceConnection.
If other operations are needed after inter process communication, they can only be implemented asynchronously.

Andromeda implements synchronous invocation of remote services.
This paper mainly explores how to realize the "synchronous call of remote service" in Andromeda.

Basic concepts

The core of Andromeda architecture is Dispatcher and RemoteTransfer.
Each process communicates with the Dispatcher process through RemoteTransfer to meet the requirements of multi process communication between businesses.

DIspatcher

There is only one global, which exists in the process with the longest life cycle. (not necessarily the main process, such as music app. The longest life cycle is the process of playing music.)
Manage the business binders of all processes and the binders of RemoteTransfer in each process.

RemoteTransfer

One for each process.
The binder responsible for managing all the businesses of the process in which it resides.

Illustrations

Take a simple example. For example, there are two processes a and B. A and B need to realize inter process communication, so the communication diagram is as follows:

Synchronous call of remote service

Let's look directly at the demo code in the Andromeda project:

    private void useBuyAppleInShop() {
        //IBinder buyAppleBinder = Andromeda.getInstance().getRemoteService(IBuyApple.class);
        IBinder buyAppleBinder = Andromeda.with(this).getRemoteService(IBuyApple.class);
        if (null == buyAppleBinder) {
            Toast.makeText(this, "buyAppleBinder is null! May be the service has been cancelled!", Toast.LENGTH_SHORT).show();
            return;
        }
        IBuyApple buyApple = IBuyApple.Stub.asInterface(buyAppleBinder);
        if (null != buyApple) {
            try {
                int appleNum = buyApple.buyAppleInShop(10);
                Toast.makeText(BananaActivity.this, "got remote service in other process(:banana),appleNum:" + appleNum, Toast.LENGTH_SHORT).show();

            } catch (RemoteException ex) {
                ex.printStackTrace();
            }
        }
    }

The above code content:

  1. Get the IBinder object registered in advance directly through Andromeda.
  2. Get the ibuyappl object through the IBinder object. (that is, the object in the aidl file for interprocess communication)
  3. Cross process calls are made directly by calling methods in the ibuyappl object.

Source code analysis

The IBinder obtained by getRemoteService in the above code first enters this method.

RemoteManager.getRemoteService()

Finally, we can see that the IBinder object is obtained from the BinderBean object, which is obtained through RemoteTransfer.getInstance().getRemoteServiceBean().

    @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);
        if (binderBean == null) {
            Logger.e("Found no binder for "+serviceCanonicalName+"! Please check you have register implementation for it or proguard reasons!");
            return null;
        }
        String commuStubServiceName = ConnectionManager.getInstance().bindAction(appContext, binderBean.getProcessName());
        commuStubServiceNames.add(commuStubServiceName);
        return binderBean.getBinder();
    }

RemoteTransfer.getRemoteServiceBean()

There are two situations:

  1. If you have obtained the BinderBean object before, you can get it directly from the cache.
  2. If the BinderBean object has not been obtained before, it will not be obtained in the cache. You need to obtain it from the Dispatcher through serviceTransfer.getAndSaveIBinder().

Next, look at the two methods serviceTransfer.getIBinderFromCache() and serviceTransfer.getAndSaveIBinder().

    @Override
    public synchronized BinderBean getRemoteServiceBean(String serviceCanonicalName) {
        Logger.d("RemoteTransfer-->getRemoteServiceBean,pid=" + android.os.Process.myPid() + ",thread:" + Thread.currentThread().getName());
        BinderBean cacheBinderBean = serviceTransfer.getIBinderFromCache(context, serviceCanonicalName);
        if (cacheBinderBean != null) {
            return cacheBinderBean;
        }
        initDispatchProxyLocked();
        if (serviceTransfer == null || dispatcherProxy == null) {
            return null;
        }
        return serviceTransfer.getAndSaveIBinder(serviceCanonicalName, dispatcherProxy);
    }

serviceTransfer.getIBinderFromCache()

First of all, we can see whether it is a local service. If it is a local service, we will not perform the bind operation and directly encapsulate a BinderBean return.
If it is not a local service, check whether there is the object in the cache, and if so, return it.

    public BinderBean getIBinderFromCache(Context context, String serviceCanonicalName) {
        //If it is your own process or the main process, do not bind
        if (stubBinderCache.get(serviceCanonicalName) != null) {
            return new BinderBean(stubBinderCache.get(serviceCanonicalName),
                    ProcessUtils.getProcessName(context));
        }

        if (remoteBinderCache.get(serviceCanonicalName) != null) {
            return remoteBinderCache.get(serviceCanonicalName);
        }
        return null;
    }

serviceTransfer.getAndSaveIBinder()

Get the BinderBean object through the IDispatcher object. If you get it, put it in the cache for a long time.
At the same time, set the listening of death for the binder in the BinderBean object. If the binder dies unexpectedly, it should be removed from the cache.

    public BinderBean getAndSaveIBinder(final String serviceName, IDispatcher dispatcherProxy) {
        try {
            BinderBean binderBean = dispatcherProxy.getTargetBinder(serviceName);
            if (null == binderBean) {
                return null;
            }
            try {
                binderBean.getBinder().linkToDeath(new IBinder.DeathRecipient() {
                    @Override
                    public void binderDied() {
                        remoteBinderCache.remove(serviceName);
                    }
                }, 0);
            } catch (RemoteException ex) {
                ex.printStackTrace();
            }
            Logger.d("get IBinder from ServiceDispatcher");
            remoteBinderCache.put(serviceName, binderBean);
            return binderBean;
        } catch (RemoteException ex) {
            ex.printStackTrace();
        }
        return null;
    }
254 original articles published, 748 praised, 1.16 million visitors+
His message board follow

Topics: github Fragment Android