First, preface
In the previous blog Android IPC mechanism (ii): Aidl's basic usage, the author describes a major way of communication between Android processes. Use Aidl for communication. The basic usage of aidl is also introduced.
In fact, the Aidl method uses binders for cross-process communication. Binder is a way of communicating across processes in Android. The underlying implementation principle is more complex. Limited to the author level, can not unfold. So this article mainly talk about the use of binder in Aidl as an example.
Second, the principle
A imyaidl.aidl file, the interface file, was created in the previous article, and the file was compiled. A. java file is generated that is under the Gen folder:
Open the file and get the following code for example:
<span style= "FONT-SIZE:18PX;" >/* * This file is auto-generated. Do not MODIFY. * Original File:g:\\android\\project\\myaidl\\app\\src\\main\\aidl\\com\\chenyu\\service\\imyaidl.aidl */package Com.chenyu.service;public interface Imyaidl extends Android.os.IInterface { /** * local-side IPC Implementation stub class. */Public static abstract class Stub extends Android.os.Binder implements Com.chenyu.service.IMyAidl { ... c8/>public void Addperson (Com.chenyu.service.Person person) throws android.os.RemoteException; Public java.util.list<com.chenyu.service.person> getpersonlist () throws Android.os.remoteexception;</span }
part of the omission, we first understand from the general, and then in depth.
(1) In general, the Java file is an interface, inheriting the IInterface interface, and then declaring a static internal abstract class: Stub. Then there are two methods that can be seen. These two methods are each of the two methods declared within the original Imyaidl.aidl file.
(2) We look back at the stub class, which inherits the binder and implements the Imyaidlat the same time.
This class implements its own interface! It is conceivable that the interface declares the Addperson,getpersonlist method. will be implemented in the stub class and how to implement it in detail. We expand the stub class:
public static abstract class Stub extends Android.os.Binder implements Com.chenyu.service.IMyAidl {private static Final java.lang.String descriptor = "Com.chenyu.service.IMyAidl"; /** * Construct The stub at attach it to the interface. */Public Stub () {<span style= ' white-space:pre ' ></span>//①this.attachinterface (this, DESCRI Ptor); }/** * Cast an IBinder object to an Com.chenyu.service.IMyAidl interface, * Generating a proxy I F needed. */public static com.chenyu.service.IMyAidl asinterface (Android.os.IBinder obj) {//②if (obj = = nul L)) {return null; } android.os.IInterface iin = Obj.querylocalinterface (descriptor); if ((iin! = null) && (iin instanceof Com.chenyu.service.IMyAidl)) {return (com.chenyu.service.i MYAIDL) iin); } return new Com.chenyu.service.IMyAidl.StuB.proxy (obj); } @Override Public Android.os.IBinder Asbinder () {<span style= "White-space:pre" ></span>//③ return this; } @Override public boolean ontransact (int code, ANDROID.OS.PARCEL data, android.os.Parcel reply, int flags) Throws Android.os.RemoteException {<span style= "White-space:pre" ></span>//④switch (code) { Case Interface_transaction: {reply.writestring (descriptor); return true; } case Transaction_addperson: {data.enforceinterface (descriptor); Com.chenyu.service.Person _arg0; if ((0! = Data.readint ())) {_arg0 = Com.chenyu.service.Person.CREATOR.createFromParcel (data); } else {_arg0 = null; } This.addperson (_ARG0); Reply.writenoexCeption (); return true; } case transaction_getpersonlist: {data.enforceinterface (descriptor); java.util.list<com.chenyu.service.person> _result = This.getpersonlist (); Reply.writenoexception (); Reply.writetypedlist (_result); return true; }} return Super.ontransact (code, data, reply, flags); } private static Class Proxy implements Com.chenyu.service.IMyAidl {<span style= "White-space:pre" ></SPAN&G T;//⑤ ...} static final int Transaction_addperson = (Android.os.IBinder.FIRST_CALL_TRANSACTION + 0); ⑥static final int transaction_getpersonlist = (Android.os.IBinder.FIRST_CALL_TRANSACTION + 1); }(3) from the top down, we analyze each method or variable function:
①stub () Construction method: This method invokes the Attachinterface () method of the parent binder, which ties the current interface to binder because the descriptor is passed, uniquely identifying the current interface.
②asinterface (IBinder obj): A static method that passes an interface object . Where does the object pass in? Let's take a look at the client code of the previous chapter blog:
public void onserviceconnected (componentname name, IBinder service) { log.d ("Cylog", "onserviceconnected success") ; Imyaidl=imyaidl.stub.asinterface (service); }
here it is. Can see, called the IMyAidl.Stub.asInterface (service) method, that is, the above ② method, and the service passed in, we continue to look down:
public static Com.chenyu.service.IMyAidl asinterface (Android.os.IBinder obj) { if ((obj = = null)) { return null; } Android.os.IInterface iin = obj.querylocalinterface (descriptor); if ((iin! = null) && (iin instanceof Com.chenyu.service.IMyAidl)) { return ((COM.CHENYU.SERVICE.IMYAIDL) IIN); } return new Com.chenyu.service.IMyAidl.Stub.Proxy (obj);
The first inference is whether obj is valid, assuming that the invalid directly returns NULL, indicating that the client connection to the server failed. The Obj.querylocalinterface (Descriptor) method is then called. Assigns a value to the IInterface object. Notice here that the descriptor parameter is passed again, and it is possible to extrapolate a method that looks for a method related to the current interface, and we look at the Querylocalinterface () method of the IBinder interface:
/** * Attempt to retrieve a local implementation of an interface * for this Binder object. If NULL is returned, you'll need * To instantiate a proxy class to Marshall calls through * The Transact () metho D. * /Public iinterface querylocalinterface (String descriptor);
probably means to say, according to the value of descriptor. Attempt to retrieve a local interface for binder, where Local is the current process , assuming that the return value is NULL, then a proxy class should be instantiated. After understanding the Obj.querylocalinterface (descriptor) method, we return to the asinterface (obj) method again. Keep looking down: Then there is an if inference. The main inference is whether the client is in the same process as the server, assuming it is in the same process. Then the stub object itself is returned directly. Assuming it is not the same process, a new proxy proxy class will be created (as mentioned below).
③asbinder (): This method is used to return the current object itself.
④ontransact (int code,parcel data,parcel reply,int Flags): This method is typically executed in the binder thread pool on the server side. That is, the remote request will be processed in this method. The code value passed is used to infer the client's request target, either Addperson or Getpersonlist.
We take the request target as Addperson () as an example to analyze, extract its main function body such as the following:
Case Transaction_addperson: { data.enforceinterface (descriptor); Com.chenyu.service.Person _arg0; if ((0! = Data.readint ())) { _arg0 = com.chenyu.service.Person.CREATOR.createFromParcel (data); } else { _ arg0 = null; } This.addperson (_arg0); Reply.writenoexception (); return true; }
The object that _arg0 is the person class is declared first. Then. The creator.createfromparcel method of the person class is called with data, and deserialization generates the person class, which is why The class that implements the Parcelable interface should implement creator at the same time, where the deserialization method is called. Then. Call the This.addperson (_arg0) method, Note: This here represents the current binder object. Then the Addperson (_arg0) method that is called by Binder is actually called by the service that binds to the binder, that is, the server invokes its own Addperson method. For convenience and clarity. Let's look back at the code from the previous article server:
Private IBinder ibinder= New Imyaidl.stub () { @Override public void Addperson (person person) throws remoteexception { persons.add (person); } @Override public list<person> getpersonlist () throws RemoteException { return persons; } };
Is it clear all of a sudden? Imyaidl.stub () implements the interface, in which the methods are implemented on the server side: Addperson and Getpersonlist (). When the This.addperson () method is called in the binder thread pool, the Addperson method of the server is actually recalled. And the bottom is how to achieve, limited to the author's level. Temporarily do not understand, and so on after the author in-depth understanding of the working mechanism of binder to answer this question.
Well, back to the current class, we continue to look down:
⑤private Static Class Proxy: There is also a private static inner class, about which the class will be described in the following detail.
⑥ the last two lines of code are each two constants, which flags two methods, the code values mentioned above.
(4) proxy class. Also implemented the Imyaidl interface, the same time to implement the Addperson and Getpersonlist methods.
And where is the proxy class instantiated? is above (3) ②, when the client is not in the same process as the server, the proxy class is instantiated and returned to the client. What is a proxy class? the so-called agent, that is, an intermediary, the client to get the instance, can operate part of the service side of the function, let the client think that they have got the server instance, in fact, is not. Just get a proxy on the server.
Next we expand the class and look inside:
private static class Proxy implements Com.chenyu.service.IMyAidl {private Android.os.IBinder mremote; Proxy (Android.os.IBinder remote) {mremote = remote; } @Override Public Android.os.IBinder Asbinder () {return mremote; } public java.lang.String Getinterfacedescriptor () {return descriptor; } @Override public void Addperson (Com.chenyu.service.Person person) throws Android.os.RemoteExceptio n {android.os.Parcel _data = Android.os.Parcel.obtain (); Android.os.Parcel _reply = Android.os.Parcel.obtain (); try {_data.writeinterfacetoken (descriptor); if ((person! = null)) {_data.writeint (1); Person.writetoparcel (_data, 0); } else {_data.writeint (0); } mremote.transact (Stub.transaction_addperson, _data, _reply, 0); _reply.readexception (); } finally {_reply.recycle (); _data.recycle (); }} @Override public java.util.list<com.chenyu.service.person> getpersonlist () thro WS Android.os.RemoteException {Android.os.Parcel _data = Android.os.Parcel.obtain (); Android.os.Parcel _reply = Android.os.Parcel.obtain (); Java.util.list<com.chenyu.service.person> _result; try {_data.writeinterfacetoken (descriptor); Mremote.transact (Stub.transaction_getpersonlist, _data, _reply, 0); <span style= "White-space:pre" ></span >//①_reply.readexception (); _result = _reply.createtypedarraylist (Com.chenyu.service.Person.CREATOR); } finally { _reply.recycle (); _data.recycle (); } return _result; } }Here we focus on the implementation of two interface methods: Addperson and Getpersonlist. These two methods have appeared several times. And in the proxy class implementation of these two methods,is performed on the client!
。!
The main implementation process is this: when the client gets the proxy class. Call the Addperson or Getpersonlist method. The input parcel object _data and the output type Parcel object _reply are created first, and then the ① code calls transact to initiate the RPC remote request, at the same time the current thread is suspended . The ontransact on the server will be called, which is the (3) ④ code mentioned above. When the server finishes processing the request. The data is returned. The current thread continues execution knowing that the _result result is returned.
At this point, one of the--aidl principles for the IPC approach has been analyzed, and the following summarizes :
1. The client makes a binding request, and the server and client are bound on the same binder. The client runs the Asinterface () method. Assuming that the client and the server are in the same process, the stub object itself is returned directly to the server, and the Stub.proxy proxy class object is returned if it is in a different process.
2, the client sends the remote request (Addperson or getpersonlist), at this time the client thread hangs, binder to get the data, processing data such as in different processes, will write the data parcel, call Transact method.
3, trigger the Ontransact method, the method executes in the binder thread pool. method, the interface method to the server implementation is called. When the data processing is complete, the reply value is returned, and the binder is returned to the client, at which point the client thread is awakened.
Third, optimize
Finally, say how to optimize the aidl. mentioned above. After the client sends the request, it is suspended. This means that assuming that the time to process the data is too long, then the thread will not wait until wake up, which is very serious, assuming that the UI thread sending the request will directly lead to the ANR, so we need to send an asynchronous request on the child thread so that the ability to avoid ANR. Another point. Binder may be accidental death, assuming that binder accidentally dies, then the child thread may hang, so we want to enable another connection service. There are two methods, one is to set Deathrecipient listener for Binder and the other is to re-connect the service in onservicedisconnected .
Reference book: "Android Development Art Exploration" Ningyugang, September 2015 first edition
Android IPC Mechanism (III): On the use of binder