Practical Android technology: an in-depth understanding of Android RPC and AIDL

Source: Internet
Author: User
Tags mremote
Understanding Adil

Aidl is an interface description file used to implement RPC on the Android platform. during compilation, aapt will automatically generate interfaces and objects for IPC according to the rules. As a user, you only need to: 1. implement interfaces on the server service; 2. obtain the interface object when bindservice and onserviceconnected are on the client. The interfaces here are all the interfaces described in aidl, and other details are in the source code file of the same name generated by aidl.

Unveil

Let's take a look at the source code file generated in the gen folder with the same name as the aidl file. This file looks very complicated and can be used to understand the essence of aidl. There is an interface in it, the method is defined in the aidl file. There is also a stub, which is the base class we want to implement on the Service side. There is also a proxy. The relationship between them is this.

View the source code file from the user's perspective: the outermost layer of the file is an interface with the same name as aidl. Here is printerinterface, which contains a print method that accepts the string. Printerinterface. stub. asinterface is used on the client. You can see that this method returns an object that implements the printerinterface interface. In addition, the server enables the service to implement printerinterface. stub, which is actually implemented by printerinterface, because stub also inherits from printerinterface. Therefore, the sequence is as follows: the client obtains an object that implements the printerinterface interface, and the server must implement this interface.
But it looks a little messy. We need to take off our clothes! (It's a hot day. You have to take it off again !)

Remove coat

Because the files generated by aidl cannot be compiled, we create an identical file to facilitate editing and modification. When we get the ibinder object, we add log statements to the methods related to stub and proxy to track program behavior:
The following conclusions can be obtained through tracking and debugging:
When both parties of communication are in the same process, the object returned by onServiceConnected is the object returned by Service. onBind (). However, if it is a cross-process, it returns a BinderProxy object. Therefore, we can see that the class generated by AIDL has the following judgment:

if (((iin != null) && (iin instanceof MyPrinterInterface))) {Log.e(TAG, "we have local interface, so we use it");        return ((MyPrinterInterface) iin);}

This is actually to determine whether the communication is in the same process or across processes, because the object returned by the same process is Service. the object returned by onBind (), and this object must implement the interface (otherwise, it will be a problem !). Therefore, if it is only in the same process, it does not go through the Binder process IPC, but directly returns the object provided by the Service and calls its method directly, therefore, there is no restriction on the Parcelable object!
That is to say, in the same process, AIDL actually becomes like this:
In other words, it directly returns the Service. the onBind () object is actually the same as the first method mentioned above: the method for directly implementing the Binder object is the same, and other code is redundant. Therefore, as previously suggested, if it is only in the same process, you can directly use the Binder, and there is no need to create an AIDL file.
In different processes, the client Stub. asInterface returns a Stub. Proxy object and calls the print method on it. The server will only execute the Stub. onTransact () method, and then tune it to the print method of the Service.
When cross-process operations are performed, you must use the IPC-related methods and mechanisms of the Binder object. The client needs to implement the Binder. transact () method to execute a remote method, which is used by the client. The server needs to implement the Binder. onTransact () method to respond to the client's request. For upper-layer users, the information (parameters, identifiers, and switches) of functions are sent out using transact (). The rest is the work of the Binder, and there are a lot of internal details, however, the onTransact () method of the Binder on the server end will be called. The function identifier is identified here, and then the specific implementation is called, and the return value is returned. This completes the call of an IPC function.
When cross-process execution is performed, only the following codes are required by each other, And irrelevant codes are removed:
Server Service:

public class MyServerService extends Service {private static final String TAG = "MyServerService";private Handler mHandler = new Handler();@Overridepublic IBinder onBind(Intent intent) {return mBinder;}private MyPrinterInterfaceStub mBinder = new MyPrinterInterfaceStub() {@Overridepublic void print(String msg) throws RemoteException {MyServerService.this.print(msg);}};    public void print(String msg) {    try {    Log.e(TAG, "Preparing printer...");Thread.sleep(1000);Log.e(TAG, "Connecting printer...");Thread.sleep(1000);Log.e(TAG, "Printing.... " + msg);Thread.sleep(1000);Log.e(TAG, "Done");} catch (InterruptedException e) {}    mHandler.post(new Runnable() {    @Override    public void run() {    Toast.makeText(MyServerService.this, "MyServerService Printing is done.", Toast.LENGTH_LONG).show();    }    });    }}

Serer side interface definition:

public interface MyPrinterInterface extends android.os.IInterface {public void print(String msg) throws android.os.RemoteException;}abstract class MyPrinterInterfaceStub extends Binder implements MyPrinterInterface {private static final String DESCRIPTOR = "MyPrinterInterface";private static final String TAG = "MyPrinterInterfaceStub";public MyPrinterInterfaceStub() {attachInterface(this, DESCRIPTOR);}@Overridepublic IBinder asBinder() {return this;}@Overridepublic boolean onTransact(int code, Parcel data, Parcel reply, int flags)throws android.os.RemoteException {Log.e(TAG, "onTransact, code is " + code);switch (code) {case INTERFACE_TRANSACTION: {Log.e(TAG, "onTransact, code is " + code + ", when this happens");reply.writeString(DESCRIPTOR);return true;}case TRANSACTION_print: {data.enforceInterface(DESCRIPTOR);String _arg0;_arg0 = data.readString();Log.e(TAG, "ontransact, arg is " + _arg0 + ", when this happened?");this.print(_arg0);reply.writeNoException();return true;}}return super.onTransact(code, data, reply, flags);}static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0);}

