@martin0207
2017-12-01T10:44:27.000000Z
字数 9482
阅读 1194
跨进程通讯
目录
AIDL(Android Interface Definition Language),接口定义语言
实现进程间通讯(特定规格和特定方法下)
AIDL的定向Tag表示在跨进程通讯中,信息的流向
| Tag符 | 作用 | 表现 |
|---|---|---|
| in | 数据只能从客户端流向服务端 | 服务端可以接收到一个完整的对象,且客户端不会因为服务端对该对象的操作而改变 |
| out | 数据只能从服务端流向客户端 | 服务端会接收到该对象的空对象,但服务端对接收到的空对象有任何修改之后,客户端将会同步变动 |
| inout | 数据可以在服务端和客户端之间双向流通 | 服务端可以接收到客户端发过来的完整对象,并且客户端会同步服务端对该对象的所有操作 |
下面做个简单的图来展示:
| Tag符 | 服务端是否接收到完整对象 | 客户端是否同步服务端对Object的操作 |
|---|---|---|
| in | √ | × |
| out | × | √ |
| inout | √ | √ |
注意: Java中的八种基本类型和String、CharSequence默认并且只能使用in。
*.aidl文件
我们以 Book.aidl 为例,创建完成之后,Android Studio会自动给我们创建aidl与java包同级

