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 设计模式之观察者模式:

    什么是观察者模式?

    定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

    观察者模式结构图:

    

Android tracerote IP命令 android ipc方式_客户端

 

    根据上面的结构图,我们可以知道观察者模式中有如下角色:

    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);
}