preface

Before I decided to use the title, I was worried that I didn’t know enough about AIDL, and people would look at it and say, “What are you, that’s enough?” It’s like looking in the mirror — that would be embarrassing. But then I thought, our young people should have an indomitable spirit, wouldn’t it be better to have an atmosphere? And we’re all civilized, so it’s always more about understanding and supplementing than insulting and reviling. Right? So in the end it had the audacity to go with such an unashamed title.

Ok, let’s get down to business and talk about my understanding of AIDL.

The body of the

1, an overview of the

AIDL is an acronym that stands for Android Interface Definition Language. Yes, the first thing we know is that AIDL is a language. Since it is a language, some problems naturally arise accordingly:

  • Why design such a language?
  • What syntax does it have?
  • How should we use it?
  • And a little bit deeper, we can think about, how do we use it to get what we want?
  • Further, why is the language designed this way? Is there a better way to achieve our purpose?

Let’s take a step-by-step approach to answering these questions.

Ps: 1, some necessary knowledge is needed before studying aiDL-related things. On the one hand, it is necessary to understand the knowledge related to Android service. For this aspect, you can refer to Android service: Silent devotee (1), Android service: Binder, Messenger, AIDL (2). On the other hand, serialization in Android is related to knowledge, which will be briefly mentioned in this article, but if you want to further study it is best to find some information on this aspect. 2. My compilation environment is Android Studio2.1.2, SDK Version 23, JDK 1.7.

2. Why was this language designed?

The language was designed to enable interprocess communication.

Each process has its own Dalvik VM instance, its own independent memory, stores its own data in its own memory, performs its own operations, and lives its life in its own narrow space. In each process, you do not know me, I do not know you, just like two islands facing each other across the river, both in the same world, but each has its own world. And AIDL is a bridge between the two islands. We are like demi-makers to them, and we can use AIDL to make rules about what they can communicate with each other — for example, they can transfer certain specifications of data under our rules.

In short, with this language, we can happily access data from one process to another and even call some of its methods, of course, only certain methods.

3. What syntax does it have?

AIDL is a very simple language, and basically has the same syntax as Java, with some minor differences – it was created to simplify the work of Android programmers, after all, and is too complex – so I’ll focus on the differences here. There are mainly the following points:

  • File type: Files written in AIDL have an.aidl suffix, not.java.
  • Data types: There are some data types that AIDL supports by default, and you don’t need to package them when using them. However, for data types other than these types, you must package them before using them.Even if the target file is in the same package as the.aidl file you are currently writingIn Java, there is no package guide required in this case. For example, we have now written two files, one calledBook.javaThe other one is calledBookManager.aidlThey are all therecom.lypeer.aidldemoNow we need to use the Book object in the.aidl file, so we must write in the.aidl fileimport com.lypeer.aidldemo.Book;Even though.java files and.aidl files are in the same package.

    The default data types are:

    • The eight basic data types in Java include byte, short, int, long, float, double, Boolean, char.
    • The String type.
    • CharSequence type.
    • List types: All elements in a List must be one of the types supported by AIDL, an interface generated by another AIDL, or a defined Parcelable (more on this below). List can use generics.
    • Map type: All elements in a Map must be one of the types supported by AIDL, an interface generated by another AIDL, or a parcelable defined. Map does not support generics.
  • Directional tag: This is a point that is easily overlooked – by “overlooked” I mean not that nobody knows it, but that few people use it correctly. In indicates that data can only flow from the client to the server, out indicates that data can only flow from the server to the client, and inout indicates that data can flow in both directions between the server and client. Where the data flow is for the object passing the method in the client. In indicates that the server will receive the complete data of the object, but the object on the client will not change as the server changes the parameter. Out indicates that the server will receive the empty object of that object, but the client will synchronize the change after the server has made any changes to the received empty object. When inout is tag oriented, the server will receive complete information about the object from the client, and the client will synchronize any changes the server makes to the object. For specific analysis, you can refer to my other blog post: Do you really understand AIDL’s in, out, inout? In addition, in Java, the orientation tag for primitive types and strings and CharSequence can only be in by default. Also, please note that don’t abuse targeted tags, but choose the appropriate ones based on your needs – if you use inout all the time, the system will be much more expensive when the project is big – because sorting parameters is expensive.
  • Two TYPES of AIDL files: As I understand it, all AIDL files can be roughly divided into two categories. One class is used to define parcelable objects for other AIDL files to use data types that are not supported by default in AIDL. One is used to define method interfaces that the system uses to accomplish cross-process communication. As you can see, both types of files “define” something, not the implementation, which is why it’s called the Android Interface Definition Language. Note: All non-default supported data types must be defined in a category 1 AIDL file before they can be used.

Here are two examples of common AIDL files:

