[关闭]
@martin0207 2017-12-01T10:44:27.000000Z 字数 9482 阅读 1128

AIDL

跨进程通讯


参考Blog

目录


AIDL(Android Interface Definition Language),接口定义语言

为什么要设计这门语言

实现进程间通讯(特定规格和特定方法下)

可传递的数据类型

定向Tag

AIDL的定向Tag表示在跨进程通讯中,信息的流向

Tag符 作用 表现
in 数据只能从客户端流向服务端 服务端可以接收到一个完整的对象,且客户端不会因为服务端对该对象的操作而改变
out 数据只能从服务端流向客户端 服务端会接收到该对象的空对象,但服务端对接收到的空对象有任何修改之后,客户端将会同步变动
inout 数据可以在服务端和客户端之间双向流通 服务端可以接收到客户端发过来的完整对象,并且客户端会同步服务端对该对象的所有操作

下面做个简单的图来展示:

Tag符 服务端是否接收到完整对象 客户端是否同步服务端对Object的操作
in ×
out ×
inout

注意: Java中的八种基本类型和String、CharSequence默认并且只能使用in

实现步骤

1 服务端配置

1.1 创建*.aidl文件

创建AIDL文件的界面

我们以 Book.aidl 为例,创建完成之后,Android Studio会自动给我们创建aidljava包同级

创建完成之后,编辑器自动为我们创建的包

创建后,里面的初始化代码为:

  1. // Book.aidl
  2. package com.martin.aidlblog;
  3. // Declare any non-default types here with import statements
  4. interface Book {
  5. /**
  6. * Demonstrates some basic types that you can use as parameters
  7. * and return values in AIDL.
  8. */
  9. void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
  10. double aDouble, String aString);
  11. }

1.2 创建数据Model

这时候,我们创建Book.java文件,在Book.aidl相同包下。写入成员变量

  1. private String name;
  2. private int price;
  3. private boolean nowRead;
  4. private int readPage;

并且创建成员变量的 set/get 方法,空构造方法以及方便创建对象的全有属性构造方法。
这是一个Model类的基本创建过程。但AIDL通讯中,数据对象必须是Parcelable对象,所以我们将Book.java实现Parcelable接口,最终代码如下:

  1. public class Book implements Parcelable{
  2. private String name;
  3. private int price;
  4. private boolean nowRead;
  5. private int readPage;
  6. public Book() {
  7. }
  8. public Book(String name, int price, boolean nowRead, int readPage) {
  9. this.name = name;
  10. this.price = price;
  11. this.nowRead = nowRead;
  12. this.readPage = readPage;
  13. }
  14. public String getName() {
  15. return name;
  16. }
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. public int getPrice() {
  21. return price;
  22. }
  23. public void setPrice(int price) {
  24. this.price = price;
  25. }
  26. public boolean isNowRead() {
  27. return nowRead;
  28. }
  29. public void setNowRead(boolean nowRead) {
  30. this.nowRead = nowRead;
  31. }
  32. public int getReadPage() {
  33. return readPage;
  34. }
  35. public void setReadPage(int readPage) {
  36. this.readPage = readPage;
  37. }
  38. protected Book(Parcel in) {
  39. name = in.readString();
  40. price = in.readInt();
  41. nowRead = in.readByte() != 0;
  42. readPage = in.readInt();
  43. }
  44. public static final Creator<Book> CREATOR = new Creator<Book>() {
  45. @Override
  46. public Book createFromParcel(Parcel in) {
  47. return new Book(in);
  48. }
  49. @Override
  50. public Book[] newArray(int size) {
  51. return new Book[size];
  52. }
  53. };
  54. @Override
  55. public int describeContents() {
  56. return 0;
  57. }
  58. @Override
  59. public void writeToParcel(Parcel dest, int flags) {
  60. dest.writeString(name);
  61. dest.writeInt(price);
  62. dest.writeByte((byte) (nowRead ? 1 : 0));
  63. dest.writeInt(readPage);
  64. }
  65. }

