AIDL(Android Interface Definition Language),Android 接口定义语言。Android 中的 IPC 方式之一。前面说过的 IPC 方式有 Bundle、文件共享、Messenger,它们都有各自的局限性,比如 Messenger 因为处理消息是一个一个的处理,如果有大量的并发请求,使用它就不合适了。同时,Messenger 的作用主要就是传递消息,通过 Message 为载体,可以携带 Bundle 类型的数据进行传输,如果客户端想通过 Messenger 这种方式调用服务端的方法时,就无法实现这个功能。所以这个时候我们就可以使用 AIDL 这种方式,它可以实现跨进程的方法调用。虽然 Messenger 的底层实现也是 AIDL,但是系统对它进行了封装,使它只可以简单的完成特定的任务,方便我们使用。
下面我们详细看一下 AIDL 的使用。
AIDL 文件所支持的数据类型:
1. 基本数据类型(byte、int、short、long、char、boolean、float、double等)。
2. String 和 CharSequence(char 值的一个可读序列)。
3. List:只支持 ArrayList,里面每个元素都必须能够被 AIDL 支持。
4. Map:只支持 HashMap,里面的每个元素都必须能够被 AIDL 支持,包括 key 和 value。
5. Parcelable:所有实现了 Parcelable 接口的对象。
6. AIDL:所有的 AIDL 接口本身也可以在 AIDL 文件中使用。
这里我们需要注意:
① 如果 AIDL 文件中用到了我们自定义的实现了 Parcelable 接口的对象,必须新建一个和该对象同名的 AIDL 文件,并在其中声明它为 Parcelable 类型。
② 如果 AIDL 文件中要使用我们自定义的实现了 Parcelable 接口的对象,不管它们是否和当前的 AIDL 文件位于同一个包内,必须要将该对象显式的 import 进来。
③ AIDL 中除了基本数据类型,其它类型的参数必须标上参数:in、out、inout。其中 in 表示输入型参数,out 表示输出型参数,inout 表示输入输出型参数。
④ AIDL 接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口。
由于使用 AIDL 时,客户端和服务端中的 AIDL 文件必须完全一致,包括包名。所以为了方便 AIDL 开发,建议把所有和 AIDL 相关的类和文件全部放入同一个包中,这样,当客户端是另外一个应用时,就可以直接把整个包复制到客户端工程即可和服务端中 AIDL 接口保持完全一致。
使用 AIDL:
eg1:
AIDL 相关文件(在 aidl 目录下):
Book.aidl:
package com.cfm.aidltest;
parcelable Book; // 因为 Book 类是我们自定义的实现了 Parcelable 接口的类,而且在 AIDL 文件中使用到了,所以在这里要进行声明。
IBookManager.aidl:(AIDL 类型接口)
// IBookManager.aidl
package com.cfm.aidltest;
import com.cfm.aidltest.Book; // 注意这里要显式 import 进来 Book 类。
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
Book.java: (实现了 Parcelable 接口的 Book 类)
package com.cfm.aidltest;
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(){
}
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
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 String toString() {
return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
BookManagerService.java:(服务端实现代码)
package com.cfm.aidltest;
public class BookManagerService extends Service {
private static final String TAG = "cfmtest";
/**
* 我们这里使用的是 CopyOnWriteArrayList 类,它实现了 Serializable 接口,所以能够跨进程传输,同时它也实现了 List 接口,
* 所以它也属于 List 类型。而 AIDL 中支持 List 接口类型数据,所以我们可以使用 CopyOnWriteArrayList 类,只是虽然服务端返回
* 的是 CopyOnWriteArrayList,但是在 Binder 底层会按照 List 的规范去访问数据并最终形成一个新的 ArrayList 传递给客户端。
* (这也是为什么前面说 AIDL 中支持的 List 只能是 ArrayList,因为 Binder 经过底层转换之后返回给客户端的就是 ArrayList 对象)
*
* 为什么使用这个类?由于 AIDL 方法是在服务端的 Binder 线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程
* 同时访问的情形,所以我们要在 AIDL 方法中处理线程同步。而 CopyOnWriteArrayList 支持并发读/写,它的底层已经实现了线程
* 同步。与此类似的还有 ConcurrentHashMap。
*/
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
// 当客户端请求服务端时,服务端完成的具体任务
private IBinder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "服务端的 service 创建成功!");
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "ios"));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
ClientActivity.java:(客户端实现代码)
package com.cfm.aidltest;
public class ClientActivity extends AppCompatActivity{
private static final String TAG = "cfmtest";
// 绑定服务端服务
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 将服务端传过来的的 IBinder 类型参数转换成客户端 AIDL 接口类型以方便调用 AIDL 中的实现方法
IBookManager clientManager = IBookManager.Stub.asInterface(service);
try{
// 在客户端中为 服务端添加一本书
clientManager.addBook(new Book(3, "World Peace!"));
List<Book> list = clientManager.getBookList();
Log.d(TAG, "服务端返回给客户端的 List 类型: " + list.getClass().getCanonicalName());
Log.d(TAG, "客户端取到的 List 内容: " + list.toString());
}catch (RemoteException e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
// 绑定服务端服务
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 解绑服务
unbindService(conn);
}
}
AndroidManifest.xml:
...
<service
android:name=".BookManagerService"
android:enabled="true"
android:exported="true"
android:process=":remote">
</service>
... // 服务端与客户端不在一个进程中
Log 打印信息:
// 服务端
com.cfm.aidltest:remote/cfmtest: 服务端的 service 创建成功!
// 客户端
com.cfm.aidltest/cfmtest: 服务端返回给客户端的 List 类型: java.util.ArrayList
com.cfm.aidltest/cfmtest: 客户端取到的 List 内容: [[bookId:1, bookName:Android], [bookId:2, bookName:ios], [bookId:3, bookName:World Peace!]]
可以看到,虽然服务端使用的 List 类型为 CopyOnWriteArrayList 类,但是最后返回给客户端的是 ArrayList。还有,我们在客户端调用 addBook 方法为服务端的书单增加了一本书,可以看到也成功从服务端获取到了刚刚添加的这本书的信息。
下面引入一个 Java 中的设计模式,然后通过这个设计模式,再丰富一下上面的 Demo。
java 设计模式之观察者模式:
什么是观察者模式?
定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者模式结构图:
根据上面的结构图,我们可以知道观察者模式中有如下角色:
Subject: 抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个容器里,每个抽象主题都可以有任意数量的观察者,抽象主题还提供增加和删除观察者对象的接口(attach() 和 detach() 方法)。
ConcreteSubject: 具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
Observer: 抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题状态改变通知时更新自己。
ConcreteObserver: 具体观察者,实现抽象观察者定义的更新接口,当具体主题状态改变通知时,执行自己准备更新的内容。
下面举个简单的例子,妈妈是具体的被观察者,儿子和女儿是具体的观察者,妈妈把饭做好了之后,通知女儿和儿子吃饭:
Subject(抽象被观察者):
package com.cfm.observerpattern;
/**
* 抽象被观察者(Subject)
*/
public interface Subject {
// 增加一个吃饭的人
void attach(Observer observer);
// 减少一个吃饭的人
void detach(Observer observer);
// 通知所有人吃饭
void notifyEat();
}
ConcreteSubject(具体被观察者):
package com.cfm.observerpattern;
public class Mother implements Subject{
// 管理要通知吃饭的人的容器
private List<Observer> users = new ArrayList<>();
@Override
public void attach(Observer observer) {
users.add(observer);
}
@Override
public void detach(Observer observer) {
users.remove(observer);
}
@Override
public void notifyEat() {
System.out.println("妈妈饭做好了,在通知孩子们准备吃饭了!");
for(Observer observer : users){
observer.eat();
}
}
}
Observer(抽象观察者):
package com.cfm.observerpattern;
/**
* 抽象观察者 Observer
*/
public interface Observer {
void eat();
}
ConcreteObserver(具体观察者):
package com.cfm.observerpattern;
/**
* 具体观察者(ConcreteObserver)
*/
public class Son implements Observer {
private String name;
Son(String name){
this.name = name;
}
@Override
public void eat() {
System.out.println(this.name + " 收到了通知,准备开始吃饭!");
}
}
package com.cfm.observerpattern;
/**
* 具体观察者(ConcreteObserver)
*/
public class Daughter implements Observer {
private String name;
Daughter(String name){
this.name = name;
}
@Override
public void eat() {
System.out.println(this.name + " 收到了通知,准备开始吃饭了!");
}
}
测试代码:
package com.cfm.observerpattern;
public class Test {
public static void main(String[] args) {
// 创建一个具体被观察者(也就是妈妈,当妈妈把饭做好了之后,就通知儿子和女儿吃饭)
Mother mother = new Mother();
// 创建两个具体的观察者,并告诉妈妈,饭做好了要告诉我。
Son son = new Son("cfm");
Daughter daughter = new Daughter("ym");
mother.attach(son);
mother.attach(daughter);
// 饭做好了,妈妈通知孩子孩子们可以吃饭了
mother.notifyEat();
}
}
Log 信息:
妈妈饭做好了,在通知孩子们准备吃饭了!
cfm 收到了通知,准备开始吃饭!
ym 收到了通知,准备开始吃饭了!
--------------------------------------分割线--------------------------------------
现在我们开始丰富第一个 aidl 的 demo,需求: 当服务端有新书到来时,就会通知每一个申请了提醒功能的客户端。(这会用到上面说的观察者模式。)
1. 新建一个 IOnNewBookArrivedListener.aidl 文件,然后添加一个接口方法: onNewBookArrived()。这个就相当于上面的抽象观察者 Observer.java 以及里面的 update() 方法。为什么使用 AIDL 接口而不是普通接口呢?这是因为在 AIDL 中无法使用普通接口。注意,这个是客户端要具体实现的接口,所以它的方法运行在客户端的 Binder 线程池中。
IOnNewBookArrivedListener.aidl:
package com.cfm.aidltest;
import com.cfm.aidltest.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book book);
}
2.
IBookManager.aidl:(这个是抽象被观察者)
package com.cfm.aidltest;
import com.cfm.aidltest.Book;
import com.cfm.aidltest.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener); // 注册观察者
void unregisterListener(IOnNewBookArrivedListener listener); // 注销观察者
}
3.
Book.aidl:
package com.cfm.aidltest;
parcelable Book;
Book.java:
package com.cfm.aidltest;
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(){
}
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
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 String toString() {
return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
4. 在服务单开启一个线程,每隔 5s 就向书单中添加一本新书并通知注册了提醒功能的客户端。
BookManagerService.java:
package com.cfm.aidltest;
public class BookManagerService extends Service {
private static final String TAG = "cfmtest";
// 如果服务端服务被销毁,就停止往书单中加书的线程。
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
// 书单
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
// 存储具体观察者的容器,为什么使用 RemoteCallbackList,而不是普通的 List? 下面会具体分析。
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
// 具体的被观察者(ConcreteSubject),里面的方法运行在服务端的 Binder 线程池中。
private Binder mBinderWithConcreteSubject = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
// 返回书单
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
// 向书单中添加新书
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
// 客户端(具体观察者)向服务端(具体被观察者)注册新书到来时的提醒通知,以当服务端有新书到来时通知给客户端。
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
// 客户端(具体观察者)向服务端(具体被观察者)注销新书到来时的提醒通知
mListenerList.unregister(listener);
}
};
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "服务端的 service 创建成功!");
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "ios"));
// 开始一个每隔 5s 添加一本新书到书单的线程
new Thread(new addNewBookRunnable()).start();
}
@Override
public IBinder onBind(Intent intent) {
return mBinderWithConcreteSubject;
}
private void addNewBookAndNotifyClient(Book book) throws RemoteException {
// 通知所有注册了提醒通知的客户端,有新书到了。
mBookList.add(book); // 先添加一本新书,然后通知客户端有新书到了
Log.d(TAG, "服务端添加了一本新书: " + book.toString());
final int N = mListenerList.beginBroadcast(); // 返回服务端中注册的客户端数量
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener mClient = mListenerList.getBroadcastItem(i);
if (mClient != null) {
mClient.onNewBookArrived(book);
}
}
mListenerList.finishBroadcast();
}
private class addNewBookRunnable implements Runnable {
@Override
public void run() {
while (!mIsServiceDestoryed.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBookList.size() + 1;
Book newBook = new Book(bookId, "new book#" + bookId);
try {
addNewBookAndNotifyClient(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
5. 客户端要实现抽象观察者接口(IOnNewBookArrivedListener)并向服务端注册提醒功能通知,在退出的时候再注销。由于 IOnNewBookArrivedListener 接口中的方法运行在客户端的 Binder 线程池中,所以为了方便进行 UI 操作,我们创建了一个 Handler 来将其切换到客户端的主线程中执行。
ClientActivity.java:
package com.cfm.aidltest;
public class ClientActivity extends AppCompatActivity{
private static final String TAG = "cfmtest";
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoteServerBookManager;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MESSAGE_NEW_BOOK_ARRIVED:
Book newBook = (Book) msg.obj;
Log.d(TAG, "客户端收到服务端的新书提醒,提醒的新书为: " + newBook.toString());
}
super.handleMessage(msg);
}
};
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try{
mRemoteServerBookManager = bookManager; // 待会注销具体观察者时要用到
List<Book> list = bookManager.getBookList();
Log.d(TAG, "query book list: " + list.toString());
mRemoteServerBookManager.registerListener(mConcreteObsever);
}catch (RemoteException e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
// 具体的被观察者
private IOnNewBookArrivedListener mConcreteObsever = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book book) throws RemoteException {
// 由于这个方法运行在客户端的 Binder 线程池中,为了方便操作 UI。所以使用 Handler 切换到
// 客户端的主线程中执行。
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
// 绑定远程服务
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销提醒
if(mRemoteServerBookManager != null && mRemoteServerBookManager.asBinder().isBinderAlive()){
try {
Log.d(TAG, "客户端注销提醒成功!");
mRemoteServerBookManager.unregisterListener(mConcreteObsever);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(conn);
super.onDestroy();
}
}
AndroidManifest.xml:
...
<service
android:name=".BookManagerService"
android:enabled="true"
android:exported="true"
android:process=":remote">
</service>
...
Log 打印信息:
服务端:
2019-05-22 21:16:47.308 21611-21611/com.cfm.aidltest:remote D/cfmtest: 服务端的 service 创建成功!
2019-05-22 21:16:52.325 21611-21635/com.cfm.aidltest:remote D/cfmtest: 服务端添加了一本新书: [bookId:3, bookName:new book#3]
2019-05-22 21:16:57.337 21611-21635/com.cfm.aidltest:remote D/cfmtest: 服务端添加了一本新书: [bookId:4, bookName:new book#4]
2019-05-22 21:17:02.346 21611-21635/com.cfm.aidltest:remote D/cfmtest: 服务端添加了一本新书: [bookId:5, bookName:new book#5]
2019-05-22 21:17:07.356 21611-21635/com.cfm.aidltest:remote D/cfmtest: 服务端添加了一本新书: [bookId:6, bookName:new book#6]
客户端:
2019-05-22 21:16:47.388 21591-21591/com.cfm.aidltest D/cfmtest: query book list: [[bookId:1, bookName:Android], [bookId:2, bookName:ios]]
2019-05-22 21:16:52.335 21591-21591/com.cfm.aidltest D/cfmtest: 客户端收到服务端的新书提醒,提醒的新书为: [bookId:3, bookName:new book#3]
2019-05-22 21:16:57.345 21591-21591/com.cfm.aidltest D/cfmtest: 客户端收到服务端的新书提醒,提醒的新书为: [bookId:4, bookName:new book#4]
2019-05-22 21:17:02.354 21591-21591/com.cfm.aidltest D/cfmtest: 客户端收到服务端的新书提醒,提醒的新书为: [bookId:5, bookName:new book#5]
2019-05-22 21:17:07.363 21591-21591/com.cfm.aidltest D/cfmtest: 客户端收到服务端的新书提醒,提醒的新书为: [bookId:6, bookName:new book#6]
2019-05-22 21:17:09.490 21591-21591/com.cfm.aidltest D/cfmtest: 客户端注销提醒成功!
可以看到这里当客户端退出时,向服务端注销提醒功能成功,而这一切得益于 RemoteCallbackList 类。为什么要使用它呢?因为 Binder 会把客户端传递到服务端的 mConcreteObsever 转换成一个全新的对象,如果直接注销,会导致服务端找不到这个 mConcreteObsever 对象而失败。下面来看为什么使用 RemoteCallbackList 可以。
RemoteCallbackList 是 Android System 专门提供的用于删除跨进程 Listener 的接口。先看一下它的源码:
public class RemoteCallbackList<E extends IInterface>{ ... }
可以看到 RemoteCallbackList 是一个泛型,支持管理任意的 AIDL 接口( 也就是实现或者继承了 IInterface 接口的类 )。
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
IBinder key = listener.asBinder();
Callback value = new Callback(listener, cookie);
上面这个就是它的工作原理,在它的内部有一个 Map 结构专门用来保存所有的 AIDL 回调,这个 Map 的 key 是 IBinder 类型,value 是 Callback 类型。其中 Callback 中封装了真正的远程 listener。当客户端注册 listener 的时候,它会把这个 listener 的信息存入 mCallbacks。虽然跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是它们底层的 Binder 对象都是同一个。
RemoteCallback 的主要作用:
1. 当客户端解注册的时候,我们只要遍历服务端所有的 listener,找出那个和解注册 listener 具有相同 Binder 对象的服务端 listener 并把它删掉即可。
2. 当客户端进程终止后,它能够自动移除客户端所注册的 listener。
3. RemoteCallback 内部自动实现了线程同步的功能,所以我们使用它来注册和解注册时,不需要做额外的线程同步工作。
下面说一下需要注意的几点:
1. 使用 RemoteCallback 时,我们无法像操作 List 一样去操作它,从源码可以看到,它压根就不是一个 List 类型接口。遍历 RemoteCallback,必须按照下面的方式进行,其中 beginBroadcast 和 finishBroadcast 必须要配对使用,即使我们只是想获取 RemoteCallback 中的元素个数也要如此:
RemoteCallback mListenerList;
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
...
}
mListenerList.finishBroadcast();
2. 使用 AIDL 进行跨进程通信时,注意我们在客户端调用服务端中的方法时,由于是在 UI 线程中,并且当前线程会挂起直到等待服务端方法执行完毕后唤醒它,所以为了避免 ANR,一般都会在调用服务端方法时开一个子线程。
当服务端进程意外停止导致 Binder 死亡时,我们在客户端重新连接服务的两种方法:
1. 通过 linkToDeath() 和 unlinkToDeath() 方法,当服务端 Binder 死亡时,客户端会回调 DeathRecipient 接口中的 binderDied 方法,然后我们可以在这个方法中重新连接远程服务。
2. 在客户端的 onServiceDisconnected() 方法中重新连接远程服务。
这两个方法的区别是,onServiceDisconnected 运行在客户端的 UI 线程中,而 binderDied 运行在客户端的 Binder 线程池中。所以在 binderDied 方法中我们不能访问 UI。其它效果都是一样的。
最后,我们看一下关于权限验证功能,之前我们在分析 Binder 底层时说过除了传输性能高效还有一个特点就是安全性更高。我们可以在服务端实现 AIDL 接口时重写 onTransact() 方法,并通过 permission 和 UID/PID 来验证。
在 AndroidManifest.xml 中我们可以自定义权限,eg:
<permission android:name="com.cfm.aidltest.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
下面我们看一下具体的应用:
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
// 第一道: 通过 permission 验证
int check = checkCallingOrSelfPermission("com.cfm.aidltest.ACCESS_BOOK_SERVICE");
if( check == PackageManager.PERMISSION_DENIED){
return false; // 权限验证失败
}
// 第二道: 通过 packageName 验证
String packageName = null;
// 通过 getCallingUid 和 getCallingPid 可以得到客户端的 Uid 和 Pid
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if(packages != null && packages.length > 0){
packageName = packages[0];
}
if (packageName != null && !packageName.startsWith("com.cfm")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}