One, the introduction
As for interprocess communication in Android, we know that it can probably be done in the following ways:
Bundle
: Communication between four componentsFile
: File sharingContentProvider
: Data is shared between applicationsAIDL
:Binder
mechanismMessager
Based on:AIDL
,Handler
implementationSocket
: to establish aC/S
Communication model
This article mainly explores AIDL and Socket implementation methods, and analyzes the cross-process communication mechanism of Binder based on the code generated by AIDL in daily use.
Complete code for this article: AndroidIPCDemo
Second, the use ofAIDL
andSocket
communicate
First, let’s talk about the communication model we will implement in a moment, which is roughly as follows:
Then look at the directory structure:
Look at IMyAidlInterface aidl, here in defining method is names need to be aware of when it is not same, package need to manually import, may be because the aidl file when parsing does not distinguish parameter types, cause I have been compile errors in setting method of the same name, do I have to find other problems, So be aware of this:
// IMyAidlInterface.aidl
package com.project.horselai.bindprogressguarddemo;
// Package that needs to be imported manually
import com.project.horselai.bindprogressguarddemo.IMyAidlInterfaceCallback;
import com.project.horselai.bindprogressguarddemo.MyMessage;
interface IMyAidlInterface {
void sendMessage(String msg);
void sendMessageObj(in MyMessage msg);
int getProcessId(a);
void registerCallback(IMyAidlInterfaceCallback callback);
void unregisterCallback(IMyAidlInterfaceCallback callback);
}
Copy the code
And then the IMyAidlInterfaceCallback. Aidl:
// IMyAidlInterfaceCallback.aidl
package com.project.horselai.bindprogressguarddemo;
interface IMyAidlInterfaceCallback {
void onValueCallback(int value);
}
Copy the code
Finally the MyMessage. Aidl:
// MyMessage.aidl
package com.project.horselai.bindprogressguarddemo;
parcelable MyMessage;
Copy the code
Other code is too long to post, see AndroidIPCDemo.
The demo diagram is as follows. Let’s run and see.
One phenomenon is that the ServiceConnection does not break after the unbindService call, so it can be sent and received if the message is sent again.
Third, fromAIDL
Generate class source code to understandBinder
mechanism
1. Let’s start with some theoretical knowledge about IBinder
Official text: IBinder
IBinder, as the basic interface for remote objects, is the core of a lightweight remote call mechanism designed for high-performance cross-process and in-process invocation. IBinder describes an abstract protocol for interacting with remote objects, which should be inherited from Binder and implemented directly.
The key API for IBinder is Transact (), which is used in conjunction with binder.ontranscat () to send a request to an IBinder object when the transcat() method is invoked and received in binder.ontranscat (), Transcat() is executed synchronously. After transcat() is executed, transcat() will wait for the other binder.ontranscat () method to return. This behavior is inevitable when executed in the same process, but when executed between different processes, The underlying IPC mechanism also ensures the same behavior.
The transact() method sends data of type Parcel. A Parcel is a generic data buffer that contains meta-data describing what it is hosting, which is used to manage IBinder object references in the buffered data. These references can therefore be saved as buffered data to be passed to other processes. This ensures that the IBinder can be written to a Parcel and sent to other processes. If other processes send the same IBinder reference back to the source process, the source process receives the same IBinder object. This feature enables IBinder/Binder objects to act as unique identifiers between processes (as server tokens or for other purposes).
The system maintains A transaction thread pool for each running process. The threads in the thread pool are used to distribute all IPC transactions from other processes. For example, when process A conducts IPC with process B (A is the sending process), A calls Transact () to send the transaction to process B. The called thread in A is blocked in Transact (), and if the available thread pool thread in process B receives A transaction from A, the binder.ontranscat () of the target object (process A) is called and A Parcel is replied. Upon receiving the reply from process B, the thread executing transact() in process A terminates the block and continues with the other logic.
Binder systems also support cross-process recursion, for example, if process A executes A transaction to process B, and then process B executes ibinder.transact () implemented by process A while processing the received transaction. The thread in process A that is waiting for the completion of the original transaction will be used to perform the binder.ontranscat () reply for the object called by process B. This behavior ensures that the recursive mechanism behaves identically when calling Binder objects remotely and locally.
You can determine whether a remote object is available in the following three ways:
- When calling a process that does not exist
IBinder.transact()
Thrown whenRemoteException
The exception; - call
pingBinder()
returnfalse
“Indicates that the remote process does not exist. - use
linkToDeath()
Approach toIBinder
Sign up for aIBinder.DeathRecipient
When the host process is killed, it will be notified through this listener.
2. AIDL generation class source code analysis
Let’s look at the structure of the generated class:
The IMyAidlInterface interface is the direct code generation for defining the AIDL interface, while imyaidlInterface.stub is the abstract class that implements the IMyAidlInterface interface and implements the onTranscat() method. But it does not have specific implementation IMyAidlInterface method, but the implementation of this part to the IMyAidlInterface. The Stub. The Proxy.
OK, let’s break it down. Start by locating Stub#asInterface, you can see that it is mainly responsible for distinguishing between local and cross-process communication.
public static com.project.horselai.bindprogressguarddemo.IMyAidlInterface asInterface(android.os.IBinder obj) {
// 1. Check whether the IBinder object exists locally
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if(((iin ! =null) && (iin instanceof com.project.horselai.bindprogressguarddemo.IMyAidlInterface))) {
// If it is local communication, local communication will take place later
return ((com.project.horselai.bindprogressguarddemo.IMyAidlInterface) iin);
}
// 2. Otherwise, use this object for remote communication later
return new com.project.horselai.bindprogressguarddemo.IMyAidlInterface.Stub.Proxy(obj);
}
Copy the code
Looking at the Stub#onTranscat method, each parameter does the following
- Code:Identifies the action to be performed as a slave
FIRST_CALL_TRANSACTION
toLAST_CALL_TRANSACTION
Between the numbers. - Data:
transcat()
Data sent by the caller. - Reply:Used to
transcat()
The caller writes the reply data. - Flags:If it is
0
, stands for an ordinaryRPC
If it isFLAG_ONEWAY
It means oneone-way
The type ofRPC
. - Return:return
true
The request is successful and returnsfalse
You do not understand the transaction code (code
).
Based on the theory above, we already know that onTransact() in process A is called by process B to call back data remotely. Here are two symbolic methods to explain what happens in onTransact() :
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
// For remote write requests
case TRANSACTION_sendMessage: {
data.enforceInterface(descriptor);
java.lang.String _arg0;
// 1. Read request data from process A's remote request packet
_arg0 = data.readString();
// 2. Execute sendMessage in process B to write data from process A
this.sendMessage(_arg0);
reply.writeNoException();
return true;
}
// For remote read requests
case TRANSACTION_getProcessId: {
data.enforceInterface(descriptor);
Run getProcessId() in process B to read the data to be used as response data
int _result = this.getProcessId();
// 2. Write the read response data to process A's reply
reply.writeNoException();
reply.writeInt(_result);
return true;
}
// ...
default: {
return super.onTransact(code, data, reply, flags); }}}Copy the code
SendMessage (_arg0) and this.getProcessid () above, such as why this.sendMessage(_arg0) is executed in TRANSACTION_sendMessage, Isn’t that a loop? No. Why? Because TRANSACTION_sendMessage determines the method type code from process A, and after parsing the request parameter data from process A, process B calls its own sendMessage(_arg0) method to save the data to its own memory. SendMessage (_arg0) has its own implementation, as follows in process B:
IMyAidlInterface.Stub myAidlInterface = new IMyAidlInterface.Stub() {
@Override
public void sendMessage(String msg) throws RemoteException {
Log.i(TAG, "sendMessage: " + msg);
}
@Override
public int getProcessId(a) throws RemoteException {
returnProcess.myPid(); }};Copy the code
It makes a lot of sense here.
Proxy#sendMessage and Proxy#getProcessId are used to explain how client process A sends requests to remote server process B:
@Override
public void sendMessage(java.lang.String msg) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain(); // Request parameters
android.os.Parcel _reply = android.os.Parcel.obtain(); // Response data
try {
// 1. Encapsulate the remote request parameters
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(msg);
// 2. Remote requests are executed with Binder and the final response data is wrapped in _reply
mRemote.transact(Stub.TRANSACTION_sendMessage, _data, _reply, 0);
// 3. No data needs to be returned
_reply.readException();
} finally{ _reply.recycle(); _data.recycle(); }}@Override
public int getProcessId(a) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain(); // Request parameters
android.os.Parcel _reply = android.os.Parcel.obtain(); // Response data
int _result;
try {
// 1. If no parameter is specified, only identifiers are written
_data.writeInterfaceToken(DESCRIPTOR);
// 2. Remote requests are executed with Binder and the final response data is wrapped in _reply
mRemote.transact(Stub.TRANSACTION_getProcessId, _data, _reply, 0);
// 3. Read response data after transact blocking ends
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
Copy the code
As you can see, _reply has always been the same, created by process A and sent to process B, which writes the processed response data to _reply and finally calls back to process A via the onTranscat method, thus completing an RPC.
In general, the execution process of the whole process is as follows:
Four,Messenger
Use and source code analysis
1. Use
Use the following in a Service process:
public class MessengerRemoteService extends Service {
private static final String TAG = "MessengerRemoteService";
private Messenger mMessenger;
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
@Override
public void onCreate(a) {
super.onCreate();
// 3. Use Messenger to communicate between processes
mMessenger = new Messenger(new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.i(TAG, "handleMessage: " + msg);
Log.i(TAG, "handleMessage data: " + msg.getData().get("msg"));
return true; }})); }}Copy the code
Then establish the service connection in the Activity as follows:
// for Messenger
private Messenger mMessenger;
ServiceConnection mServiceConnection3 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected3: ");
mMessenger = new Messenger(service);
btnBindRemote.setEnabled(false);
mIsBond = true;
Toast.makeText(MainActivity.this."service bond 3!", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected 3: ");
mIsBond = false;
btnBindRemote.setEnabled(true); }};Copy the code
After binding, the following information is sent:
if(mMessenger ! =null){
Message message = new Message();
Bundle bundle = new Bundle();
bundle.putString("msg"."message clicked from Main ..");
message.what = 122;
message.setData(bundle);
try {
mMessenger.send( message);
} catch(RemoteException e) { e.printStackTrace(); }}Copy the code
The following output is displayed:
Based on the above use, you can see that the communication for Messenger is one-way. If you want two-way communication, you need to create A Messenger and Handler on process A as the client, and then send the response message in process B.
To enable two-way communication, we can modify the above code as follows, where Messenger in MessengerRemoteService can be modified as follows:
mMessenger = new Messenger(new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.i(TAG, "handleMessage: " + msg);
Log.i(TAG, "handleMessage data: " + msg.getData().get("msg"));
Message message = Message.obtain();
message.replyTo = mMessenger;
Bundle bundle = new Bundle();
bundle.putString("msg"."MSG from MessengerRemoteService..");
message.setData(bundle);
message.what = 124;
try {
// Notice here
msg.replyTo.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
return true; }}));Copy the code
Note msg.replyto.send (message) above, where msg.replyto is a Messenger that sends this message. You can change it like this in your Activity:
/ / onCreate
mClientMessenger = new Messenger(mHandler);
// ...
Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 2) {
Toast.makeText(MainActivity.this."" + msg.obj, Toast.LENGTH_SHORT).show();
return true;
}else if (msg.what == 124){
Toast.makeText(MainActivity.this, msg.getData().getString("msg"), Toast.LENGTH_SHORT).show();
Log.i(TAG, "handleMessage: " + msg.getData().getString("msg"));
Log.i(TAG, "handleMessage: ");
return true;
}
textView.setText(String.valueOf(msg.obj));
return true; }});Copy the code
Finally, when the Activity receives a message, it pops up the received message, as shown below:
The whole two-way communication process is as follows:
2. Implementation principle of Messenger
The Messenger layer is simply wrapped with Binder, specifically AIDL, so it doesn’t affect the life cycle of a process, although the connection is broken when a process is destroyed.
Here is a brief look at some of its source code:
public final class Messenger implements Parcelable {
private final IMessenger mTarget;
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
// ...
}
Copy the code
IMessenger is an AIDL interface, as follows:
package android.os;
import android.os.Message;
/ * *@hide* /
oneway interface IMessenger {
void send(in Message msg);
}
Copy the code
It’s pretty easy to understand this stuff with the previous knowledge.
Five, the summary
This paper mainly describes two ways of Android interprocess communication: AIDL and Socket. The Socket method is not described and analyzed too much in this paper, because the use of Socket communication is relatively basic, and its implementation process is relatively easy to understand, so it is briefly covered. See AndroidIPCDemo for detailed implementation source code. This paper focuses on the operation mechanism of Binder from the perspective of AIDL source code, and briefly introduces the use and implementation of Messenger.
OK, the level is limited, welcome rational correction.