创建后,里面的初始化代码为:
// Book.aidlpackage com.martin.aidlblog;// Declare any non-default types here with import statementsinterface Book {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);}
这时候,我们创建Book.java文件,在Book.aidl相同包下。写入成员变量
private String name;private int price;private boolean nowRead;private int readPage;
并且创建成员变量的 set/get 方法,空构造方法以及方便创建对象的全有属性构造方法。
这是一个Model类的基本创建过程。但AIDL通讯中,数据对象必须是Parcelable对象,所以我们将Book.java实现Parcelable接口,最终代码如下:
public class Book implements Parcelable{private String name;private int price;private boolean nowRead;private int readPage;public Book() {}public Book(String name, int price, boolean nowRead, int readPage) {this.name = name;this.price = price;this.nowRead = nowRead;this.readPage = readPage;}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;}public boolean isNowRead() {return nowRead;}public void setNowRead(boolean nowRead) {this.nowRead = nowRead;}public int getReadPage() {return readPage;}public void setReadPage(int readPage) {this.readPage = readPage;}protected Book(Parcel in) {name = in.readString();price = in.readInt();nowRead = in.readByte() != 0;readPage = in.readInt();}public static final Creator<Book> CREATOR = new Creator<Book>() {@Overridepublic Book createFromParcel(Parcel in) {return new Book(in);}@Overridepublic Book[] newArray(int size) {return new Book[size];}};@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeString(name);dest.writeInt(price);dest.writeByte((byte) (nowRead ? 1 : 0));dest.writeInt(readPage);}}
注意:这里默认生成的模板类,只能支持 in的定向Tag,因为生成的方法里面,只有writeToParcel(),如果想要支持out或inout定向Tag的话,还需要实现readFromParcel()方法,但这个方法并不在Parcelable里面,需要我们手写。万幸,有writeToParcel()方法作为参考:
@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeString(name);dest.writeInt(price);dest.writeByte((byte) (nowRead ? 1 : 0));dest.writeInt(readPage);}public void readFromParcel(Parcel dest) {//注意,此处的读值顺序应当是和writeToParcel()方法中一致的name = dest.readString();price = dest.readInt();nowRead = dest.readByte() == 1;readPage = dest.readInt();}
再生成 toString() 方法,方便后面展示日志
@Overridepublic String toString() {return "Book{" +"name='" + name + '\'' +", price=" + price +", nowRead=" + nowRead +", readPage=" + readPage +'}';}
Book.aidl和Book.Java文件之所以同名,是因为我们要将两个文件进行关联,所以这两个文件的包名需要一致,并且,我们将Book.aidl的内容改写成:
// Book.aidlpackage com.example.marti.aidlstudy.aidl;//第一类AIDL文件//这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用// 注意:Book.aidl 与 Book.java 包名需要一致parcelable Book;
接下来创建一个AIDL接口,并且定义几个方法:
// BookManager.aidlpackage com.martin.aidlblog;// 第二类AIDL文件// 在同一包下,也需要导包import com.martin.aidlblog.Book;interface BookManager {// 定向tag == inBook inBook(in Book book);// 定向tag == outBook outBook(out Book book);// 定向 inoutBook inoutBook(inout Book book);// 获取书籍集合List<Book> getBooks();}
这两个.aidl文件创建完成,我们将项目 builder 一下,如果没有报错,那么AIDL的操作就成功了。
编辑器会自动将我们的第二类AIDL文件编译成.java文件,存放在以下目录:
我们定义的方法被编译后的样子:
// 定向tag == inpublic com.martin.aidlblog.Book inBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;// 定向tag == outpublic com.martin.aidlblog.Book outBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;// 定向 inoutpublic com.martin.aidlblog.Book inoutBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;// 获取书籍集合public java.util.List<com.martin.aidlblog.Book> getBooks() throws android.os.RemoteException;
现在创建的Service就没有路径限制了,我们在java包下,随便位置创建一个AIDLService文件。
我们怎么让Service和BookManager产生联系呢?
现在看BookManager.java文件,我们发现文件中的内部类 Stub继承了android.os.Binder:

我们立刻联想到Service里面的onBind()方法,返回值是IBinder,而IBinder是Binder的父类!
所以,Service内部逻辑:
public class AIDLService extends Service {// 维护一个 书籍 集合private List<Book> books = new ArrayList<>();public AIDLService() {}private BookManager.Stub manager = new BookManager.Stub() {@Overridepublic Book inBook(Book book) throws RemoteException {resetBook(book);return book;}@Overridepublic List<Book> getBooks() throws RemoteException {return books;}@Overridepublic Book outBook(Book book) throws RemoteException {resetBook(book);return book;}@Overridepublic Book inoutBook(Book book) throws RemoteException {resetBook(book);book.setNowRead(true);return book;}};// 重设 书籍 内容private void resetBook(Book book) {book.setPrice(91);book.setReadPage(23);books.add(book);}@Overridepublic IBinder onBind(Intent intent) {return manager;}@Overridepublic void onCreate() {super.onCreate();}}
Service的java文件内容写完,我们再到AndroidManifest.xml文件中,给Service添加意图过滤器:
<serviceandroid:name=".AIDLService"android:enabled="true"android:exported="true"><intent-filter><action android:name="com.martin.aidlblog" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></service>
大家都知道,Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets来配置不同文件的访问路径,从而加快查找速度——问题就出在这里。Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。那应该怎么办呢?
build.gradle文件里android内添加以下内容:
sourceSets {main {java.srcDirs = ['src/main/java', 'src/main/aidl']}}
这样Gradle在构建项目时,也会自动遍历aidl包。
我们将服务端的aidl包直接copy到客户端项目中,位置要一样,与java包平级。
所谓创建连接,就是打开服务端的服务:
private void bindService() {Intent intent = new Intent();intent.setAction("com.martin.aidlblog");intent.setPackage("com.martin.aidlblog");bindService(intent, connection, BIND_AUTO_CREATE);}private ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {manager = BookManager.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {connection = null;}};
到这里,我们已经实现了进程间通讯的功能。但是前面说的定向Tag好像并没有什么体现。
我们在客户端设置四个Button,分别调用我们在 BookManager.aidl里面设置的四个方法,整体代码如下:
public class MainActivity extends AppCompatActivity {private static final String TAG = "客户端";private BookManager manager;private TextView txtIn;private TextView txtGet;private TextView txtInout;private TextView txtOut;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);init();}private void init() {txtIn = findViewById(R.id.txt_in);txtGet = findViewById(R.id.txt_get);txtInout = findViewById(R.id.txt_inout);txtOut = findViewById(R.id.txt_out);}private void bindService() {Intent intent = new Intent();intent.setAction("com.martin.aidlblog");intent.setPackage("com.martin.aidlblog");bindService(intent, connection, BIND_AUTO_CREATE);}private ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {manager = BookManager.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {connection = null;}};public void inBook(View view) {try {Book sendBook = getBook();Book book = manager.inBook(sendBook);txtIn.setText(sendBook.toString());Log.e(TAG, "add: 客户端 书籍信息 :" + sendBook);Log.e(TAG, "add: 服务端 返回书籍信息:" + book);} catch (RemoteException e) {e.printStackTrace();}}public void outBook(View view) {try {Book sendBook = getBook();Book book = manager.outBook(sendBook);txtOut.setText(sendBook.toString());Log.e(TAG, "read: 客户端 书籍信息 :" + sendBook);Log.e(TAG, "read: 服务端 返回书籍信息:" + book);} catch (RemoteException e) {e.printStackTrace();}}public void inoutBook(View view) {try {Book sendBook = getBook();Book book = manager.inoutBook(sendBook);txtInout.setText(sendBook.toString());Log.e(TAG, "now: 客户端 书籍信息 :" + sendBook);Log.e(TAG, "now: 服务端 返回书籍信息:" + book);} catch (RemoteException e) {e.printStackTrace();}}public void get(View view) {try {List<Book> books = manager.getBooks();txtGet.setText(books.toString());Log.e(TAG, "get: 书籍数量 " + books.size());} catch (RemoteException e) {e.printStackTrace();}}public Book getBook() {return new Book("自动获取的名字", 66, false, 35);}@Overrideprotected void onResume() {super.onResume();bindService();}@Overrideprotected void onPause() {super.onPause();unbindService(connection);}}
先运行服务端,再运行客户端,分别点击按钮,查看获取的内容:
12-01 17:38:36.516 7536-7536/com.martin.aidlclientblog E/客户端: add:客户端 书籍信息 :Book{name='自动获取的名字', price=66, nowRead=false, readPage=35}12-01 17:38:36.516 7536-7536/com.martin.aidlclientblog E/客户端: add:服务端 返回书籍信息:Book{name='自动获取的名字', price=91, nowRead=false, readPage=23}
12-01 17:43:04.444 7536-7536/com.martin.aidlclientblog E/客户端: read:客户端 书籍信息 :Book{name='null', price=91, nowRead=false, readPage=23}12-01 17:43:04.444 7536-7536/com.martin.aidlclientblog E/客户端: read:服务端 返回书籍信息:Book{name='null', price=91, nowRead=false, readPage=23}
12-01 17:43:54.412 7536-7536/com.martin.aidlclientblog E/客户端: now:客户端 书籍信息 :Book{name='自动获取的名字', price=91, nowRead=true, readPage=23}12-01 17:43:54.412 7536-7536/com.martin.aidlclientblog E/客户端: now:服务端 返回书籍信息:Book{name='自动获取的名字', price=91, nowRead=true, readPage=23}
12-01 17:47:27.979 12139-12139/com.martin.aidlclientblog E/客户端: get: 书籍数量 3
我们打印一下获取的书籍信息:
12-01 17:47:27.976 12139-12139/com.martin.aidlclientblog E/客户端: get:这是原本的书籍 Book{name='自动获取的名字', price=66, nowRead=false, readPage=35}
现在结合上面写过的,定向Tag的特点以及图标,三个定向符的含义就更加清晰了。
AIDL在学习之前一直是我的心头大恨,一直不知从何入手,完全没有概念。
注意事项:
.aidl 文件放在同一包下