Those of you who have used AIDL have probably seen the following:
interface IInterface {
void foo0(in int input);
void foo1(out IDTParcel parcel);
void foo2(inout IDTParcel parcel);
}
Copy the code
Have you ever wondered what “in/out/inout” means?
directional tag
Go to the official website and find only a little information:
All non-primitive parameters require a directional tag indicating which way the data goes. Either
in
,out
, orinout
(see the example below).Primitives,
String
,IBinder
, and AIDL-generated interfaces arein
by default, and cannot be otherwise.Caution: You should limit the direction to what is truly needed, because marshalling parameters is expensive.
Well, in/out/inout is directional tag. Which way the data goes? What do you mean? From concept to interpretation is not human language; Unsatisfied, you continue to search related blogs……
Directional Tag is not what?
Before we get to what it is, let’s talk about what Directional Tag is not:
If you search for aidl in out, a lot of articles come up, many of which conclude something like this:
Directional tags in AIDL represent the flow of data in cross-process communication
In indicates that data can only be transferred from the client to the serverOut indicates that data can only be transferred from the server to the clientInout indicates that data can be bidirectional between the server and the client.
The same goes for Stack Overflow:
Here it goes,
- Its only a directional tag indicating which way the data goes.
in – object is transferred from client to service only used for inputsout – object is transferred from client to service only used for outputs.inout – object is transferred from client to service used for both inputs and outputs.
(In order to avoid some “efficiency” readers read only keywords, the article will be wrong conclusions are underlined)
The above conclusion sounds reasonable, but you may find a problem: the interface callback scenario will not be implemented!
In aidl, if the client registers a Callback with the server (as shown in the following code), the server will Callback the client in certain scenarios. In this case, the data flow is server => client. An int directional tag can only be in, and in can only support data transfer from client to server
//aidl file
interface ICallback {
void onResult(int result);
}
//aidl file
interface IController {
void registerCallback(ICallback callback);
}
Copy the code
However, if you have used AIDL, you can see that the interface callback works (verify the demo address below), otherwise we would have noticed the exception in this high-frequency usage scenario.
D/directional tag: server register callback
D/directional tag: client onResult: 1
Copy the code
The conclusion is in conflict with the facts. There must be something wrong with the assumption!
One could be forgiven for coming to this wrong conclusion, after all, for most developers AIDL “hears a lot and uses a lot”, the first person who wrote the Demo verification was in a particular situation, and based on that particular situation the conclusion would be wrong. In fact, that’s what inspired me to write this post, because I’m angry and proud that the most viewed blogs on the web (almost) all get it wrong
So what exactly is Directional Tag? Let’s verify it step by step:
The source code below
To figure out what’s going on, there’s no secret in the source code.
In order to avoid the confusion of some students, here is a bit of pre-knowledge about AIDL:
As a cross-process communication scheme, AIDL relies on Binder at the bottom level. When cross-process communication is implemented, methods defined in AIDL will be called and parameter data of caller (caller only) will be copied to Callee (receiver only). The same method of another proxy object is then called in the Callee process. The logic is encapsulated by the Binder framework. The user looks and feels like calling a method directly on an object in the other process.
The AIDL file generates two important implementation classes when compiled:
Stub callee, when invoked, indirectly invokes Local Binder methods through stub.ontransact (code, data, reply, flag).
When the Proxy caller calls AIDL method, remote. Transact (code, _data, _reply, flag) is eventually called by Proxy, and corresponding remote methods are then called by Binder mechanism.
The onTransact() and Transact() methods above are both Binder defined methods, and the lower-level cross-process logic implemented by Binder mechanisms is not the focus of this article.
With that in mind, let’s write an AIDL file and see what the corresponding method does. See the full code here.
//aidl file: State
parcelable State;
Copy the code
//aidl file: IController
interface IController {
int transIn(in State state);
int transOut(out State state);
int transInOut(inout State state);
}
Copy the code
AIDL file IController compiled with the following key code:
in
//Proxy(caller)
public int transIn(com.littlefourth.aidl.State state) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
if((state ! =null)) {
_data.writeInt(1);
// Write state data to _data
state.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
// Transfer data and call Callee transIn()
mRemote.transact(Stub.TRANSACTION_transIn, _data, _reply, 0);
// Read the return value
_result = _reply.readInt();
return _result;
}
//Stub(callee)
case TRANSACTION_transIn: {
com.littlefourth.aidl.State _arg0;
if ((0! = data.readInt())) {// Create a State object based on the data passed in
_arg0 = com.littlefourth.aidl.State.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
// Call callee transIn()
int _result = this.transIn(_arg0);
// Write the return value
reply.writeInt(_result);
return true;
}
Copy the code
Output log:
caller value before transIn(): 1
callee transIn(), value: 1
callee set value to 2
caller value after transIn(): 1
Copy the code
out
//Proxy(caller)
public int transOut(com.littlefourth.aidl.State state) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
// No state data was written to _data
mRemote.transact(Stub.TRANSACTION_transOut, _data, _reply, 0);
// Read the return value
_result = _reply.readInt();
if ((0! = _reply.readInt())) {// Read callee's updated state data
state.readFromParcel(_reply);
}
return _result;
}
//Stub(callee)
case TRANSACTION_transOut: {
com.littlefourth.aidl.State _arg0;
// Create a new State object directly
_arg0 = new com.littlefourth.aidl.State();
// Call callee transOut()
int _result = this.transOut(_arg0);
// Write the return value
reply.writeInt(_result);
if((_arg0 ! =null)) {
// Write the flag bit. Caller checks whether state data is written according to the data
reply.writeInt(1);
// Write state data (full data is written regardless of whether the data is updated)
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
Copy the code
Log output:
caller value before transOut(): 1
callee transOut(), value: -1000
callee set value to 2
read new value 2
caller value after transOut(): 2
Copy the code
inout
//Proxy(caller)
public int transInOut(com.littlefourth.aidl.State state) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
if((state ! =null)) {
_data.writeInt(1);
// Write state data to _data
state.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
// Transfer data and call Callee transInOut()
mRemote.transact(Stub.TRANSACTION_transInOut, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
if ((0! = _reply.readInt())) {// Read callee's updated state data
state.readFromParcel(_reply);
}
return _result;
}
//Stub(callee)
case TRANSACTION_transInOut: {
com.littlefourth.aidl.State _arg0;
if ((0! = data.readInt())) {// Create a State object based on data
_arg0 = com.littlefourth.aidl.State.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
// Call callee transInOut()
int _result = this.transInOut(_arg0);
// Write the return value
reply.writeInt(_result);
if((_arg0 ! =null)) {
// Write the flag bit. Caller checks whether state data is written according to the data
reply.writeInt(1);
// Write state data (full data is written regardless of whether the data is updated)
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
Copy the code
Log output:
caller value before transInOut(): 1
callee transInOut(), value: 1
callee set value to 2
read new value 2
caller value after transInOut(): 2
Copy the code
What exactly is Directional Tag?
According to the source code and demo verification results, we can draw a conclusion:
Directional Tag | Desc |
---|---|
in | Data is transferred from the Caller to the Callee. After the callee calls the caller, the callee does not write the data back to the caller. |
out | Caller data will not be passed to callee (because there is no data written to callee). When callee calls callee, the data will be written back to caller (no matter whether the data is updated or not). |
inout | Data is transferred from caller to Callee. When callee calls caller, the data is written back to caller (no matter whether the data is updated or not). |
As a client and a server, caller and callee are both clients and servers. Since client and server can call each other, the compiled code of AIDL files is the same, and the logic (AIDL layer) that client and server execute when they are caller or callee is the same. So you can’t say that in/out/inout explicitly indicates the direction from client to server (or vice versa).
What does this out do?
At this point, you’ve probably figured out what Directional Tag is, but there’s a question:
What does out mean? Caller doesn’t even send data? And read the data callee wrote back?
This question is very reasonable, after all, many friends who have used AIDL have never noticed the difference here, and then fill in an IN according to the prompt when the partial compilation error occurs, and find the logic is quite normal, and then end without any problems.
Before we answer that question, there’s another one:
> why directional tag?
You don’t need a Directional Tag when calling a method in the same process, so why would you need one in a cross-process scenario?
Within the same process, changes to object properties are directly reflected in subsequent contexts because they access the same memory address. Binder’s cross-process mechanism (as you can see from the source code above) copies data from Caller to Callee with each call, so that callee’s changes to data do not (automatically) show up in Caller’s data. This cross-process data transfer process is called marshaling. Marshling is a more expensive process than serialization.
Caution: You should limit the direction to what is truly needed, because marshalling parameters is expensive.
So back to the question, why directional Tag? Because cross-process communication does not synchronize data updates by default, this would require that all of the marshaling procedures be treated as they were when directional Tag was inout. The concept of using Directional Tags allows developers to select the tag that best fits their current scenario.
Word-wrap: break-word! Important; “> < p style =” max-width: 100%;
If you use directional Tag, you’ll see a compile-time error when using out/inout:
‘out int integer’ can only be an in parameter
Why is it designed this way?
Because there’s no point!
When we execute a method in Java, an argument modification to the primitive type in the method does not change the external variable, because it is a copy, and the String type does not change the result for a different reason.
So in this scenario, we do not expect changes to the (basic data type) parameters in the method to be reflected in external variables. Using in (and only IN) at this point will satisfy our needs.
In fact, we don’t have to worry about that much here, and we’ll just use in by default.
Word-wrap: break-word! Important; “>
After figuring out out, my first thought was why not return a value (after all, callee writes data to Reply)?
After careful consideration of some details, I found the benefits of such a design:
- Using the return value requires a new object to be created, which is expensive.
- If you do not create a new object, you can only use the original object. In this case, the original object may not want to change, or the change logic needs to be customized and cannot be supported.
- Using return values in scenarios with multiple OUT parameters is cumbersome and requires another layer of objects to be wrapped.
ArrayCopy (SRC, srcPos, dest, destPos, int Length) does not return a new array. Instead, it passes in the destination data as an argument. On the one hand, it is not wise to create arrays frequently at the lowest level. On the other hand, a business requirement might be to add data incrementally, a scenario where a new array needs to be created every time and old data needs to be moved around would be a performance disaster.
The problems listed above can be solved nicely using the OUT parameter; In addition, if the return value indicates the operating status, and also need to return the data according to the state, using the out also let more clear logic, data update operation is encapsulated in the Parcelable readFromParcel (), facilitate the custom data update details.
public void readFromParcel(Parcel reply) {
int temp = reply.readInt();
Log.d(T, "read new value " + temp);
value = temp;
}
Copy the code
When you dig deeper, it’s all about the details, but when you practice it, only Parcel and collection types can use out and inout, and you need to display tags. As you can imagine, designers take great pains for ease of use and performance.
Back to the question: What scenario is appropriate for out?
Caller needs data processed by Callee, and has many parameters, complex data structures, or incremental updates.
Back to the question in this section: What’s the use of out?
The purpose of Out is to give you the best performance solution in the above scenario!
Honestly, a scene like this… I haven’t met one yet, I hope you can!