AIDL基本介绍

AIDL全称Android Interface Define Language, 后缀名是.aidl. 其支持以下几个数据类型

  • 基本数据类型int, float, long, double, byte , short, boolean, char
  • String类型和CharSequence类型
  • List类型, 并其内元素可以是泛型. 但元素一定是可Parcelable类型或是其他AIDL支持的类型. 可选择将 List 用作 “通用” 类(例如,List<String>)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。
  • Map类型. 并其内元素不能是泛型, 元素一定AIDL支持的类型, 或者可Parcelable或者其他AIDL接口. 不支持通用 Map(如Map<String,Integer> 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。

一般有两种.aidl文件. 一种是定义可Parcelable的数据结构. 一种是定义方法接口.数据结构需要由我们自己来实现, 一般也会写在包内. 而接口方法由系统生成固定的接口, 然后在需要的地方再实现具体的业务逻辑, 实现该接口即可

一般我们会把AIDL文件全部都定义在一个package内. 这样才发送给客户端时, 只需把整个包发给他就 OK了. 但即使在同一个包内的aidl文件在使用其他包内的数据时也是要import才行.

在接口方法中定义的参数都必须带有tag标志们, Primitives基本类型和StringCharSequence默认是in而且只能是in. 其他可以为in, out, inout. 后面详解这三个tag

AIDL简单使用示例

首先定义两个AIDL文件

1
2
3
4
5
// Book.aidl
package info.ivicel.github.aidldemo;

// 注意是小写
parcelable Book;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package info.ivicel.github.aidldemo;

// 非基本数据类型需要导入
import info.ivicel.github.aidldemo.Book;

interface BookManager {
    // 测试不同的 tag 标志的影响
    // 返回值不需要 tag
    Book addBookIn(in Book book);
    Book addBookOut(out Book book);
    Book addBookInout(inout Book book);
}

再定义Book.aidl的实现Book.java. 需要注意的两点: 一是如果把Book.java定义在aidl包中, 一定要向build.gradle添加查找java文件的路径.

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

二是默认的Parcelable接口并不要求实现readFromParcel方法, 但在AIDL中, tag标签的out,inout需要其来实现写入流, 如果不实现这个方法, 则只能为in

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package info.ivicel.github.aidldemo;


import android.os.Parcel;
import android.os.Parcelable;

// 如果 java 的实现类是放在和 aidl 同一个包内
// 一定不能忘了把 java 源码的路径加入到 build.gradle 中, 否则会找不到文件

public class Book implements Parcelable {
    private String name;
    private int price;

    // 需要显示的定义一个无参的 constructor
    public Book() {}
    
    // getter and setter...

    protected Book(Parcel in) {
        readFromParcel(in);
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @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.writeInt(price);
        dest.writeString(name);
    }

    // 默认的 Parcelable 是没有规定要实现 readFromParcel 方法
    // 但如果不实现这个方法, Book 的 tag 只能为 in
    public void readFromParcel(Parcel dest) {
        price = dest.readInt();
        name = dest.readString();
    }
}

以上三个文件Book.aidl, Book.java, BookManager.aidlclientserver端都必须有一份.

rebuild,或是clean之后, 会在应用目录build/source/aidl下生成同名接口的java文件. 我们只要在server端根据具体的业务逻辑实现该接口中的方法即可. 然后使用Service来监听来自client的请求. 在client端来调用接口中的方法与server端进行通信

Server端实现一个Service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// AIDLService.java
public class AIDLService extends Service {
    private static final String TAG = "AIDLService";

    private List<Book> books = new ArrayList<>();

    // 由 AIDL 文件生成的 BookManager 接口的实现
    // 一般会有多个 client 连接到 server, 所以需要对 server 中的数据处理同步问题
    private final BookManager.Stub bookManager = new BookManager.Stub() {
        @Override
        public List<Book> getBooks() throws RemoteException {
            synchronized (this) {
                if (books != null) {
                    return books;
                }
            }
            return new ArrayList<>();
        }

        @Override
        public Book getBook() throws RemoteException {
            synchronized (this) {
                if (books == null) {
                    return null;
                }

                Random r = new Random(System.currentTimeMillis());
                int n = r.nextInt(books.size());
                return books.get(n);
            }
        }

        @Override
        public int getBookCount() throws RemoteException {
            synchronized (this) {
                if (books != null) {
                    return books.size();
                }
            }

            return 0;
        }

 		// ... 
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate: ");
        Book book = new Book();
        book.setName("Android开发艺术探索");
        book.setPrice(28);
        books.add(book);

        book = new Book();
        book.setName("Android编程权威指南");
        book.setPrice(55);
        books.add(book);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(getClass().getSimpleName(), String.format("on bind, intent = %s", intent.toString()));
        return bookManager;
    }
}

然后在Manifest.xml中定义Service, 如果Client是另一个程序的话, 需要定义一个隐式的Intent-filter来通知Service接收什么连接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!-- exported=true 表示能让非同应用的进程访问 -->
<!-- 为了安全最好还是定义一个 permission, 让拥有权限的应用访问 -->
<service
         android:name=".AIDLService"
         android:exported="true">
    <intent-filter>
        <!-- 定义一个 action, client 请求时使用, 需一致 -->
        <action android:name="info.ivicel.github.aidldemo.aidl"/>
        <!-- 定义一个 category, java 代码中系统会自动给添加上一个 DEFAULT -->
        <!-- 不定义会导致无法指收到请求. 或者定义其他的的 category -->
        <category android:category="android.intent.category.DEFAULT"/>
    </intent-filter>
</service>

Client端, 我们可以通过bindService()来获得BookManager的引用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// client 
// MainActivity.java 
public class MainActivity extends AppCompatActivity {
    
    private BookManager bookManager;
    private boolean bound = false;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
			// 在 bindService 之后我们就可以拿到 binder
            // 转为在 aidl 中定义好的接口, 就可以使用接口的方法
            bookManager = BookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bound = false;
            bookManager = null;
        }
    };
    
 	@Override
    protected void onCreated(Bundle saveInstanceState) {
     	super.onCreated(saveInstanceState);
        setContentView(R.layout.activity_main);
        
        if (!bound) {
            attempToBindService();
        }
    }
    
    private void attempToBindService() {
     	Intent intent = new Intent();
        intent.setAction("info.ivicel.github.aidldemo.aidl");
        intent.setPackage("info.ivicel.github.aidldemo");
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestory();
        if (bound) {
            unbindService(connection);
        }
    }
}
AIDL方法参数in, out, inout意义

