解锁进程间通信的各种姿势(1)AIDL

作者: 落魄的安卓开发 | 来源:发表于2018-01-17 17:10 被阅读53次

本文包含内容如下:

  1. 实现一个客户端调用远程服务的Demo,包括添加书籍addBook,获取书籍getBookList功能
  2. 在Demo基础上分析AIDL底层通过Binder来实现跨进程通信的流程

如果文中有不正确的地方还望指出,不要误人误己,感谢。学习自《Android开发艺术探索》

实现Demo

实现步骤:

  1. 定义Bean
  2. 定义AIDL接口
  3. 定义远程Service实现AIDL接口
  4. 客户端调用远程服务
Setp1 定义Bean:

说明:

AIDL接口支持的数据类型:

  1. 基本数据类型:int long char boolean double
  2. String和CharSequence
  3. List:只支持ArrayList,里面的每个元素都必须能够被AIDL支持
  4. Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
  5. Parcelable:所有实现了Parcelable接口的对象
  6. AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

Book.java,实现了Parcelable接口

public class Book implements Parcelable {

    private String bookName;
    private int bookId;

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

    protected Book(Parcel in) {
        bookName = in.readString();
        bookId = in.readInt();
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    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 int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(bookName);
        dest.writeInt(bookId);
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookName='" + bookName + '\'' +
                ", bookId=" + bookId +
                '}';
    }
}
Setp2 定义AIDL接口:

说明:

  1. 在AIDL中定义Parcelable和AIDL对象必须显示的import进来,不管他们是否位于和当前的AIDL文件位于同一个包内

  2. 如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型

     package com.thc.binderdemo
     parcelable Book;
    
  3. AIDL中除了基本数据类型,其他类型的参数必须标上方向:in(输入型参数)、out(输出型参数)、或者inout(输入输出型参数)

  4. AIDL只支持方法,不支持声明静态常量

所以我们除了定义IBookManager.aidl文件外还要定义Book.aidl,尽管它内部只是声明了parcelable Book

Book.aidl

// Book.aidl
package com.thc.binderdemo;

parcelable Book;

IBookManager.aidl

// IBookManager.aidl
package com.thc.binderdemo;

// Declare any non-default types here with import statements
import com.thc.binderdemo.Book;
import com.thc.binderdemo.IOnNewBookArrivedListener;
interface IBookManager {

    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}
Setp3 定义远程Service实现AIDL接口:

MyRemoteService.java

用到了CopyOnWriteArrayList,看注释即可

public class MyRemoteService extends Service {

    /**
     * 因为AIDL方法是在服务端的Binder线程池中执行的,因此多个客户端同时连接的时候会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步。
     * CopyOnWriteArrayList支持并发读/写,和CopyOnWriteArrayList相似的还有ConcurrentHashMap
     * AIDL中能够使用的List只有ArrayList,但是这里使用了CopyOnWriteArrayList(不继承ArraryList),这里为什么能够工作呢?
     * 因为 AIDL 中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,
     * 但是再Binder中会按照List的规范去访问数据并最终形成一个新的ArrayList返回给客户端。
     * 
     */
    CopyOnWriteArrayList<Book> copyOnWriteArrayList = new CopyOnWriteArrayList<>();

    public MyRemoteService() {
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //在Service创建后先添加两本书
        copyOnWriteArrayList.add(new Book("三国演义", 0));
        copyOnWriteArrayList.add(new Book("水浒传", 1));
    }


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

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return copyOnWriteArrayList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            copyOnWriteArrayList.add(book);
        }
    };
}

在AndroidManifest.xml中定义:

<service
    android:name=".MyRemoteService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote" />
Setp3 客户端调用远程服务:

客户端通过bindService来绑定远程服务,绑定成功后将服务端返回的Binder对象转成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了。

 public void bindRemoteService(View view) {
    connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iBookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> bookList = iBookManager.getBookList();
                Log.e("result", bookList.get(0).getBookName() + "--" + bookList.get(1).getBookName());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    bindService(new Intent(this, MyRemoteService.class), connection, BIND_AUTO_CREATE);
}

当然AIDL的使用不止这些内容,还有通过RemoteCallbackList实现一个观察者模式、远程服务死亡处理、权限验证等

Binder工作原理

从Android应用层来讲,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和AIDL服务。

所以AIDL的底层通信就是通过Binder来实现的。

分析步骤如下:

  1. 分析根据AIDL生成的Binder类
  2. Binder工作机制流程
Setp1 分析根据AIDL生成的Binder类:

在创建AIDL之后,SDK会自动给我们生成AIDL对应的Binder类,比如根据我们上述的IBookManager.adil生成的IBookManager.java这个Binder类。代码如下:

package com.thc.binderdemo;

