preface
Series of articles:
Display process from Android Activity creation to View
Android four components communication core
We know that the four components of Android: Activity/Service/Broadcast/ContentProvider can conduct cross process communication, which are the ability to realize cross process communication with Binder. There are countless connections between the four. This paper will analyze the communication core, connection and difference of the four from a macro perspective.
Through this article, you will learn:
1. Communication foundation of four components
2. Activity interacts with AMS
3. Service interaction with AMS
4. Interaction between Broadcast and AMS
5. ContentProvider interacts with AMS
1. Communication foundation of four components
Basic functions of four components
Let's take a look at the basic functions of the four components:
How do the four interact?
As can be seen from the figure, as long as you get the Context object, you can enable the functions of the four components, which shows the position of Context in Android.
For Context, please move to: Past and present life of Android Context
When we go deeper into the source code in Context, take Context.startService(xx) as an example, we will call ContextImpl.startService(xx), and then call ContextImpl.startServiceCommon(xx).
The implementation of startServiceCommon(xx) method is different before and after Android 8.0.
Take the implementation before Android 8.0 as an example:
#ContextImpl.java private ComponentName startServiceCommon(Intent service, UserHandle user) { try { validateServiceIntent(service); service.prepareToLeaveProcess(this); //The method in ActivityManagerNative is called ComponentName cn = ActivityManagerNative.getDefault().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( getContentResolver()), getOpPackageName(), user.getIdentifier()); ... return cn; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
ActivityManagerNative.getDefault() retrieves data from the singleton. This method returns the IActivityManager interface, and all exposed methods are declared in the IActivityManager interface. The actual object is the ActivityManagerProxy instance, and the ActivityManagerProxy implements the IActivityManager interface. When calling the method in the ActivityManagerProxy, it actually calls the Binder driver through BinderProxy.transact(xx) for cross process communication, and the class that receives the call, that is, the class that implements onTransact(xx) is ActivityManagerNative, It is an abstract class, and its subclass is implemented as ActivityManagerService.
Let's take the implementation after Android 8.0 (included) as an example:
#ContextImpl.java private ComponentName startServiceCommon(Intent service, boolean requireForeground, UserHandle user) { try { validateServiceIntent(service); service.prepareToLeaveProcess(this); //The method in ActivityManagerService is called ComponentName cn = ActivityManager.getService().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( getContentResolver()), requireForeground, getOpPackageName(), user.getIdentifier()); ... return cn; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
Similarly, ActivityManager.getService() also obtains data from the singleton. This method returns the iaactivitymanager interface. The actual object is IActivityManager.Proxy, which implements the IActivityManager interface. When calling the methods in IActivityManager.Proxy, it actually calls the Binder driver through BinderProxy.transact(xx) for cross process communication, and the class that receives the call, that is, the class that implements onTransact(xx), is IActivityManager.Stub, It is an abstract class, and its subclass is implemented as ActivityManagerService.
The difference between the two is shown in the figure below:
ActivityManagerService runs in system_ In the server process; Service manager runs in a separate process; The application runs in another process. Therefore, the above figure involves three processes.
1. ActivityManagerService registers the IBinder reference in ServiceManager. This is step 1. This process is an IPC.
2. When the application process wants to obtain the functions provided by the ActivityManagerService, it needs to query the ServiceManager. This is step 2. This process is an IPC (after taking it once, cache it, and don't use IPC again next time)
3. After obtaining the IBinder reference, the application process looks for the corresponding interface IActivityManager, and then calls the method provided by ActivityManagerService. This is step 3. This process is an IPC.
It can be seen that although the ways to obtain the IActivityManager interface are different before and after Android 8.0, they all have to go through the above three steps. Only after Android 8.0, the automatic code generation function of AIDL is used to replace ActivityManagerProxy(IActivityManager.Proxy), ActivityManagerNative(IActivityManager.Stub) and IActivityManager(AIDL automatically generates this class), which facilitates coding.
For more comparison between AIDL and native Binder, please move to: AIDL application of Android IPC (Part I)
Four component communication bridge
Taking starting Service as an example, the above describes the relationship between the application process and ServiceManager and ActivityManagerService(AMS). The actual ultimate purpose is to call AMS functions. Let's briefly see what functions AMS provides:
#IActivityManager.aidl { //Bind Application void attachApplication(in IApplicationThread app, long startSeq); //Start service ComponentName startService(in IApplicationThread caller, in Intent service, in String resolvedType, boolean requireForeground, in String callingPackage, in String callingFeatureId, int userId); //Binding service int bindService(in IApplicationThread caller, in IBinder token, in Intent service, in String resolvedType, in IServiceConnection connection, int flags, in String callingPackage, int userId); //Publishing services void publishService(in IBinder token, in Intent intent, in IBinder service); //Start Activity int startActivity(in IApplicationThread caller, in String callingPackage, in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options); //Send broadcast int broadcastIntent(in IApplicationThread caller, in Intent intent, in String resolvedType, in IIntentReceiver resultTo, int resultCode, in String resultData, in Bundle map, in String[] requiredPermissions, int appOp, in Bundle options, boolean serialized, boolean sticky, int userId); //Get contentProvider ContentProviderHolder getContentProvider(in IApplicationThread caller, in String callingPackage, in String name, int userId, boolean stable); //Publish contentProvider void publishContentProviders(in IApplicationThread caller, in List<ContentProviderHolder> providers); }
Only some methods related to the four components are listed, and AMS also provides many other methods.
You may have found that the first parameter of each method is "in iaapplicationthread caller". This parameter is very important and we will analyze it later.
It can be seen from the above methods that the four components need to deal with AMS, and AMS controls their life cycle. Therefore, AMS is also called "housekeeper":
Next, we analyze how the four components communicate with themselves (other processes) through AMS.
2. Activity interacts with AMS
There are now two activities, AMSActivity and AMSTargetActivity.
AMSActivity to start AMSTargetActivity, call the following method:
public static void start(Context context) { //Context is AMSActivity Intent intent = new Intent(context, AMSTargetActivity.class); context.startActivity(intent); }
The call stack of this method in AMSActivity process is as follows:
Activity.startActivity-->Activity.startActivityForResult -->FragmentActivity.startActivityForResult -->Instrumentation.execStartActivity -->ActivityTaskManager.getService().startActivity(xx)
ActivityTaskManager.getService() has been analyzed before, that is, get the AMS external interface: IActivityManager, get the reference after calling AMS startActivity(xx) method:
#ActivityManagerService.java public int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) { return mActivityTaskManager.startActivity(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions); }
Focus on two parameters: iaapplicationthread caller and Intent intent.
Intent is familiar to us. It indicates which Activity to start.
Iaapplicationthread is an interface in which many methods are defined, such as:
#IApplicationThread.java { //Callback static broadcast void scheduleReceiver(in Intent intent, in ActivityInfo info, in CompatibilityInfo compatInfo, int resultCode, in String data, in Bundle extras, boolean sync, int sendingUser, int processState); @UnsupportedAppUsage //Callback creation service void scheduleCreateService(IBinder token, in ServiceInfo info, in CompatibilityInfo compatInfo, int processState); @UnsupportedAppUsage //Callback stop service void scheduleStopService(IBinder token); //Callback binding Application void bindApplication(in String packageName, in ApplicationInfo info, in ProviderInfoList providerList, in ComponentName testName, in ProfilerInfo profilerInfo, in Bundle testArguments, IInstrumentationWatcher testWatcher, IUiAutomationConnection uiAutomationConnection, int debugMode, boolean enableBinderTracking, boolean trackAllocation, boolean restrictedBackupMode, boolean persistent, in Configuration config, in CompatibilityInfo compatInfo, in Map services, in Bundle coreSettings, in String buildSerial, in AutofillOptions autofillOptions, in ContentCaptureOptions contentCaptureOptions, in long[] disabledCompatChanges); //Callback Service onStartCommand void scheduleServiceArgs(IBinder token, in ParceledListSlice args); //Callback binding service void scheduleBindService(IBinder token, in Intent intent, boolean rebind, int processState); @UnsupportedAppUsage //Callback unbinding service void scheduleUnbindService(IBinder token, in Intent intent); //Callback Activity lifecycle related void scheduleTransaction(in ClientTransaction transaction); }
This interface is implemented in ActivityThread.java (AIDL definition is used after Android 8.0, which is analyzed here as an example).
#ActivityThread.java private class ApplicationThread extends IApplicationThread.Stub { ... //Implements the methods defined in the iaapplicationthread interface }
What's the use of iaapplicationthread? After we call AMS methods, some methods do not return a value or only return int, such as startActivity(xx). How can our process receive the callback of the Activity's life cycle, not only the Activity's life cycle callback, but also the Service callback. At this time, you have to rely on the iaapplicationthread interface callback.
Therefore, when AMS method is called, the iaapplicationthread instance is passed in. When AMS completes an action, it is called back to the application process through iaapplicationthread.
In fact, only one iaapplicationthread instance is saved for each application process in AMS, and it is first passed to AMS when the process starts:
#ActivityThread.java private void attach(boolean system, long startSeq) { ... if (!system) { //Get AMS reference final IActivityManager mgr = ActivityManager.getService(); try { //Pass in the iaapplicationthread instance to AMS mgr.attachApplication(mAppThread, startSeq); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } ... }
At this time, AMS saves the iaapplicationthread instance. When an application process calls the AMS method later, it will also pass in the iaapplicationthread instance. AMS will directly take it out for use by finding out whether there is an instance.
After calling AMS startActivity(), AMS will detect whether the process of the target Activity is alive. If not, start the process. If yes, move the Activity to the top of the stack, and then process the activities that were moved out of the top of the stack. After these operations, it is necessary to notify the involved application processes, and this notification is called back through iaapplicationthread.
Assuming AMSActivity and AMSTargetActivity are in the same process, AMS will call back the scheduleTransaction() method in iaapplicationthread:
#ApplicationThread public void scheduleTransaction(ClientTransaction transaction) throws RemoteException { ActivityThread.this.scheduleTransaction(transaction); } public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case EXECUTE_TRANSACTION: final ClientTransaction transaction = (ClientTransaction) msg.obj; //Finally, methods such as handleLaunchActivity() in ActivityThread are called //Then call back onCreate/onPause/onResume and other methods of the target Activity mTransactionExecutor.execute(transaction); break; } #ClientTransactionHandler.java void scheduleTransaction(ClientTransaction transaction) { transaction.preExecute(this); sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction); }
It can be seen that after the AMS callback, it is sent to the main thread for execution through the Handler. When the Handler processes the Message, it will call the onCreate()/onResume() / and other methods of the Activity.
This is why we often say that the rewriting methods of Activity are executed in the main thread.
It can be seen from the above that after the application process initiates the startActivity action, AMS manages the life cycle of the target Activity. We only need to rewrite the corresponding method of the target Activity in the application process and process the corresponding logic in it to realize the function of an Activity jump.
3. Service interaction with AMS
Start start Service
Intent intent = new Intent(AMSActivity.this, AMSTargetService.class); startService(intent);
The call stack is as follows:
ContextWrapper.startService--> ContextImpl.startServiceCommon -->ActivityManager.getService().startService(xx)
The AMS interface is still obtained first, and then startService(xx) is called:
#ContextImpl.java public ComponentName startService(IApplicationThread caller, Intent service, String resolvedType, boolean requireForeground, String callingPackage, String callingFeatureId, int userId)
Suppose AMSActivity and AMSTargetService are in the same process.
After receiving the startService request, AMS will find the target Service. If the Service has not been created, AMS will eventually call back the iaapplicationthread methods scheduleCreateService(xx) and scheduleServiceArgs(xx). These methods are processed as follows:
#ActivityThread.java public final void scheduleCreateService(IBinder token, ServiceInfo info, CompatibilityInfo compatInfo, int processState) { ... //Switch to main thread execution sendMessage(H.CREATE_SERVICE, s); } public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) { List<ServiceStartArgs> list = args.getList(); for (int i = 0; i < list.size(); i++) { ... //Switch to main thread execution sendMessage(H.SERVICE_ARGS, s); } } public void handleMessage(Message msg) { switch (msg.what) { case CREATE_SERVICE: //1. Create a Service instance in this method //2. Callback Service onCreate method handleCreateService((CreateServiceData)msg.obj); break; case SERVICE_ARGS: //Callback onStartCommand method handleServiceArgs((ServiceArgsData)msg.obj); break; } }
Similar to Activity, the Service Lifecycle callback is finally switched to the main thread for execution.
This is why we often say that time-consuming tasks cannot be executed in the Service, otherwise ANR is easy to occur, because the callback method is executed in the main thread.
bind start Service
Assuming that AMSActivity and AMSTargetService are not in the same process, take the process of AMSActivity as the client and the process of AMSTargetService as the server, as shown in the following figure:
The premise is that the client and server have been bound to AMS (iaapplicationthread is passed)
1. The client initiates a binding request.
2. AMS looks for the target Service, creates the server-side Service through the iaapplicationthread callback scheduleCreateService, and binds the Service through scheduleBindService.
3. The server creates and binds successfully, and passes the IBinder to AMS.
4. AMS receives the IBinder reference and tells the client that the service binding is successful through the IServiceConnection callback connected method, that is, callback the ServiceConnection.onServiceConnected method registered during client binding.
4. Interaction between Broadcast and AMS
Static registration
There are two registration methods for broadcasting: static and dynamic.
First analyze the static registration:
Declare the broadcast in AndroidManifest.xml, and specify the received Action and the class that handles the Action.
As follows:
<receiver android:name=".ams.AMSTargetBroadcast"> <intent-filter> <action android:name="my_action"></action> </intent-filter> </receiver>
Send a broadcast in AMSActivity. The call stack for sending a broadcast is as follows:
ContextWrapper.sendBroadcast-->ContextImpl.sendBroadcast -->ActivityManager.getService().broadcastIntent(xx)
The AMS interface is still obtained first, and then broadcastIntent(xx) is called:
#ContextImpl.java int broadcastIntent(in IApplicationThread caller, in Intent intent, in String resolvedType, in IIntentReceiver resultTo, int resultCode, in String resultData, in Bundle map, in String[] requiredPermissions, int appOp, in Bundle options, boolean serialized, boolean sticky, int userId);
Suppose AMSActivity and AMSTargetBroadcast are in the same process.
After receiving the broadcastIntent request, AMS forwards it to BroadcastQueue for processing. If the broadcast receiver is statically registered, it will call back the iaapplicationthread schedulereceiver method. The following process is similar to Activity/Service:
After receiving the scheduleReceiver callback, the process where AMSActivity is located switches to the main thread, then constructs the AMSTargetBroadcast instance by reflection, and calls the onReceive(xx) method. The onReceive(xx) method is exactly the processing logic we have rewritten after receiving the broadcast.
Therefore, the onReceive(xx) method is also executed in the main thread and cannot perform time-consuming operations, otherwise ANR is easy to occur
Dynamic registration
Dynamic registration is as follows:
IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(AMSTargetBroadcast.MY_ACTION); registerReceiver(new AMSTargetBroadcast(), intentFilter);
registerReceiver(xx) is finally called to:
#ContextImpl.java private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, IntentFilter filter, String broadcastPermission, Handler scheduler, Context context, int flags) { //AIDL implementation IIntentReceiver rd = null; if (receiver != null) { if (mPackageInfo != null && context != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } //Construct receiver callback rd = mPackageInfo.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(), true); } else { ... } } try { //Register with AMS final Intent intent = ActivityManager.getService().registerReceiver( mMainThread.getApplicationThread(), mBasePackageName, rd, filter, broadcastPermission, userId, flags); ... return intent; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
Before registering with AMS, the IIntentReceiver object is constructed. The interface is declared by AIDL, that is, a callback interface is registered with AMS. When AMS receives the request to send a broadcast, it is found that it is dynamically registered, so it calls the onReceive(xx) method in BroadcastReceiver by calling the performReceive(xx) method of IIntentReceiver interface, I don't seem to see switching to the main thread for execution? See the processing of IIntentReceiver performReceive(xx):
#LoadedApk.java public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { ... //Mmactivitythread.post switches to the main thread for execution if (intent == null || !mActivityThread.post(args.getRunnable())) { ... } } public final Runnable getRunnable() { return () -> { final BroadcastReceiver receiver = mReceiver; ... try { ... //BroadcastReceiver passed in at registration receiver.onReceive(mContext, intent); } catch (Exception e) { ... } }; }
Obviously, this is a switch to the main thread.
It can be seen that whether static registration or dynamic registration, the onReceive(xx) method is finally called back in the main thread.
The interaction diagram between Broadcast and AMS is as follows:
It can also be concluded from the above:
1. Every time you send a broadcast, you need to go through the IPC twice (requesting AMS/AMS callback). Therefore, if the broadcast is only sent / received in the same process, it is not necessary to use the broadcast. It is recommended to use the local broadcast: LocalBroadcastManager.
2. If the broadcast is statically registered, AMS will re create the BroadcastReceiver instance every time it calls back. Therefore, static registration is not recommended when the broadcast is sent / received frequently, and dynamic registration is recommended.
5. ContentProvider interacts with AMS
ContentProvider as its name implies: content provider.
Taking a typical mobile phone address book as an example, how can the address book be provided to other processes to use its data? According to the previous experience, the address book needs to expose an external interface. If an external program wants to use the address book, it has to get the exposed interface.
Next, let's see how to implement a custom ContentProvider:
Declare ContentProvider
#AndroidManifest.xml <provider android:authorities="com.fish.AMSTargetProvider" android:name=".ams.AMSTargetProvider"> </provider>
public class AMSTargetProvider extends ContentProvider { public static String AUTHORITY = "com.fish.AMSTargetProvider"; private static int MATCH_CODE = 1000; private static UriMatcher uriMatcher; ... //Rewrite the addition, deletion, modification and query methods to deal with specific logic }
With interfaces and processing logic, you need to show your ability. Of course, we don't need to complete this process. The system processes it automatically.
Publish ContentProvider to AMS
Remember that after the Application process is started, it will bind (register) iaapplicationthread to AMS. After successful binding, it will call back the Application process through the bindApplication(xx) of iaapplicationthread, and then switch to the main thread to execute handleBindApplication(xx). In this method, it will create an Application instance, and then process the Provider declared in AndroidManifest.xml.
The call stack is as follows:
ActivityThread.handleBindApplication-->ActivityThread.installContentProviders -->ActivityManager.getService().publishContentProviders(xx)
In ActivityThread.installContentProviders, ContentProvider is instantiated and its reference is saved to Map, and finally AMS publishContentProviders(xx) is passed to AMS.
#AMS public final void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers)
ContentProviderHolder implements the Parcelable interface, so it can be passed across processes, and it internally holds IContentProvider member variables. You may wonder here that IContentProvider cannot be passed across processes! In fact, IContentProvider is just an interface. Its specific implementation class is ContentProvider.Transport. Its declaration is as follows:
#ContentProvider.java class Transport extends ContentProviderNative { ... } #ContentProviderNative.java abstract public class ContentProviderNative extends Binder implements IContentProvider { ... }
Therefore, ContentProvider.Transport can be passed across processes.
Get ContentProvider
Relevant information about ContentProvider has been stored in AMS. When a process needs to use ContentProvider, take inserting data as an example. The use method is as follows:
{ ContentValues contentValues = new ContentValues(); getContentResolver().insert(uri, contentValues); }
When calling the insert(xx) method, the call stack is as follows:
ContentResolver.insert-->ContentResolver.acquireProvider-->ApplicationContentResolver.acquireProvider -->ActivityThread.acquireProvider-->ActivityThread.acquireExistingProvider -->ActivityManager.getService().getContentProvider(xx)
Among them, ActivityThread.acquireExistingProvider will first query whether there is a ContentProvider cached locally. If so, it will directly return (corresponding to the AMSTargetProvider instance in the same process as the caller). At this time, it will directly return the ContentProvider instance without querying AMS.
For different processes, you need to query the ContentProvider through AMS:
public final ContentProviderHolder getContentProvider( IApplicationThread caller, String callingPackage, String name, int userId, boolean stable)
If AMS has cached the ContentProvider before, return directly. Otherwise, check whether the target process is alive. If not, pull it up and take the ContentProvider.
One thing to note is:
The remote ContentProvider will also be cached, but releaseProvider(xx) will be called at the end of the insert()/delete()/query() method in ContentResolver to release the cache. Therefore, the remote ContentProvider is retrieved through AMS every time.
The interaction between ContentProvider and AMS is shown in the figure:
ContentProvider data change
When the ContentProvider data changes, it needs to notify the listener. If other processes want to listen for changes, they need to register the observer.
Registered observers are as follows:
Handler handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); } }; Uri uri = Uri.parse("content://" + AMSTargetProvider.AUTHORITY + "/ams"); getContentResolver().registerContentObserver(uri, false, new ContentObserver(handler) { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); } });
The call stack of registerContentObserver is as follows:
ContentResolver.registerContentObserver -->ContentResolver.registerContentObserver -->getContentService().registerContentObserver(xx)
ContentObserver itself has no cross process capability, so it needs to be wrapped:
#ContentObserver.java public IContentObserver getContentObserver() { synchronized (mLock) { if (mTransport == null) { mTransport = new Transport(this); } return mTransport; } } private static final class Transport extends IContentObserver.Stub { //Hold ContentObserver private ContentObserver mContentObserver; ... }
After encapsulating the observer, you need to pass it out.
Focus on getContentService():
#ContentResolver.java public static IContentService getContentService() { if (sContentService != null) { return sContentService; } //Get IBinder reference from ServiceManager IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME); sContentService = IContentService.Stub.asInterface(b); return sContentService; }
The services registered in ServiceManager are as follows:
#ContentService.java public final class ContentService extends IContentService.Stub { ... }
Therefore, call ContentService.registerContentObserver(xx) to pass ContentObserver.Transport to ContentService.
When the ContentProvider data changes, the following code is called:
#ContentResolver.java private void notifyChange() { Uri notifyUri = Uri.parse("content://" + AUTHORITY + "/ams"); getContext().getContentResolver().notifyChange(notifyUri, null); } //Finally called here public void notifyChange(@android.annotation.NonNull Uri uri, ContentObserver observer, boolean syncToNetwork, @UserIdInt int userHandle) { try { //Methods in ContentService getContentService().notifyChange( uri, observer == null ? null : observer.getContentObserver(), observer != null && observer.deliverSelfNotifications(), syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0, userHandle, mTargetSdkVersion, mContext.getPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
In fact, it still calls the ContentService, finds the previously registered ContentObserver. Transport, and then calls back Transport.onChange. Finally, it will call the ContentObserver.onChange() method just registered.
It should be noted that onChange() can choose to call back in the main / sub thread, and only need to pass in the Handler when registering.
The data change notice does not deal with AMS, but with ContentService, as shown in the figure below:
So far, the core of the communication between the four components and AMS has been analyzed. This paper does not analyze the processing details of the four components in AMS, but analyzes the communication process from the perspective of IPC. As long as you understand the communication process and communication framework, it is easy to find the corresponding processing details.
The test code includes examples of startService/bindService, dynamic / static registration broadcast, cross process writing of ContentProvider and so on:
This article is based on Android 10.0/8.0/7.0
Complete code demonstration If it's helpful, give github a compliment