这三个参数表示的是数据的流向, 都是从client来看server.

  • in表示数据从client流向server. server会从client接收到一个完整的对象, 但对该对象的修改不会使client端产生变化
  • out表示数据从server流向client. server端会从client端接收到一个空对象, 对该对象的操作将反应到client传入的对象
  • inout表示数据可双向流动, 以上两点的结合. 接收完整信息并反馈回client

依旧使用上一个例子来做一个实验

client中使用三个不同的tagserver添加新的对象, 并在server中对其进行修改. 然后分别返回这个新的值. 这些过程都打对象打印出来作对比

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// client
private void addBookIn() {
    if (checkServerService()) {
        return;
    }

    Book book = new Book();
    book.setName("new_book_in");
    book.setPrice(20);
    try {
        Log.d("OnClient", "before addBookIn client book = " + book);
        Book b2 = bookManager.addBookIn(book);
        Log.d("OnClient", "addBookIn return from server: " + b2);
        Log.d("OnClient", "after addBookIn client book = " + book);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

private void addBookOut() {
    if (checkServerService()) {
        return;
    }

    Book book = new Book();
    book.setName("new_book_out");
    book.setPrice(21);
    try {
        Log.d("OnClient", "before addBookOut client book = " + book);
        Book b2 = bookManager.addBookOut(book);
        Log.d("OnClient", "addBookOut return from server: " + b2);
        Log.d("OnClient", "after addBookOut client book = " + book);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

private void addBookInout() {
    if (checkServerService()) {
        return;
    }

    Book book = new Book();
    book.setName("new_book_inout");
    book.setPrice(22);
    try {
        Log.d("OnClient", "before addBookInOut client book = " + book);
        Book b2 = bookManager.addBookInout(book);
        Log.d("OnClient", "addBookInOut return from server: " + b2);
        Log.d("OnClient", "after addBookInOut client book = " + book);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

// server 
private static final BookManager.Stub bookManager = new BookManager.Stub {
 	// ....
    // 在 server 中, 分别都对传入进来的 book 名称加上 "_by_server`, 价格加 10, 然后返回
    @Override
    public Book addBookIn(Book book) throws RemoteException {
        Log.d("OnServer", "addBookIn: " + book);
        book.setName(book.getName() + "_by_server");
        book.setPrice(book.getPrice() + 10);
        books.add(book);
        return book;
    }

    @Override
    public Book addBookOut(Book book) throws RemoteException {
        Log.d("OnServer", "addBookOut: " + book);
        book.setName(book.getName() + "_by_server");
        book.setPrice(book.getPrice() + 10);
        books.add(book);
        return book;
    }

    @Override
    public Book addBookInout(Book book) throws RemoteException {
        Log.d("OnServer", "addBookInOut: " + book);
        book.setName(book.getName() + "_by_server");
        book.setPrice(book.getPrice() + 10);
        books.add(book);
        return book;
    }
}

server端打印的结果:

OnServer: addBookIn: Book{name='new_book_in’, price=20} OnServer: addBookOut: Book{name='null’, price=0} OnServer: addBookInOut: Book{name='new_book_inout’, price=22}

可以看出来, tagin时, 传进来的是一个完整的对象数据值. 为out时, 传进来的是一个默认初始化的对象. 为inout传进来的也是一个完整对象

client端的打印结果:

首先是tagin时. 返回的值说明server对对象有修改, 但client本身的原对象未发生变化. 说明server端是一个副本

OnClient: before addBookIn client book = Book{name='new_book_in’, price=20} OnClient: addBookIn return from server: Book{name='new_book_in_by_server’, price=30} OnClient: after addBookIn client book = Book{name='new_book_in’, price=20}

tagout时, server接收到的是一个默认初始化的对象, 数据并不同于client端, 但在server修改后, client会同步变化

OnClient: before addBookOut client book = Book{name='new_book_out’, price=21} OnClient: addBookOut return from server: Book{name='null_by_server’, price=10} OnClient: after addBookOut client book = Book{name='null_by_server’, price=10}

taginout时, 是以上两种的结合

OnClient: before addBookInOut client book = Book{name='new_book_inout’, price=22} OnClient: addBookInOut return from server: Book{name='new_book_inout_by_server’, price=32} OnClient: after addBookInOut client book = Book{name='new_book_inout_by_server’, price=32}

代码地址: GitHub

Reference:

  1. https://blog.csdn.net/luoyanglizi/article/details/51980630
  2. https://blog.csdn.net/luoyanglizi/article/details/51958091
  3. https://blog.csdn.net/luoyanglizi/article/details/52029091
  4. «Android 开发艺术探索»
  5. https://developer.android.com/guide/components/aidl.html?hl=zh-cn