Client Activity:

public class AnotherMyClientActivity extends Activity {    private static final String TAG = "MyClientActivity";MyPrinterInterface mService;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.printer_activity);        setTitle("My interface another client Activity");        ((Button) findViewById(R.id.play)).setText("Print via my interface");    }    @Override    protected void onStart() {        super.onStart();        doBindService();    }private void doBindService() {Intent intent = new Intent();intent.setClassName("com.example.effectiveandroid", "com.example.effectiveandroid.MyServerService");        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);}    @Override    protected void onStop() {        super.onStop();        doUnbindService();    }private void doUnbindService() {if (mService != null) {            unbindService(mConnection);        }}public void onButtonClick(View v) {if (mService == null) {Log.e(TAG, "what the fucl service is not ready");return;}try {mService.print("In another application, create a client based on user defined IPC interfaces");} catch (RemoteException e) {e.printStackTrace();}}    private ServiceConnection mConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName className, IBinder service) {        Log.e(TAG, "on service connected, service obj " + service);            mService = MyPrinterInterface.Stub.asInterface(service);        }@Override        public void onServiceDisconnected(ComponentName arg0) {        mService = null;        }    };}

Client Side interface definiition:

public interface MyPrinterInterface extends android.os.IInterface {public void print(String msg) throws android.os.RemoteException;public abstract class Stub extends Binder implements MyPrinterInterface {private static final String DESCRIPTOR = "MyPrinterInterface";private static final String TAG = "MyPrinterInterface.Stub";public Stub() {attachInterface(this, DESCRIPTOR);}public static MyPrinterInterface asInterface(IBinder obj) {if ((obj == null)) {return null;}Log.e(TAG, "we are talking to a remote one, we must use a proxy object to wrapper binder");return new Stub.Proxy(obj);}static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0);private static class Proxy implements MyPrinterInterface {private IBinder mRemote;Proxy(IBinder remote) {mRemote = remote;}@Overridepublic IBinder asBinder() {return mRemote;}public String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic void print(String msg) throws android.os.RemoteException {Parcel _data = Parcel.obtain();Parcel _reply = Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeString(msg);mRemote.transact(Stub.TRANSACTION_print, _data, _reply, 0);Log.e(TAG, "lalalala, let us passing the parameters and calling the message");_reply.readException();} finally {_reply.recycle();_data.recycle();}}}}}

Essence-Remove underwear

In fact, the role of aidl is to encapsulate Binder's two methods: binder. transact () and binder. ontransact () for the client and server. Since the methods for implementing the transact () and ontransact () methods are basically the same, you can use templates to generate specific code. Theoretically, you only need to generate the transact () code for the client and the ontransact () code for the server, however, because the tool cannot accurately determine whether an application is a client or a server, it generates all the code and stores it in a file. This is the automatically generated file you see.
Note that the client-side proxy combines the binder object and calls its transact () method. The server must inherit the binder object and overwrite the ontransact () method. What about Xiami? Because the client initiates the call of the IPC function, it can directly call the binder method for IPC. The server is passive and receives the IPC call, but the service itself cannot know when the call will come. Therefore, the callback (ontransact () must be implemented to the binder, so that the binder can tell the service when there is an IPC call.

Principles and insider information

The role of AIDL is to implement Remote Procedure Call on the Android platform, that is, Remote routine Call. RPC is one type of IPC, but it is a mechanism that calls Methods locally or in another process, or even on another host. The purpose of RPC is to allow the program not to worry about which process or machine the method is in, just call it like a normal local method, the RPC mechanism handles all the details. RPC is generally described Using IDL (Interface Definition Language). The implementation depends on the specific platform and Language. You can refer to Wikipedia to see RPC
And IDL.
AIDL provides RPC support for the Android platform: developers only need to define AIDL and perform some adaptation work. Then they can use these methods, you do not have to worry about whether the method described in the interface is in the same process or in other processes. The details of these RPC implementations are handled by the Binder and system.
Binder RPC process: <Binder RPC sequence>

We can see that this process is a standard RPC process.

Implementation without aidl

After knowing the essence of AIDL, AIDL can be used to implement IPC. Although AIDL is simple and convenient, it is not easy to understand, besides, the Code is redundant (the server does not need to prepare objects for the Client, and vice versa ).


So we can implement it ourselves:
Server interface:

public interface ServerPrinterInterface {public void print(String msg) throws android.os.RemoteException;}abstract class MyPrinterInterfaceStub extends Binder implements ServerPrinterInterface, IInterface {private static final String DESCRIPTOR = "MyPrinterInterface";static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0);private static final String TAG = "MyPrinterInterfaceStub";public MyPrinterInterfaceStub() {attachInterface(this, DESCRIPTOR);}@Overridepublic IBinder asBinder() {return this;}@Overridepublic boolean onTransact(int code, Parcel data, Parcel reply, int flags)throws android.os.RemoteException {Log.e(TAG, "onTransact, code is " + code);switch (code) {case INTERFACE_TRANSACTION: {Log.e(TAG, "onTransact, code is " + code + ", when this happens");reply.writeString(DESCRIPTOR);return true;}case TRANSACTION_print: {data.enforceInterface(DESCRIPTOR);String _arg0;_arg0 = data.readString();Log.e(TAG, "ontransact, arg is " + _arg0 + ", when this happened?");this.print(_arg0);reply.writeNoException();return true;}}return super.onTransact(code, data, reply, flags);}}

