Implementation of cross-process invocation

ContentProvider

CC implements cross-process communication through custom ContentProviders, which are defined for each application in androidmanifest.xml.

  • Exported is true to indicate that it can be invoked across processes.
  • ApplicationId is configured in build.gradle and is usually the package name of the application
  • Aithroities is the package name + the name of the provider itself
<provider
	android:authorities="${applicationId}.com.billy.cc.core.remote"
 	android:name=".remote.RemoteProvider"
 	android:exported="true"
 	/>
Copy the code

RemoteProvider

RemoteProvider inherits ContentProvider and overrides the Query () method to return a RemoteCursor object

public class RemoteProvider extends ContentProvider {

    public static final String[] PROJECTION_MAIN = {"cc"};

    public static final String URI_SUFFIX = "com.billy.cc.core.remote";

    @Override
    public boolean onCreate(a) {
        CC.log("RemoteProvider onCreated! class:%s".this.getClass().getName());
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        if (CC.isRemoteCCEnabled() || getCallingUid() == Process.myUid()) {
            // Get the RemoteCursor singleton from the current ContentProvider process
            return RemoteCursor.getInstance();
        }
        return null;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0; }}Copy the code

RemoteCursor

RemoteCursor inherits the MatrixCursor and is a cursor used for cross-process communication, passing IBinder objects across processes through bundles.

public class RemoteCursor extends MatrixCursor {
    private static final String KEY_BINDER_WRAPPER = "BinderWrapper";

    static final String[] DEFAULT_COLUMNS = {"cc"};

    / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the singleton pattern start -- -- -- -- -- -- -- -- -- -- -- -- -- --
    /** singleton Holder */
    private static class CCCursorHolder {
        private static final RemoteCursor INSTANCE = new RemoteCursor(DEFAULT_COLUMNS, RemoteCCService.getInstance());
    }
    private RemoteCursor(String[] columnNames, IBinder binder) {
        super(columnNames);
        binderExtras.putParcelable(KEY_BINDER_WRAPPER, new BinderWrapper(binder));
    }
    /** Gets the CCCursor singleton in the current process */
    public static RemoteCursor getInstance(a) {
        return RemoteCursor.CCCursorHolder.INSTANCE;
    }
    / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the singleton pattern end -- -- -- -- -- -- -- -- -- -- -- -- -- --

    private Bundle binderExtras = new Bundle();

    @Override
    public Bundle getExtras(a) {
        return binderExtras;
    }

    public static IRemoteCCService getRemoteCCService(Cursor cursor) {
        if (null == cursor) {
            return null;
        }
        Bundle bundle = cursor.getExtras();
        bundle.setClassLoader(BinderWrapper.class.getClassLoader());
        BinderWrapper binderWrapper = bundle.getParcelable(KEY_BINDER_WRAPPER);
        if(binderWrapper ! =null) {
            IBinder binder = binderWrapper.getBinder();
            return IRemoteCCService.Stub.asInterface(binder);
        }
        return null; }}Copy the code

BinderWrapper

BinderWrapper is used to encapsulate the IBinder and implement the Parcelable interface so that IBinder objects can be serialized and deserialized and can be passed across processes.

The process for cross-process CC calls

  1. Enable cross-process function, CC query native APP that supports CC components (start task through thread pool, listen every 50ms)

    (1) Monitor the installation and uninstallation of the local application and update it

    private void listenComponentApps(a) {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        intentFilter.addAction(Intent.ACTION_MY_PACKAGE_REPLACED);
        intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
        intentFilter.addDataScheme(INTENT_FILTER_SCHEME);
        CC.getApplication().registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String packageName = intent.getDataString();
                if (TextUtils.isEmpty(packageName)) {
                    return;
                }
                if (packageName.startsWith(INTENT_FILTER_SCHEME)) {
                    packageName = packageName.replace(INTENT_FILTER_SCHEME + ":"."");
                }
                String action = intent.getAction();
                CC.log("onReceived..... pkg=" + packageName + ", action=" + action);
                if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
                    REMOTE_CONNECTIONS.remove(packageName);
                } else {
                    CC.log("start to wakeup remote app:%s", packageName);
                    if (RemoteConnection.tryWakeup(packageName)) {
                        ComponentManager.threadPool(new ConnectTask(packageName));
                    }
                }
            }
        }, intentFilter);
    }
    Copy the code

    (2) Query all installed applications that support CC components, and periodically query the Binder objects available for the application through the thread pool

    class ConnectTask implements Runnable {
        String packageName;
    
        ConnectTask(String packageName) {
            this.packageName = packageName;
        }
    
        @Override
        public void run(a) {
            IRemoteCCService service = getMultiProcessService(packageName);
            if(service ! =null) { REMOTE_CONNECTIONS.put(packageName, service); }}}Copy the code
  2. Query the RemoteProvider of the corresponding application by using the ContentResolver

    private static IRemoteCCService getService(String processNameTo) {
        Cursor cursor = null;
        try {
            cursor = CC.getApplication().getContentResolver()
                    .query(getDispatcherProviderUri(processNameTo)
                            , RemoteProvider.PROJECTION_MAIN, null
                            , null.null
                    );
            if (cursor == null) {
                return null;
            }
            return RemoteCursor.getRemoteCCService(cursor);
        } finally {
            if(cursor ! =null) {
                try {
                    cursor.close();
                } catch(Exception e) { CCUtil.printStackTrace(e); }}}}Copy the code
  3. If you get a Cursor, get the Cursor’s bundle and serialize it into BinderWrapper. After you get the IBinder object, convert it into an IRemoteCCService interface object.

    public static IRemoteCCService getRemoteCCService(Cursor cursor) {
        if (null == cursor) {
            return null;
        }
        Bundle bundle = cursor.getExtras();
        bundle.setClassLoader(BinderWrapper.class.getClassLoader());
        BinderWrapper binderWrapper = bundle.getParcelable(KEY_BINDER_WRAPPER);
        if(binderWrapper ! =null) {
            IBinder binder = binderWrapper.getBinder();
            return IRemoteCCService.Stub.asInterface(binder);
        }
        return null;
    }
    Copy the code
  4. The IRemoteCCService object makes cross-process calls to components on the corresponding application

    interface IRemoteCCService {
    
        void call(in RemoteCC remoteCC, in IRemoteCallback callback);
    
        void cancel(String callId);
    
        void timeout(String callId);
    
        String getComponentProcessName(String componentName);
    }
    Copy the code