【Android 笔记】AIDL 实现 IPC

Author Avatar
vecrates 12月 14, 2017

 AIDL是 Android 定义语言,可用其实现跨进程的数据传输。AIDL 的定义和 Java 的接口定义相似。

AIDL 和 Messenger

  • AIDL 是 Messenger 的底层实现
  • Messenger 只能顺序执行,不适合并发的情况
  • Messenger 不支持跨进程调用其他进程方法,而 AIDL 可以

    AIDL 支持的数据类型
     基本类型、CharSequence、List(ArrayList)、Map(HashMap)、 Parcelable、AIDL

AIDL 的组成

  • AIDL 接口
    定义一 AIDL 接口和方法,这个接口时服务端面向客户端的接口,这个接口在服务端中实现。在定义参数时还要加上:in、out、inout 表示输入还是输出
  • 服务端
    实现 AIDL 接口,在客户端绑定服务时(onBind() 方法)中返回该 AIDL 的实现,供客户端调用
  • 客户端
    发起绑定服务,获得服务端传回的对象,可调用该对象提供的方法

使用

客户端调用服务端

1)这里传输一个自定义的对象 Book,为了让它 Book 能被 AIDL 传输,实现 Parcelable 接口,然后在 AIDL 文件中声明。

public class Book implements Parcelable{

    private int bookId;
    private String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    //从序列化后的对象中创建原始对象
    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
        Log.v("_v", "反序列化 " + bookId + " " + bookName);
    }

    //反序列化
    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];
        }
    };

    //内容描述,含有文件描述符返回1.否则为0
    @Override
    public int describeContents() {
        return 0;
    }

    //序列化
    @Override
    public void writeToParcel(Parcel parcel, int i) {
        Log.v("_v", "序列化 " + bookId + " " + bookName);
        parcel.writeInt(bookId);
        parcel.writeString(bookName);
    }

    @Override
    public String toString() {
        return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
    }
}
//Book.aidl 文件
parcelable Book;

2)创建 AIDL 接口文件

//需要显式地导入Book.aidl
import cn.vecrates.ipc.aidl.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

3)服务端实现接口方法 (还要在 Manifest 中指定 android:process

public class BookManagerService extends Service {

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    private IBinder mBookManager = 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();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBookManager;
    }
}

4)客户端实现,调用服务端方法

public class BookManagerActivity extends AppCompatActivity {

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            IBookManager bookManager = IBookManager.Stub.asInterface(iBinder);
            try {
                bookManager.addBook(new Book(1, "android"));
                bookManager.addBook(new Book(2, "ios"));
                List<Book> list = bookManager.getBookList();
                Log.v("_v", "客户端获取到服务端数据 " + list.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {}
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);

        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }
}
增加服务器调用客户端

 不知道这么说合不合适,不过我是这么理解的。服务端调用客户端实际上定义了一个 AIDL,然后在客户端实现这个接口,客户端把接口对象传给给服务端,服务端调用这个接口对象的方法。
1)在上面的基础上添加一个接口,以及增加原接口两个方法

//IOnBookArrivedListener.aidl
import cn.vecrates.ipc.aidl.Book;

interface IOnBookArrivedListener {
    void onNewBookArrived(in Book book);
}

//IBookManager.aidl
import cn.vecrates.ipc.aidl.Book;
import cn.vecrates.ipc.aidl.IOnBookArrivedListener;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnBookArrivedListener listener);
    void unregisterListener(IOnBookArrivedListener listener);
}

2)客户端实现接口

public class BookManagerActivity extends AppCompatActivity {

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.v("_v", "客户端 连接成功");
            IBookManager bookManager = IBookManager.Stub.asInterface(iBinder);
            try {
                bookManager.addBook(new Book(1, "android"));
                bookManager.addBook(new Book(2, "ios"));
                List<Book> list = bookManager.getBookList();
//                Log.v("_v", "客户端获取到服务端数据 " + list.toString());
                //在服务端中注册
                bookManager.registerListener(mOnBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {}
    };

    private IOnBookArrivedListener mOnBookArrivedListener = new IOnBookArrivedListener.Stub() {

        @Override
        public void onNewBookArrived(Book book) throws RemoteException {
            Log.v("_v", "客户端收到通知 " + book.toString());
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);

        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }
}

3)服务器实现。
原书籍 Demo 是想实现一个观察者模式,在服务端中使用 List 存储客户端实现的接口对象。由于 List.remove() 无法移除里面对象,所以使用 RemoteCallbackList

public class BookManagerService extends Service {

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    //存储客户端的监听对象,为了能移除其中的对象只能用RemoteCallbackList
    private RemoteCallbackList<IOnBookArrivedListener> mListenerList = new RemoteCallbackList<>();

    private IBinder mBookManager = 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(IOnBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
            Log.v("_v", "客户端注册成功");
            //客户端注册成功后通知客户端
            Book book = new Book(001, "这是一本新书");
            notifyClients(book);
        }

        @Override
        public void unregisterListener(IOnBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
            Log.v("_v", "客户端解除注册");
        }
    };

    //通知所有客户端(这儿只有一个客户端)
    private void notifyClients(Book book) {
        //RemoteCallbackList 遍历的的形式
        Log.v("_v", "通知客户端 " + book.toString());
        int size = mListenerList.beginBroadcast();
        for (int i=0; i<size; i++) {
            IOnBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            if(listener != null) {
                try {
                    //调用客户端
                    listener.onNewBookArrived(book);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mListenerList.finishBroadcast();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.v("_v", "服务端 onCreate()");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBookManager;
    }
}

5)结果
5892091-eee40f97ede3320c

更新 UI 注意事项
  • 不在 UI 线程中访问服务端的耗时方法,否则可能 ANR
    被调用的方法运行在服务器的 Binder 线程池,调用者(客户端)线程会被挂起直至该方法执行完毕,所以如果服务端方法比较耗时,将会 ARN

  • 服务端不应该调用客户端中在 UI 线程中的耗时方法(可以运行在非 UI 线程),否则可能导致服务端无响应

重新连接服务的方法
  • 设置 DeathRecipient 监听
  • 在ServiceConnection 的 onServiceDisconnected() 中重新连接

这两种方式前者运行在客户端 Binder 线程池中,后者运行在 UI 线程中

AIDL 权限验证
  • 在 onBind() 中验证
  • 在 onTransact() 中验证