1. 前言
最近看了 AIDL 的使用方法, 为了加深对 AIDL 的理解, 所以有了手动去编写 AIDL 生成的 Java 文件. 不需要系统自动帮我们创建文件. 大家看的时候, 可以对照着 AIDL 生成的 Java 文件去看, 看看有哪些不同的地方.
为了模拟进程间通信, 我们为 service 单独设置一个进程. 这里我是将 service 和 client 写在了同一个 App 里面.
2. 清单文件和实体类
2.1 清单文件
- <service
- Android:name=".binder.BookManagerService"
- Android:enabled="true"
- Android:exported="false"
- Android:process=":remote" />
- ......
- <service
- Android:name=".binder.BookManagerService"
- Android:enabled="true"
- Android:exported="false"
- Android:process="com.sample.binderstudy.remote" />
这里通过 process 为 service 指定另外一个进程, 用 ":" 指定的进程, 是在进程名前面加上当前应用的包名, 并且使用 ":" 指定的进程, 属于当前应用的私有进程, 其他应用的组件不可以和它跑在同一个进程中. 而通过 "." 去指定完整的进程名, 这个进程属于全局的进程, 这个时候开启的进程是允许其他进程通过 UID 与它跑在同一个进程的. 两种写法都可以, 我们随意选择一种就好了.
2.2 实体类
- package com.sample.binderstudy.binder;
- import Android.os.Parcel;
- import Android.os.Parcelable;
- class Book implements Parcelable {
- private int price;
- private String name;
- public Book(int price, String name) {
- this.price = price;
- this.name = name;
- }
- protected Book(Parcel in) {
- price = in.readInt();
- name = 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];
- }
- };
- public int getPrice() {
- return price;
- }
- public void setPrice(int price) {
- this.price = price;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- @Override
- public String toString() {
- return "Book{" +
- "price=" + price +
- ", name='" + name + '\'' +
- '}';
- }
- @Override
- public int describeContents() {
- return 0;
- }
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(price);
- dest.writeString(name);
- }
- }
进行传输的实体类, 需要实现 Parcelable 接口.
3. 服务端
- 3.1 IBookManager
- import Android.os.RemoteException;
- import java.util.List;
- public interface IBookManager extends IInterface {
- String DESCRIPTOR = "com.sample.binderstudy.binder.IBookManager";
- int TRANSACTION_addBook = (Android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
- int TRANSACTION_listBook = (Android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
- void addBook(Book book) throws RemoteException;
- List<Book> listBook() throws RemoteException;
- }
这里 IBookManager 继承 IInterface.DESCRIPTOR 用来描述 Binder 的唯一标识, 一般以当前 Binder 的类名标识. 接口里面定义了两个方法, 并且定义了两个 int 变量, 用来标识请求的是哪一个方法.
- 3.2 BookManagerImpl
- package com.sample.binderstudy.binder;
- import Android.os.Binder;
- import Android.os.IBinder;
- import Android.os.IInterface;
- import Android.os.Parcel;
- import Android.os.RemoteException;
- import Android.support.annotation.NonNull;
- import Android.util.Log;
- import java.util.ArrayList;
- import java.util.List;
- public class BookManagerImpl extends Binder implements IBookManager {
- private static final String TAG = "BookManagerImpl";
- private List<Book> books = new ArrayList<>();
- public BookManagerImpl() {
- this.attachInterface(this, DESCRIPTOR);
- }
- @Override
- public IBinder asBinder() {
- return this;
- }
- public static IBookManager asInterface(Android.os.IBinder obj) {
- if (obj == null) {
- return null;
- }
- IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
- if ((iin != null) && (iin instanceof IBookManager)) {
- return (IBookManager) iin;
- }
- return null;
- }
- @Override
- public void addBook(Book book) throws RemoteException {
- Log.d(TAG, "service addBook :" + book.toString());
- books.add(book);
- }
- @Override
- public List<Book> listBook() throws RemoteException {
- Log.d(TAG, "service listBook :");
- return books;
- }
- /**
- * 服务端, 接收远程消息, 处理 onTransact 方法
- **/
- @Override
- protected boolean onTransact(int code, @NonNull Parcel data, Parcel reply, int flags) throws RemoteException {
- switch (code) {
- case INTERFACE_TRANSACTION: {
- reply.writeString(DESCRIPTOR);
- return true;
- }
- case TRANSACTION_addBook: {
- data.enforceInterface(DESCRIPTOR);
- Book _arg0;
- if ((0 != data.readInt())) {
- _arg0 = Book.CREATOR.createFromParcel(data);
- } else {
- _arg0 = null;
- }
- this.addBook(_arg0);
- reply.writeNoException();
- return true;
- }
- case TRANSACTION_listBook: {
- data.enforceInterface(DESCRIPTOR);
- java.util.List<Book> _result = this.listBook();
- reply.writeNoException();
- reply.writeTypedList(_result);
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
- }
这里需要注意的就是 asInterface 方法, 这个方法是用来将服务端的 Binder 对象转化为客户端所需要的 IBookManager 接口对象, 如果服务器和客户端位于相同的进程, 则可以直接进行转化, 如果不是, 那么需要进行远程调用. 这里是直接返回的 null, 为了更好的去区分服务端与客户端, 所以将 Proxy 类单独抽出来了.
由于这里的 BookManagerImpl 是一个常规类, 方法是直接在里面实现的, 与 AIDL 文件自动生成的 Java 文件不同的是, Stub 是一个抽象类, 继而将方法接着抽象了, 将方法转移到了 Service 里面让用户自己去实现.
3.3 BookManagerService package com.sample.binderstudy.binder; import Android.App.Service; import Android.content.Intent; import Android.os.IBinder; import Android.os.RemoteException; public class BookManagerService extends Service { private final BookManagerImpl bookManager = new BookManagerImpl(); @Override public void onCreate() { super.onCreate(); new Thread(new AutoTask()).start(); } @Override public IBinder onBind(Intent intent) { return bookManager; } private class AutoTask implements Runnable { @Override public void run() { int index = 0; boolean done = false; try { while (!done) { index++; bookManager.addBook(new Book(index, "Hello" + index)); Thread.sleep(2000); if (index == 50) { done = true; } } } catch (RemoteException | InterruptedException e) { e.printStackTrace(); } } } }
因为代码都在 BookManagerImpl 里面实现了, 所以服务里面的代码异常的简洁, 这与我们通过 AIDL 去写的时候有一点不同, 不同的是实现的方法转移到 BookManagerImpl 里面去了.
在 onCreate 方法里面启动了一个线程, 去模拟书籍的添加.
4. 客户端
4.1 BookManagerActivity package com.sample.binderstudy.binder; import Android.content.ComponentName; import Android.content.Context; import Android.content.Intent; import Android.content.ServiceConnection; import Android.os.IBinder; import Android.os.RemoteException; import Android.support.v7.App.AppCompatActivity; import Android.os.Bundle; import Android.util.Log; import com.sample.binderstudy.R; public class BookManagerActivity extends AppCompatActivity { private static final String TAG = "BookManagerActivity"; IBookManager bookManager; ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bookManager = BookManagerImpl.asInterface(service); Log.d(TAG, (bookManager == null) + ""); if (bookManager == null) { bookManager = new BookManagerProxy(service); } try { for (Book book : bookManager.listBook()) { Log.e(TAG, "book :" + book.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_book_manager); bindService(new Intent(this, BookManagerService.class), serviceConnection, Context.BIND_AUTO_CREATE); } }
代码也比较简洁, 在 onCreate 里面去绑定我们的远程服务, 这个时候 onServiceConnected 里面会返回远程服务的 Binder 对象, 然后通过 BookManagerImpl.asInterface(service) 去转化这个 Binder 对象, 如果返回值为空, 表示请求的是远程服务, 那么我们需要自己手动去初始化 BookManagerProxy, 然后在这个类里面去进程远程调用.
通过 AIDL 生成的 Java 类, 是直接在 asInterface 里面去调用的 Proxy 这个类, 这个类严格意义上来说是跑在客户端的进程里面的, 所以我才将其从 BookManagerImpl 里面抽出来了. AIDL 那么做, 应该是出于一个封装的思路吧, 并且将接口里面要实现的方法抛给了用户自己去实现, 这真的是一个很 nice 的思路, 反正我是写不出来的~
4.2 BookManagerProxy package com.sample.binderstudy.binder; import Android.os.Binder; import Android.os.IBinder; import Android.os.Parcel; import Android.os.RemoteException; import java.util.List; public class BookManagerProxy extends Binder implements IBookManager{ private IBinder mRemote; BookManagerProxy(IBinder mRemote) { this.mRemote = mRemote; } @Override public IBinder asBinder() { return mRemote; } public String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public void addBook(Book book) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((book != null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(IBookManager.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public List<Book> listBook() throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); List<Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(TRANSACTION_listBook, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } }
在 BookManagerProxy 里面, mRemote.transact() 去进行远程操作请求. 方法的实现则是在 BookManagerImpl 的 onTransact 方法里面, 这样就完成了进程间的通信.
5. 总结
总体来说, 代码与 AIDL 文件生成的类文件并无差异, 只是类的结构稍有改变, 通过 AIDL 生成的 Java 文件, 创建的 Stub 是一个抽象类, 这个类将方法的实现抛给了用户自己去实现, 而我是直接在 BookManagerImpl 里面实现的, 然后将 Stub.Proxy 类给分离出来了, 给拿到了客户端. 虽然我的这种做法比较麻烦, 但是是为了更好的理解 AIDL 才这么去修改代码结构的.
最后呢, AIDL 的底层也还是使用 Binder 来实现的, 这个比较复杂, 有机会一探究竟.
来源: http://www.jianshu.com/p/6e279de58613