十二、 Android 中的 IPC 方式(4) --- 使用 AIDL

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yz_cfm/article/details/90483404

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

猜你喜欢

转载自blog.csdn.net/yz_cfm/article/details/90483404