注意:这里默认生成的模板类,只能支持 in的定向Tag,因为生成的方法里面,只有writeToParcel(),如果想要支持outinout定向Tag的话,还需要实现readFromParcel()方法,但这个方法并不在Parcelable里面,需要我们手写。万幸,有writeToParcel()方法作为参考:

  1. @Override
  2. public void writeToParcel(Parcel dest, int flags) {
  3. dest.writeString(name);
  4. dest.writeInt(price);
  5. dest.writeByte((byte) (nowRead ? 1 : 0));
  6. dest.writeInt(readPage);
  7. }
  8. public void readFromParcel(Parcel dest) {
  9. //注意,此处的读值顺序应当是和writeToParcel()方法中一致的
  10. name = dest.readString();
  11. price = dest.readInt();
  12. nowRead = dest.readByte() == 1;
  13. readPage = dest.readInt();
  14. }

再生成 toString() 方法,方便后面展示日志

  1. @Override
  2. public String toString() {
  3. return "Book{" +
  4. "name='" + name + '\'' +
  5. ", price=" + price +
  6. ", nowRead=" + nowRead +
  7. ", readPage=" + readPage +
  8. '}';
  9. }

1.3 书写.aidl文件内容

Book.aidl和Book.Java文件之所以同名,是因为我们要将两个文件进行关联,所以这两个文件的包名需要一致,并且,我们将Book.aidl的内容改写成:

  1. // Book.aidl
  2. package com.example.marti.aidlstudy.aidl;
  3. //第一类AIDL文件
  4. //这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
  5. // 注意:Book.aidl 与 Book.java 包名需要一致
  6. parcelable Book;

接下来创建一个AIDL接口,并且定义几个方法:

  1. // BookManager.aidl
  2. package com.martin.aidlblog;
  3. // 第二类AIDL文件
  4. // 在同一包下,也需要导包
  5. import com.martin.aidlblog.Book;
  6. interface BookManager {
  7. // 定向tag == in
  8. Book inBook(in Book book);
  9. // 定向tag == out
  10. Book outBook(out Book book);
  11. // 定向 inout
  12. Book inoutBook(inout Book book);
  13. // 获取书籍集合
  14. List<Book> getBooks();
  15. }

这两个.aidl文件创建完成,我们将项目 builder 一下,如果没有报错,那么AIDL的操作就成功了。
编辑器会自动将我们的第二类AIDL文件编译成.java文件,存放在以下目录:
.aidl文件被编辑器编译存放位置
我们定义的方法被编译后的样子:

  1. // 定向tag == in
  2. public com.martin.aidlblog.Book inBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;
  3. // 定向tag == out
  4. public com.martin.aidlblog.Book outBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;
  5. // 定向 inout
  6. public com.martin.aidlblog.Book inoutBook(com.martin.aidlblog.Book book) throws android.os.RemoteException;
  7. // 获取书籍集合
  8. public java.util.List<com.martin.aidlblog.Book> getBooks() throws android.os.RemoteException;

1.4 创建服务端Service

现在创建的Service就没有路径限制了,我们在java包下,随便位置创建一个AIDLService文件。
我们怎么让ServiceBookManager产生联系呢?
现在看BookManager.java文件,我们发现文件中的内部类 Stub继承了android.os.Binder

编译后的内容