// Book. Aidl // an example of the first type of aiDL file // This file introduces a serialized object Book to be used by other AIDL files // Note: Book.aidl and book.java should have the same package name as package com.lypeer-ipcclient; // Note that parcelable is lowercase parcelable Book;
// bookManager.aidl // an example of the second type of AIDL file package com.lypeer-ipcclient; / / import support needed to use the default data type of the package import com. Lypeer. Ipcclient. Book; Interface BookManager {// All return values do not need to be preceded by anything, regardless of the data type List getBooks(); Book getBook(); int getBookCount(); // All types other than Java primitives, String, and CharSequence need to be preceded by a directional tag. Void setBookName(in Book Book, int price) void setBookName(in Book Book, int price) String name) void addBookIn(in Book book); void addBookOut(out Book book); void addBookInout(inout Book book); }Copy the code

4. How to use AIDL files to complete cross-process communication?

When communicating across processes, it makes a difference whether methods defined in AIDL contain data types that are not supported by default. If not, we only need to write one AIDL file, and if we do, we usually need to write N +1 AIDL files (n is the number of types of data types that are not supported by default) -- obviously, the included case is a little more complicated. So I'm going to cover only cases where AIDL files contain data types that are not supported by default, but a simpler case that I'm sure you can easily relate to by analogy.

4.1. Make data classes implement the Parcelable interface

Due to the different process have different memory area, and they can only access to their own that a piece of memory area, so we can't as usual, a handle just a matter of the past, a handle is a memory area, now target process cannot access the source process of memory, then passed it again have what use? So we have to transfer data into a form that can be circulated between memory. This process of transformation is called serialization and deserialization. It's like this in a nutshell: Such as now we're going to a data object from the client to the server, we can in the client to the operation of the this object serialization, convert the data it contains to serialization stream, then the serialization stream transmission to the server memory, again on the server to deserialize the data flow of operations, In this way, we have access to data from one process to another.

Typically, when communicating across processes through AIDL, the serialization of choice is to implement the Parcelable interface. I won't go into the details of what methods are available once the Parcelable interface is implemented, and what each method does. That's not the focus of this article, but how to quickly generate a qualified serializable class (book.java for example).

Note: This step is not required if all data types involved in AIDL files are supported by default. The default data types that are supported are serializable.

4.1.1, automatically generated by the compiler

The compiler I currently use is Android Studio 2.1.2, which comes with a template for the Parcelable interface that can easily generate a serializable Parcelable implementation class with just a few keystrokes.

First, create a class, write its member variables normally, set getters and setters, and add a no-argument construct, such as:

public class Book{
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    private String name;

    private int price;

    public Book() {}

}Copy the code

Then implements Parcelable, and as will report an error. Move your mouse over there and press Alt + Enter to make it resolve the error automatically. At this point it will do some of the work for you:

Select all member variables in the box that pops up, and then confirm. You'll notice that there's a bit more code in the class, but you'll still get an error. There's still a little line under Book. Move your mouse over there again and press Alt + Enter to have it resolve the error automatically:

The Book class basically implements the Parcelable interface and can perform serialization.

Note, however, that there is a catch: objects of the default generated template class only support an in-directed tag. Why is that? Because the default generated class only has the writeToParcel() method, and the readFromParcel() method isn't in the Parcelable interface to support out or inout directed tags, So we have to start from scratch. Do you really understand in, out, inout in AIDL?

So what about the readFromParcel() method? Write like this:

@Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(price); Public void readFromParcel(Parcel dest) {param dest */ public void readFromParcel(Parcel dest) { The order of values read here should be the same as in writeToParcel(); price = dest.readInt(); }Copy the code

After adding the readFromParcel() method as above, the object of our Book class can use out or inout as its directed tag in an AIDL file.

At this point, the complete code for the Book class looks like this:

package com.lypeer.ipcclient; import android.os.Parcel; import android.os.Parcelable; /** * Book.java * * Created by lypeer on 2016/7/16. */ public class Book implements Parcelable{ public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } private String name; private int price; public Book(){} public Book(Parcel in) { name = in.readString(); price = in.readInt(); } public static final Creator CREATOR = new Creator() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; }}; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(price); Public void readFromParcel(Parcel dest) {param dest */ public void readFromParcel(Parcel dest) { The order of values read here should be the same as in writeToParcel(); price = dest.readInt(); } @override public String toString() {return "name: "+ name +", price: "+ price; }}Copy the code

At this point, serialization of data types that are not supported by default in AIDL is complete.

4.1.2, Plug-in generation

I don't know if Eclipse or a later version of AS will help us implement the Parcelable interface as much as AS AS 2.1.2, but even if it doesn't, there are other ways to implement the Parcelable interface through plug-ins.

Specific implementation methods and implementation process we can see this article: farewell handwritten Parcelable