public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.thc.binderdemo.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.thc.binderdemo.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.thc.binderdemo.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.thc.binderdemo.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.thc.binderdemo.IBookManager))) {
                return ((com.thc.binderdemo.IBookManager) iin);
            }
            return new com.thc.binderdemo.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.thc.binderdemo.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.thc.binderdemo.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.thc.binderdemo.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.thc.binderdemo.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.util.List<com.thc.binderdemo.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.thc.binderdemo.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.thc.binderdemo.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.thc.binderdemo.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
    public java.util.List<com.thc.binderdemo.Book> getBookList() throws android.os.RemoteException;
    public void addBook(com.thc.binderdemo.Book book) throws android.os.RemoteException;
}

注释:


IBookManager2.png
  1. 这是根据我们IBookManager.aidl生成的对应的IBookManager.java这个类,它继承了IInterface这个接口,同时自己也是一个接口,所有可以在Binder中传输的接口都需要继承IInterface接口
  2. 声明了两个方法getBookList和addBook,这是在IBookManager.aidl中声明的方法
  3. 同时声明了两个整型的id分别标识这两个方法
  4. 声明了一个内部类Stub,Stub是一个Binder类,当客户端和服务端位于同一个进程时,方法调用不会走跨进程的transact过程,两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成
  5. 核心是内部类Stub和Stub的内部代理Proxy

声明的内容注释:

  1. DESCRIPTOR:Binder的唯一标识一般用Binder的类名标识
  2. asInterface():将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象,这种转换过程是需要区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的是服务端的Stub对象本身,否则返回的是系统封装后的Stub.Proxy对象
  3. asBinder():此方法用于返回当前Binder对象
  4. onTransact():这个方法运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过底层封装后交给此方法来处理
  5. Proxy#getBookList/addBook:这个方法运行在客户端

注意:

  1. 客户端发起请求时,由于当前线程会被挂起知道服务端进程返回数据,所以如果一个远程方法是耗时的,那么不能再UI线程发起此次远程请求
  2. 由于服务端的Binder方法运行在Binder线程池中,所以Binder方法不管是耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了
Setp2 Binder工作机制流程:
  1. 定义AIDL,SDK根据我们定义的AIDL文件生成对应的Binder类

  2. 在客户端通过bindService调用远程服务,在生成的Binder类内部根据是否处于同一个进程,将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象,返回给客户端

  3. 客户端拿到Binder对象就可以调用服务端的方法,比如调用了getBookList这个方法

  4. 进入getBookList方法中执行顺序如下:

    1. 创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List;

    2. 把该方法的参数信息写入_data中(如果有参数的话)

    3. 调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起(在此之前的操作还是在客户端线程,从下一步开始在服务端Binder线程池执行)

       //transact调用示例
       @Override
       public java.util.List<com.thc.binderdemo.Book> getBookList() throws android.os.RemoteException {
           android.os.Parcel _data = android.os.Parcel.obtain();
           android.os.Parcel _reply = android.os.Parcel.obtain();
           java.util.List<com.thc.binderdemo.Book> _result;
           try {
               _data.writeInterfaceToken(DESCRIPTOR);
               /**
                 *Stub.TRANSACTION_getBookList 标识方法的int值
                 *_data : 方法需要的参数
                 *_reply: 结果
                 */
               mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
               _reply.readException();
               _result = _reply.createTypedArrayList(com.thc.binderdemo.Book.CREATOR);
           } finally {
               _reply.recycle();
               _data.recycle();
           }
           return _result;
       }
      
    4. 服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果

       //onTransact调用示例
       @Override
       public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
           switch (code) {
               case INTERFACE_TRANSACTION: {
                   reply.writeString(DESCRIPTOR);
                   return true;
               }
               /**
                * onTransact调用示例:
                *
                *  1. 根据方法标识的值,判断客户端调用的是哪个方法
                *  2. 取出参数data(该方法没有用到,在addBook中有用到)
                *  3. 将返回的结果写到reply中
                *  4. 最后有个boolean类型的返回值,如果返回false,客户端的请求就会失败,可以利用这里来做权限验证
                */
               case TRANSACTION_getBookList: {
                   data.enforceInterface(DESCRIPTOR);
                   java.util.List<com.thc.binderdemo.Book> _result = this.getBookList();
                   reply.writeNoException();
                   reply.writeTypedList(_result);
                   return true;
               }
               case TRANSACTION_addBook: {
                   data.enforceInterface(DESCRIPTOR);
                   /**
                    * 取出参数data来作为参数去执行目标方法
                    */
                   com.thc.binderdemo.Book _arg0;
                   if ((0 != data.readInt())) {
                       _arg0 = com.thc.binderdemo.Book.CREATOR.createFromParcel(data);
                   } else {
                       _arg0 = null;
                   }
                   this.addBook(_arg0);
                   reply.writeNoException();
                   return true;
               }
           }
           return super.onTransact(code, data, reply, flags);
       }
      
    5. 返回_reply中的结果

一图解千愁


binder.jpg

相关文章

网友评论

    本文标题:解锁进程间通信的各种姿势(1)AIDL

    本文链接:https://www.haomeiwen.com/subject/cyotkxtx.html