我们立刻联想到Service里面的onBind()方法,返回值是IBinder,而IBinderBinder的父类!
所以,Service内部逻辑:

  1. public class AIDLService extends Service {
  2. // 维护一个 书籍 集合
  3. private List<Book> books = new ArrayList<>();
  4. public AIDLService() {
  5. }
  6. private BookManager.Stub manager = new BookManager.Stub() {
  7. @Override
  8. public Book inBook(Book book) throws RemoteException {
  9. resetBook(book);
  10. return book;
  11. }
  12. @Override
  13. public List<Book> getBooks() throws RemoteException {
  14. return books;
  15. }
  16. @Override
  17. public Book outBook(Book book) throws RemoteException {
  18. resetBook(book);
  19. return book;
  20. }
  21. @Override
  22. public Book inoutBook(Book book) throws RemoteException {
  23. resetBook(book);
  24. book.setNowRead(true);
  25. return book;
  26. }
  27. };
  28. // 重设 书籍 内容
  29. private void resetBook(Book book) {
  30. book.setPrice(91);
  31. book.setReadPage(23);
  32. books.add(book);
  33. }
  34. @Override
  35. public IBinder onBind(Intent intent) {
  36. return manager;
  37. }
  38. @Override
  39. public void onCreate() {
  40. super.onCreate();
  41. }
  42. }

Service的java文件内容写完,我们再到AndroidManifest.xml文件中,给Service添加意图过滤器:

  1. <service
  2. android:name=".AIDLService"
  3. android:enabled="true"
  4. android:exported="true">
  5. <intent-filter>
  6. <action android:name="com.martin.aidlblog" />
  7. <category android:name="android.intent.category.DEFAULT" />
  8. </intent-filter>
  9. </service>

1.5 修改配置信息

大家都知道,Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets来配置不同文件的访问路径,从而加快查找速度——问题就出在这里。Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。那应该怎么办呢?

  1. sourceSets {
  2. main {
  3. java.srcDirs = ['src/main/java', 'src/main/aidl']
  4. }
  5. }

这样Gradle在构建项目时,也会自动遍历aidl包。

2 客户端配置

2.1 文件拷贝

我们将服务端的aidl包直接copy到客户端项目中,位置要一样,与java包平级。

2.2 创建连接

所谓创建连接,就是打开服务端的服务:

  1. private void bindService() {
  2. Intent intent = new Intent();
  3. intent.setAction("com.martin.aidlblog");
  4. intent.setPackage("com.martin.aidlblog");
  5. bindService(intent, connection, BIND_AUTO_CREATE);
  6. }
  7. private ServiceConnection connection = new ServiceConnection() {
  8. @Override
  9. public void onServiceConnected(ComponentName name, IBinder service) {
  10. manager = BookManager.Stub.asInterface(service);
  11. }
  12. @Override
  13. public void onServiceDisconnected(ComponentName name) {
  14. connection = null;
  15. }
  16. };

到这里,我们已经实现了进程间通讯的功能。但是前面说的定向Tag好像并没有什么体现。

2.3 展示 定向Tag