4.2. Write AIDL files

First we need a book. aidl file to introduce the Book class so that other AIDL files can use the Book object. So the first step, how do I create an AIDL file? Android Studio has already integrated this for us:

Mouse over the app, right click, then new->AIDL->AIDL File, press the left mouse button will pop up a box prompting the creation of AIDL File. After the AIDL file is generated, the project directory looks like this:

There is a new package called AIDL, which has the same hierarchy as the Java package, and the aiDL package has the same structure as the Java package by default. So what if you're using Eclipse or a later version of AS and the compiler doesn't have this option? That's okay. We can write our own. Open the project folder, go to app-> SRC ->main, create an aidl folder under main that is the same level as the Java folder. Then manually create the same folder structure as the default Java folder structure. Create a new.aidl file on the innermost layer:

Notice the file directories in the picture.

Ok, how to create a new AIDL file is almost said, now it is time to write the contents of the AIDL file. The content is basically fine if you looked at it carefully in the last video. Here, we need two AIDL files, which I wrote like this:

// book.aidl // the first type of aiDL file // The purpose of this file is to introduce a serialized object Book for other aiDL files to use // note: Book.aidl and book.java should have the same package name as package com.lypeer-ipcclient; // Note that parcelable is lowercase parcelable Book;
// bookManager.aidl // the second type of aiDL file is used to define the method interface package com.lypeer. / / import support needed to use the default data type of the package import com. Lypeer. Ipcclient. Book; Interface BookManager {// All return values do not need to be preceded by anything, regardless of the data type List getBooks(); Void addBook(in Book Book); void addBook(in Book Book); void addBook(in Book Book); }Copy the code

Attention: here is another pit! You may have noticed that in the book.aidl file I have been emphasizing that the package name for book.aidl and book.java should be the same. This seems to mean that the two files should be in the same package - in fact, many older articles say that they should be in the same package in the AIdl package for easy portability - but this is not the case in Android Studio. If you do this, the book.java file will not be found at all, resulting in a Symbol Not found error when using Book objects in other AIDL files. Why is that? Because Gradle. As you all know, Android Studio uses Gradle by default to build Android projects, and Gradle uses sourceSets to configure access paths for different files to speed up the lookups -- that's where the problem lies. By default, Gradle sets the access path of Java code in the Java package. Therefore, if the Java file is placed in the AIDL package, the system will not find the Java file. So what should we do?

The solution is obvious if you think about it: the Java file must have the same package name as the AIDL file, and the Java file must be found. First we can translate the problem into: how can the system find our Java files while ensuring that the two file packages have the same name? The idea is clear: either let the system find the Java files in the AIDL package, or put the Java files where the system can find them, in the Java package. Next, I will talk about how to do these two ways in detail:

  • To modify the build.gradle file, add the following to android{} :
sourceSets {
    main {
        java.srcDirs = ['src/main/java', 'src/main/aidl']
    }
}Copy the code

That is, the access path of Java code is set to Java package and AIDL package, so that the system will look for Java files in aiDL package, which is our purpose. One thing is, the project directory in Android Studio will change a little bit, and I find it ugly.

  • Add Java files to Java package: Place book. Java under any package in the Java package, keeping its package name the same as book. aidl. Book.aidl will find book.java as long as its package name remains the same, and book.java will be found by the system as long as it is under the Java package. The problem with this is that it is not easy to migrate the related.aidl and.java files. You can't just take the entire aidl folder and put the.java files into the Java folder.

We can use one of the above two methods to solve the problem of not finding.java files, which is up to you, but it's pretty simple.

At this point we have created and written the AIDL file, clean the project, and if there are no errors, we are done.

4.3. Transplant relevant files

We need to make sure that we have.aidl files and the.java files involved on both the client and server, so whatever we write on either end, we copy the files to the other end. If you can't find the.java file using the first of the two methods, you can simply copy the aiDL package to the main directory on the other end. If you use the second method, in addition to taking the entire AIDL folder, you also need to place the.java files in the Java folder separately.

4.4. Write server-side code

Now that we've completed the AIDL and its associated files, how exactly should we use them to communicate across processes? In fact, after we write the AIDL file and clean or rebuild the project, the compiler will generate a.java file with the same name as the AIDL file. This.java file is the most relevant for our cross-process communication. In fact, the basic workflow is to implement the specific logic of the method interfaces defined in AIDL on the server side, and then invoke those method interfaces on the client side to achieve cross-process communication.

Next I'll just post the server-side code I wrote:

