“This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!”
background
In bytedance developer ecosystem projects, there is a lot of IPC communication. To avoid having to write AIDL every time you write an IPC call, the complexity of corrections and the speed of compilation is a burden for users. In order to make it as easy for developers to use IPC as to call local objects, an AIDL-based IPC communication framework was developed.
features
- Replace the AIDL interface with a normal Java interface
- Generate an IPC implementation of the remote service interface like Retrofit
- You can extend CallAdapter yourself to support Rxjava in a retrofit-like manner
- Supported Call Adapters: Call
- Support the remote service Callback mechanism IPC Callback
- All AIDL data types are supported
- Support AIDL for all data orientation tags (default: in) : in, Out, inOUT
- Support the AIDL Oneway keyword
- Support for in-process calls when demoted to local calls
- When IPC Callback is supported, a Callback notification is made to the void {methodName}() method annotated with @connecterror if a Binder dies
- Automatic registration and deregistration of IPC Callback objects is supported
BdpIPC supports all AIDL data types
- All primitive types in the Java language (e.g. Int, long, char, Boolean, etc.)
- String
- CharSequence
- Parcelable
- List (All elements in the List must be supported data types in this List)
- Map (All elements in the Map must be supported data types in this list)
- JSONObject and JSONArrayType are also supported
- You don’t need to implement Parcelable serialization for types that are already supported, but you do need to implement Parcelable serialization for those that are not explicitly supported
- If there are additional requirements, you can modify the source code to expand
use
In the client App, create the BdpIPC object through the Builder, and generate an IPC implementation of the remote interface of IRemoteService through the create() method
.packageName(*REMOTE_SERVICE_PKG*) .action(*REMOTE_SERVICE_ACTION*) //.usedProvider(getPackageName() + "." + RemoteProvider.URI_SUFFIX) // Specify the callback executor by yourself / / addCallAdapterFactory (OriginalCallAdapterFactory. Create (callbackExecutor)) / / Basic MainThread callback / / Settings specified Callback < T > () in the thread. AddCallAdapterFactory (OriginalCallAdapterFactory. * the create * (new Executor () {@ Override public void execute(Runnable command) { BdpThreadUtil.*runOnWorkThread*(command); }})) // Set the thread where the request is sent. Dispatcher(new IDispatcher() {@override public void enqueue(Runnable Task) { BdpThreadUtil.*runOnWorkThread*(task); } }) .addInterceptor(new CacheInterceptor()) // Basic //.addCallAdapterFactory(OriginalCallAdapterFactory.create()) .build();Copy the code
Now all the methods in the mRemoteService object are IPC methods and can be called directly
int pid = mRemoteService.getPid(); MRemoteService. BasicTypes (1, 2 l, true, 3.0 f, 4.0 d, "STR");Copy the code
Use the advanced
Call Adapters
In the client App, you can copy and modify the remote service interface, wrapping the return value of the method
@RemoteInterface
public interface IRemoteService {
Call<Integer> getPid();
Call<Void> basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
Copy the code
Register the corresponding Call Adapter Factory in bdPIPC. Builder, and the rest of the steps are basically the same as Retrofit.
new BdpIPC.Builder(this)
...
.addCallAdapterFactory(OriginalCallAdapterFactory.create()) // Basic
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // RxJava2
.build();
Copy the code
Call the IPC
Call<Integer> call = mRemoteTask.remoteCalculate(10, 20); // Synchronous Request // int result = call.execute(); // Log.d("IPCDemoActivity", "remoteCalculate() result:" + result); // Async Request call.enqueue(new Callback<Integer>() { @Override public void onResponse(Call<Integer> call, Integer response) { Toast.makeText(IPCDemoActivity.this, "remoteCalculate() onResponse: " + response, Toast.LENGTH_SHORT).show(); } @Override public void onFailure(Call<Integer> call, Throwable t) { Toast.makeText(IPCDemoActivity.this, "remoteCalculate() failure: " + t.getMessage(), Toast.LENGTH_SHORT).show(); }});Copy the code
Handle remote service interface callbacks using @callback
Modify the remote service callback interface IRemoteCallback with the @remoteInterface annotation
@RemoteInterface
public interface IRemoteCallback {
void onValueChange(int value);
}
Copy the code
The Callback parameter is decorated with the @callback annotation in the remote method
void registerCallback(@Callback IRemoteCallback callback);
Copy the code
Callback objects are registered internally through dynamic proxies, and automatic deregistration is supported.
Specifies the data orientation TAG
You can specify @in, @Out, or @inout annotations for remote method arguments, which mark the flow of data through the underlying Binder, as used In AIDL
void directionalMethod(@In KeyEvent event, @Out int[] arr, @Inout Rect rect);
Copy the code
Note that all non-primitive types must specify the data orientation tag: '@in', '@out', or '@inout', to mark the direction of the data. Primitive types default to '@in' and cannot specify other values. Parcelable arguments that use '@out' or '@inout' must implement the 'SuperParcelable' interface, otherwise you must manually add this method 'public void readFromParcel(Parcel in)'.Copy the code
Use the @oneway annotation
You can use the @oneway annotation on remote methods, which will modify the behavior of remote method calls. When used, the remote method call is not blocked; it simply sends data and returns it immediately, as in AIDL
@OneWay
void onewayMethod(String msg);
Copy the code
Use the @Connecterror annotation
You can use the @Connecterror annotation on a method of the remote Callback object, which will make the remote method listen
Callbacks for connections broken due to Binder’s death
Using the @ RemoteInterface (target = IpcServiceInterfaceImpl. Class)
You can use this to achieve automatic registration on the Server side.
The idea is to generate an instance of the object by reflecting the name of the class (reflecting on the first call)
Here are some examples:
Specify the thread of execution (Callback)<T>
– Callback after execution and Disptacher- thread that sent the request)
Currently, you cannot specify the thread in which the remote implementation class resides. Binder thread is the default
mBdpIPC = new BdpIPC.Builder(this) .packageName(REMOTE_SERVICE_PKG) .action(REMOTE_SERVICE_ACTION) //.usedProvider(getPackageName() + "." + RemoteProvider.URI_SUFFIX) // Specify the callback executor by yourself / / addCallAdapterFactory (OriginalCallAdapterFactory. Create (callbackExecutor)) / / Basic MainThread callback / / Settings specified Callback < T > () in the thread. AddCallAdapterFactory (OriginalCallAdapterFactory. Create (new Executor () {@ Override public void execute(Runnable command) { BdpThreadUtil.runOnWorkThread(command); }})) // Set the thread where the request is sent. Dispatcher(new IDispatcher() {@override public void enqueue(Runnable Task) { BdpThreadUtil.runOnWorkThread(task); } }) .addInterceptor(new CacheInterceptor()) // Basic //.addCallAdapterFactory(OriginalCallAdapterFactory.create()) .build();Copy the code
performance
Because of the use of dynamic proxies, overall, the performance penalty is still around 20% compared to native calls. However, in contrast, the cost of using Parcelable is reduced, and the transfer of data using Parcelable is generally less costly than other third-party frameworks (using JSON strings and converting to objects), and the scalability and complexity of the code is much lower
If 1000 simple IPC communications are repeated 3 times 1000 IPC, the average value is taken
Online use
BdpIPC is currently in steady use in developer Ecology projects. If there is a scenario requirements of the business, welcome to discuss.
Design principle
- Dynamic proxies cause the final implementation of a normal interface to follow a binder’s invocation
- References to Okhttp’s Intercept mode allow for rapid scaling of interception, such as adding in-memory caching, or other uniform processing
- Reference to Retrofit’s CallAdapter makes it possible to support the Call mode, even Rxjava
- – phantom references (virtual references) are referenced so that object reclamation across processes can be supported synchronously (manually, of course)
- This framework is not a complete re-creation, absorbing the design approach of many open source projects, thanks to open source
Flow chart of the call
The whole process
1. Initialization process I. The local object obtains the remote Binder object (ITransfer) in Service mode and registers the Callback Binder (ICallback) object ii with the remote Binder object (ITransfer). The remote Service registers the ServiceImplObject(the remote Service interface implementation object) using BdpIPCBinderImpl(the remote object that holds an Invoker object internally).Iii. The registered ServiceImplObject turns into MethodInvoker (Key based on class name and method name) and is stored in the remote Invoker object (automatic registration is added later, no manual registration is required).
- The local object calls the method localCallback(Callback Callback). Note: this method is user-owned, not in the library
- The method after the hook is called by the local remote proxy object (dynamic proxy). ,BdpIPC registers the callback object (resolved to MethodInvoker object) with the local Invoker object and stores it inside the Invoker
- Hook method is parsed into Request object and wrapped into RemoteCall object for Request processing. A Request is sent with binder
5. Remote Invoker object discovery is a method with callback interface parameters. When the method is implemented, the dynamic proxy object implements the implementation of the interface object, and BdpCallbackGc is used for GC listening
6. The remote object’s interface is called and a Callback request is sent using the previously registered Callback BInder (locally registered to the remote Callback BInder (ICallback)). Wrap the Request as the previous Request logic and send the Request with the binder inside RemoteCall7. The CallbackBinder (ICallback) object generated by the local process receives the Response Callback (Request Request), and uses the local Invoker to process the Request. Find a MethodInvoker with the corresponding key in the container that was previously registered with the local Invoker, and perform the callback request handling 8. The local process fires the callback callback. 9. If the remote BdpCallbackGc is listening to an object that needs to be Gc, call the previously registered Callback BInder (locally registered to the remote Callback BInder (ICallback)) and send a Gc process. The local Invoker receives a MethodInvoker object that needs to be processed by GC to prevent the local Invoker object from holding too many temporary callback objects
Important Class Description
BdpCallbackGc The remote process listens for temporary callback objects, and once there is a GC, it triggers a message to the local process to explicitly manipulate the Calllback response objects in the Invoker
BdpIPC does the generation of local remote proxy objects, the acquisition of remote Binder objects and the registration of local callback objects
Invoker the actual method implementor, both local and remote
At remote: Internally holds the method responder for each remote service interface implementation object (Remote service interface implementation object parsing MethodInvoker)
Local: internally holds the method responder of the callback object (the MethodInvoker generated by the callback object resolution)
The true implementor of the proxy object generated by RemoteCall BdpIPC, the internally constructed Request object uses ICallback to make the Request
ITransfer The Binder object that executes the request
ICallback remote obtains the binder object from the local process that was registered when the local process was initialized, and uses the binder object to perform the Callback