Service:

public class MyServerService extends Service {private static final String TAG = "MyServerService";private Handler mHandler = new Handler();@Overridepublic IBinder onBind(Intent intent) {return mBinder;}private MyPrinterInterfaceStub mBinder = new MyPrinterInterfaceStub() {@Overridepublic void print(String msg) throws RemoteException {MyServerService.this.print(msg);}};    public void print(String msg) {    try {    Log.e(TAG, "Preparing printer...");Thread.sleep(1000);Log.e(TAG, "Connecting printer...");Thread.sleep(1000);Log.e(TAG, "Printing.... " + msg);Thread.sleep(1000);Log.e(TAG, "Done");} catch (InterruptedException e) {}    mHandler.post(new Runnable() {    @Override    public void run() {    Toast.makeText(MyServerService.this, "MyServerService Printing is done.", Toast.LENGTH_LONG).show();    }    });    }}

Client interface:

public interface ClientPrinterInterface {public void print(String msg) throws android.os.RemoteException;}class MyPrinterInterfaceProxy implements ClientPrinterInterface {private static final String DESCRIPTOR = "MyPrinterInterface";static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0);private static final String TAG = "MyPrinterInterfaceProxy";private IBinder mRemote;MyPrinterInterfaceProxy(IBinder remote) {mRemote = remote;}@Overridepublic void print(String msg) throws android.os.RemoteException {Parcel _data = Parcel.obtain();Parcel _reply = Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeString(msg);mRemote.transact(TRANSACTION_print, _data, _reply, 0);Log.e(TAG, "lalalala, let us passing the parameters and calling the message");_reply.readException();} finally {_reply.recycle();_data.recycle();}}}

Client activity:

public class AnotherMyClientActivity extends Activity {    private static final String TAG = "MyClientActivity";ClientPrinterInterface mService;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.printer_activity);        setTitle("My interface another client Activity");        ((Button) findViewById(R.id.play)).setText("Print via my interface");    }    @Override    protected void onStart() {        super.onStart();        doBindService();    }private void doBindService() {Intent intent = new Intent();intent.setClassName("com.example.effectiveandroid", "com.example.effectiveandroid.MyServerService");        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);}    @Override    protected void onStop() {        super.onStop();        doUnbindService();    }private void doUnbindService() {if (mService != null) {            unbindService(mConnection);        }}public void onButtonClick(View v) {if (mService == null) {Log.e(TAG, "what the fucl service is not ready");return;}try {mService.print("In another application, create a client based on user defined IPC interfaces");} catch (RemoteException e) {e.printStackTrace();}}    private ServiceConnection mConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName className, IBinder service) {        Log.e(TAG, "on service connected, service obj " + service);            mService = new MyPrinterInterfaceProxy(service);        }@Override        public void onServiceDisconnected(ComponentName arg0) {        mService = null;        }    };}

Here we can see that there are two advantages of not using AIDL:

1. Another advantage of self-implementation is that you can design the package name at will. If AIDL is used, the package of the AIDL file on the Client must be exactly the same as that of the AIDL package on the Server. Otherwise, the service cannot be found and an exception occurs on the Client. However, if you implement the interface by yourself, you can put the interface file in any package.
Why? Because the identity descriptor of the stub and proxy generated by aidl adds the package name. If you implement your own interface, you can write this descriptor at will.
2. The interface name does not actually matter. Further, the signature of the method may be inconsistent. Because the two interfaces are on the client side and the other is on the server side. The relationship between them is indirectly implemented through the binder object. As long as the two methods of the binder object can find the corresponding interface methods.

Key communication identifier and method identifier
From the above example, we can see that the customer's proxy's transact and the Service's ontransact use two identifiers to identify the other party: one is the descriptor, and the other is the token that identifies the binder, that is to say, it identifies the server and client; the method ID is transaction_print, which is used to identify the method called. This is not difficult to understand. It is like making a phone call. First, you need to find the corresponding person through the phone number marked by the communication, then, when talking to people, tell them what it is (which method ).
Next, we can conduct in-depth research in two aspects:

1. How does bindservice obtain the binder object (whether it is a local service implementation or a remote binderproxy), or how to query the binder object.
This is the call stack of serviceconnection. onserviceconnected:
2. Details of binder. transact () and binder. ontransact () are also details of the specific binder IPC Mechanism.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.