/** * server aidlservice.java *Copy the code

* Created by lypeer on 2016/7/17. */ public class AIDLService extends Service { public final String TAG = this.getClass().getSimpleName(); Private list mBooks = new ArrayList<>(); Stub mBookManager = new BookManager.stub () {@override public List getBooks() throws RemoteException { synchronized (this) { Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString()); if (mBooks ! = null) { return mBooks; } return new ArrayList<>(); } } @Override public void addBook(Book book) throws RemoteException { synchronized (this) { if (mBooks == null) { mBooks = new ArrayList<>(); } if (book == null) { Log.e(TAG, "Book is null in In"); book = new Book(); } // Try to modify the parameters of book, mainly to observe its feedback to the client book.setprice (2333); if (! mBooks.contains(book)) { mBooks.add(book); Invoking addBooks() method, now the list is: "+ mbooks.tostring ()); }}}; @Override public void onCreate() { super.onCreate(); Book book = new Book(); Book.setname ("Android Development Art Exploration "); book.setPrice(28); mBooks.add(book); } @Nullable @Override public IBinder onBind(Intent intent) { Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString())); return mBookManager; }}

The overall code structure is very clear, can be roughly divided into three pieces: the first piece is initialization. In the onCreate() method I did some initialization of the data. The second block overrides the methods in bookManager.stub. This provides the implementation logic for the method interface defined in AIDL. The third piece is to override the onBind() method. Inside returns the written bookManager.stub.

Next, register the Service we wrote in the Manefest file. Otherwise, all the previous work would be useless:


        
            
            
        
Copy the code

This is the end of the server code. If you feel a little strange or confused about some parts of it, you may have forgotten about Service. Android Services: Binder, Messenger, AIDL (2)

4.5. Write client code

As mentioned earlier, the main thing we need to do on the client side is to call the methods on the server side, but first we need to connect to the server side. The complete client code looks like this:

/** * aidlactivity. Java * Because there are too many useless debug messages in the test machine, so the log is used by e *Copy the code

* Created by lypeer on 2016/7/17. */ public class AIDLActivity extends AppCompatActivity {// Java classes generated by AIDL files are private BookManager mBookManager = null; Private Boolean mBound = false; private Boolean mBound = false; // List private list mBooks; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_aidl); Public void addBook(view view) {public void addBook(view view) {// If (! mBound) { attemptToBindService(); Toast.maketext (this, toast.length_short).show(); toast.maketext (this, toast.length_short).show(); return; } if (mBookManager == null) return; Book book = new Book(); Book.setname ("APP r&d record In"); book.setPrice(30); try { mBookManager.addBook(book); Log.e(getLocalClassName(), book.toString()); } catch (RemoteException e) { e.printStackTrace(); AttemptToBindService () {Intent Intent = new Intent(); intent.setAction("com.lypeer.aidl"); intent.setPackage("com.lypeer.ipcserver"); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStart() { super.onStart(); if (! mBound) { attemptToBindService(); } } @Override protected void onStop() { super.onStop(); if (mBound) { unbindService(mServiceConnection); mBound = false; } } private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e(getLocalClassName(), "service connected"); mBookManager = BookManager.Stub.asInterface(service); mBound = true; if (mBookManager ! = null) { try { mBooks = mBookManager.getBooks(); Log.e(getLocalClassName(), mBooks.toString()); } catch (RemoteException e) { e.printStackTrace(); } } } @Override public void onServiceDisconnected(ComponentName name) { Log.e(getLocalClassName(), "service disconnected"); mBound = false; }}; }

It is also clear to establish the connection, then fetch the BookManager object in ServiceConnection, and then call the server-side methods through it.

4.6, Start correspondence!

With the above steps, we have completed all the preliminary work and are now ready to communicate across processes via AIDL! Run both apps on the same phone and call the addBook() method on the client side. The logcat information on the server side looks like this:

On bind,intent = intent {act=com.lypeer. Aidl PKG =com.lypeer. Invoking getBooks() method, now the list is: name, invoking getBooks() method, now the list is: Invoking addBooks() method, now the list is: [name: Android dev, price: 28, name: APP dev, price: 2333] 6, invoking addBooks() method, now the list is: [name: Android dev, price: 28, name: APP dev, price: 2333]Copy the code

The client message looks like this:

1, Service Connected 2, [name: Android development art Exploration, Price: 28Copy the code

All of the log information is normal and as expected -- this means that we have done the right thing up to this point, and that we can use AIDL correctly for cross-process communication.

conclusion

This article mainly introduces the first three questions we mentioned in the overview, namely:

  • Why was AIDL designed?
  • What is the syntax of AIDL?
  • How do I communicate across processes using AIDL?

I was going to cover all five of my questions in this article, but now I find it's getting too long, and few people will have the patience to read on - so what's the point of the rest? So I'll just cut it off and put AIDL in the next post about how it works and how it's designed and what I think it's designed to do - which happens to be sort of basic and improved, haha.

The relevant code in this article can be downloaded by clicking portal.

Thank you.

Copy the codeCopy the code