我们在客户端设置四个Button,分别调用我们在 BookManager.aidl里面设置的四个方法,整体代码如下:

  1. public class MainActivity extends AppCompatActivity {
  2. private static final String TAG = "客户端";
  3. private BookManager manager;
  4. private TextView txtIn;
  5. private TextView txtGet;
  6. private TextView txtInout;
  7. private TextView txtOut;
  8. @Override
  9. protected void onCreate(Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. setContentView(R.layout.activity_main);
  12. init();
  13. }
  14. private void init() {
  15. txtIn = findViewById(R.id.txt_in);
  16. txtGet = findViewById(R.id.txt_get);
  17. txtInout = findViewById(R.id.txt_inout);
  18. txtOut = findViewById(R.id.txt_out);
  19. }
  20. private void bindService() {
  21. Intent intent = new Intent();
  22. intent.setAction("com.martin.aidlblog");
  23. intent.setPackage("com.martin.aidlblog");
  24. bindService(intent, connection, BIND_AUTO_CREATE);
  25. }
  26. private ServiceConnection connection = new ServiceConnection() {
  27. @Override
  28. public void onServiceConnected(ComponentName name, IBinder service) {
  29. manager = BookManager.Stub.asInterface(service);
  30. }
  31. @Override
  32. public void onServiceDisconnected(ComponentName name) {
  33. connection = null;
  34. }
  35. };
  36. public void inBook(View view) {
  37. try {
  38. Book sendBook = getBook();
  39. Book book = manager.inBook(sendBook);
  40. txtIn.setText(sendBook.toString());
  41. Log.e(TAG, "add: 客户端 书籍信息 :" + sendBook);
  42. Log.e(TAG, "add: 服务端 返回书籍信息:" + book);
  43. } catch (RemoteException e) {
  44. e.printStackTrace();
  45. }
  46. }
  47. public void outBook(View view) {
  48. try {
  49. Book sendBook = getBook();
  50. Book book = manager.outBook(sendBook);
  51. txtOut.setText(sendBook.toString());
  52. Log.e(TAG, "read: 客户端 书籍信息 :" + sendBook);
  53. Log.e(TAG, "read: 服务端 返回书籍信息:" + book);
  54. } catch (RemoteException e) {
  55. e.printStackTrace();
  56. }
  57. }
  58. public void inoutBook(View view) {
  59. try {
  60. Book sendBook = getBook();
  61. Book book = manager.inoutBook(sendBook);
  62. txtInout.setText(sendBook.toString());
  63. Log.e(TAG, "now: 客户端 书籍信息 :" + sendBook);
  64. Log.e(TAG, "now: 服务端 返回书籍信息:" + book);
  65. } catch (RemoteException e) {
  66. e.printStackTrace();
  67. }
  68. }
  69. public void get(View view) {
  70. try {
  71. List<Book> books = manager.getBooks();
  72. txtGet.setText(books.toString());
  73. Log.e(TAG, "get: 书籍数量 " + books.size());
  74. } catch (RemoteException e) {
  75. e.printStackTrace();
  76. }
  77. }
  78. public Book getBook() {
  79. return new Book("自动获取的名字", 66, false, 35);
  80. }
  81. @Override
  82. protected void onResume() {
  83. super.onResume();
  84. bindService();
  85. }
  86. @Override
  87. protected void onPause() {
  88. super.onPause();
  89. unbindService(connection);
  90. }
  91. }

先运行服务端,再运行客户端,分别点击按钮,查看获取的内容:

  1. 12-01 17:38:36.516 7536-7536/com.martin.aidlclientblog E/客户端: add:
  2. 客户端 书籍信息 Book{name='自动获取的名字', price=66, nowRead=false, readPage=35}
  3. 12-01 17:38:36.516 7536-7536/com.martin.aidlclientblog E/客户端: add:
  4. 服务端 返回书籍信息:Book{name='自动获取的名字', price=91, nowRead=false, readPage=23}
  1. 12-01 17:43:04.444 7536-7536/com.martin.aidlclientblog E/客户端: read:
  2. 客户端 书籍信息 Book{name='null', price=91, nowRead=false, readPage=23}
  3. 12-01 17:43:04.444 7536-7536/com.martin.aidlclientblog E/客户端: read:
  4. 服务端 返回书籍信息:Book{name='null', price=91, nowRead=false, readPage=23}
  1. 12-01 17:43:54.412 7536-7536/com.martin.aidlclientblog E/客户端: now:
  2. 客户端 书籍信息 Book{name='自动获取的名字', price=91, nowRead=true, readPage=23}
  3. 12-01 17:43:54.412 7536-7536/com.martin.aidlclientblog E/客户端: now:
  4. 服务端 返回书籍信息:Book{name='自动获取的名字', price=91, nowRead=true, readPage=23}
  1. 12-01 17:47:27.979 12139-12139/com.martin.aidlclientblog E/客户端: get: 书籍数量 3

我们打印一下获取的书籍信息:

  1. 12-01 17:47:27.976 12139-12139/com.martin.aidlclientblog E/客户端: get:
  2. 这是原本的书籍 Book{name='自动获取的名字', price=66, nowRead=false, readPage=35}

现在结合上面写过的,定向Tag的特点以及图标,三个定向符的含义就更加清晰了。

总结

AIDL在学习之前一直是我的心头大恨,一直不知从何入手,完全没有概念。

注意事项:

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注