Android AIDL跨进程通讯解析例子

前言

本文通过一个模拟书店的小项目来向大家一步一步展示Android中通过AIDL实现跨进程通讯,虽然项目虽小,但是五脏俱全,通过本篇博客的学习,可以让大家迅速掌握AIDL的使用和了解Android跨进程通讯的一些基本知识,为接下来学习binder这块打下基础。

项目介绍

首先看一下项目的目录结构:

可以看到有三个Module:

  • BookStock:图书存储,数据保存在本地数据库(该项目是没有界面的);
  • BookStore:书店展示,提供图书的增删改查功能;
  • model:存放实体类和aidl文件。

这里的BookStore模块和BookStock模块是通过跨进程通讯实现数据交互的,下面看下项目截图:

就是这么简单,两个界面,一个列表一个详情。

开始

一、创建AIDL文件

1.首先创建Book实体类Book.java:

public class Book implements Parcelable{

    /**
     * id
     */
    private Long id;
    /**
     * 名称
     */
    private String name;
    /**
     * 描述
     */
    private String desc;
    /**
     * 价格
     */
    private int price;

    ......

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeValue(this.id);
        dest.writeString(this.name);
        dest.writeString(this.desc);
        dest.writeInt(this.price);
    }

    public void readFromParcel(Parcel in){
        this.id = (Long) in.readValue(Long.class.getClassLoader());
        this.name = in.readString();
        this.desc = in.readString();
        this.price = in.readInt();
    }


    protected Book(Parcel in) {
        this.id = (Long) in.readValue(Long.class.getClassLoader());
        this.name = in.readString();
        this.desc = in.readString();
        this.price = in.readInt();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
}

因为跨进程设计到序列化的过程,这里当然是使用Android特定的序列化方式,实现Parcelable接口,Parcelable的实现主要是把对象写进Parcel和从Parcel里恢复对象,而Parcel是跨进程数据传输的媒介。

2.创建aidl文件:

创建Book.aidl

// Book.aidl
package cn.xdeveloper.model;  
parcelable Book;  

在aidl里使用到的对象都需要有一份类型声明。

创建IBookManager.aidl

package cn.xdeveloper.model;  
import cn.xdeveloper.model.Book;

interface IBookManager {

    //加载所有
    List<Book> loadAll();

    //删除
    boolean delete(long id);

    //添加或更新
    boolean addOrUpdate(inout Book book);
}

这里定义跨进程调用的接口:这里的数据类型仅支持java基本数据类型、List、Map和自己声明的parcelable 对象。

在boolean addOrUpdate(inout Book book);可以看到有一个inout修饰,这个东西是干什么的呢?
它表示的是数据的流向:
我们知道跨进程通讯是分为服务端和客户端,这个数据流向是站在客户端的角度上看的,它有三个取值:

  • in :标识对象可以从客户端传到服务端;
  • out :标识标识对象只能从服务端传到客户端;
  • inout:标识对象可以在服务端和客户端之间互传。

可能还是有一点抽象,比如这里的boolean addOrUpdate(inout Book book); inout表示的意思是book对象可以传入到服务端,如果不设置in的话,服务端接收到的将会是一个空的Book对象,out表示如果服务端对该对象进行了修改,会回传给客户端。这里设置inout的意义就是:在新增书本的时候book对象是没有ID的,ID是在服务端赋值后回传回来的。

3.查看生成的aidl文件

写好aidl文件后build--make project就会自动生成binder文件,这里是IBookManager.java,格式化一下,虽然第一眼看上去代码多,但是里面的结构还是挺清晰的,现在我们逐个分析里面的方法。

package cn.xdeveloper.model;

/**
 * 我们定义的接口
 */
public interface IBookManager extends android.os.IInterface {


    /**
     * Binder对象,存在于服务端
     */
    public static abstract class Stub extends android.os.Binder implements cn.xdeveloper.model.IBookManager {

        /**
         * Binder的标识
         */
        private static final java.lang.String DESCRIPTOR = "cn.xdeveloper.model.IBookManager";

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

        /**
         * 接口转化,如果是同一个进程调用则返回该Binder,否则返回Binder的代理对象
         * @param obj
         * @return
         */
        public static cn.xdeveloper.model.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof cn.xdeveloper.model.IBookManager))) {
                return ((cn.xdeveloper.model.IBookManager) iin);
            }
            return new cn.xdeveloper.model.IBookManager.Stub.Proxy(obj);
        }

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

        /**
         * 服务端响应
         * @param code  调用方法编号
         * @param data  传入的数据
         * @param reply 返回的数据
         * @param flags
         * @return
         * @throws android.os.RemoteException
         */
        @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_loadAll: {
                    data.enforceInterface(DESCRIPTOR);
                    //调用具体实现
                    java.util.List<cn.xdeveloper.model.Book> _result = this.loadAll();
                    reply.writeNoException();
                    //返回数据
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_delete: {
                    data.enforceInterface(DESCRIPTOR);
                    long _arg0;
                    _arg0 = data.readLong();
                    boolean _result = this.delete(_arg0);
                    reply.writeNoException();
                    reply.writeInt(((_result) ? (1) : (0)));
                    return true;
                }
                case TRANSACTION_addOrUpdate: {
                    data.enforceInterface(DESCRIPTOR);
                    cn.xdeveloper.model.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = cn.xdeveloper.model.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    boolean _result = this.addOrUpdate(_arg0);
                    reply.writeNoException();
                    reply.writeInt(((_result) ? (1) : (0)));
                    if ((_arg0 != null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        /**
         * Binder代理对象,供客户端调用
         */
        private static class Proxy implements cn.xdeveloper.model.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<cn.xdeveloper.model.Book> loadAll() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<cn.xdeveloper.model.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //远程调用
                    mRemote.transact(Stub.TRANSACTION_loadAll, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(cn.xdeveloper.model.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
//删除

            @Override
            public boolean delete(long id) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                boolean _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeLong(id);
                    mRemote.transact(Stub.TRANSACTION_delete, _data, _reply, 0);
                    _reply.readException();
                    _result = (0 != _reply.readInt());
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
//添加或更新

            @Override
            public boolean addOrUpdate(cn.xdeveloper.model.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                boolean _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addOrUpdate, _data, _reply, 0);
                    _reply.readException();
                    _result = (0 != _reply.readInt());
                    if ((0 != _reply.readInt())) {
                        book.readFromParcel(_reply);
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_loadAll = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_delete = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_addOrUpdate = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    }
//加载所有

    public java.util.List<cn.xdeveloper.model.Book> loadAll() throws android.os.RemoteException;
//删除

    public boolean delete(long id) throws android.os.RemoteException;
//添加或更新

    public boolean addOrUpdate(cn.xdeveloper.model.Book book) throws android.os.RemoteException;
}

这个类里面主要定义了两个对象Stub和Proxy:Stub是Binder类,运行在服务端,提供跨进程通讯,而Proxy是Binder的代理对象,负责与代理的Binder通讯。

二、服务端Service

这里主要就一个StockService

public class BookStockService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "---------onBind--------");

        return mBinder;
    }

    private IBinder mBinder = new IBookManager.Stub() {

        @Override
        public List<Book> loadAll() throws RemoteException {
            Realm realm = Realm.getDefaultInstance();
            try {
                List<Book> bookList = realm.where(Book.class).findAll();
                Log.d(TAG, "loadAll:" + bookList.toString());
                return realm.copyFromRealm(bookList);
            } finally {
                realm.close();
            }
        }

        @Override
        public boolean delete(final long id) throws RemoteException {
            Realm realm = Realm.getDefaultInstance();

            boolean result = false;
            try {
                realm.beginTransaction();
                result = realm.where(Book.class).equalTo("id", id).findAll().deleteAllFromRealm();
                realm.commitTransaction();
            } catch (Exception e) {
                e.printStackTrace();
                realm.cancelTransaction();
            } finally {
                realm.close();
            }
            return result;
        }

        @Override
        public boolean addOrUpdate(final Book book) throws RemoteException {
            Realm realm = Realm.getDefaultInstance();
            boolean result = false;

            if (book.getId() == null) {
                book.setId(realm.where(Book.class).max("id").longValue() + 1);
            }

            try {
                realm.beginTransaction();
                realm.insertOrUpdate(book);
                realm.commitTransaction();
                result = true;
            } catch (Exception e) {
                e.printStackTrace();
                realm.cancelTransaction();
            } finally {
                realm.close();
            }
            return result;
        }

}

实例化一个Stub对象并实现接口内容,在onBind()方法中返回改对象。这里数据库的存储用到Realm,使用方法很简单,这里就不多叙述了。这里要注意的是,这里的方法是运行在服务端的Binder线程池中的,如果要处理耗时操作是不需要开启子线程的。

最后不要忘了在AndroidMenifest.xml中注册Service,定义好action,提供隐式调用:

<service  
    android:name=".BookStockService"
    android:exported="true">
    <intent-filter>
        <action android:name="cn.xdeveloper.book.stock.BookStockService" />
    </intent-filter>
</service>  
三、客户端BookStore

客户端通过BindService的方式绑定远程服务:

private ServiceConnection mServiceConnection = new ServiceConnection() {  
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookManager = IBookManager.Stub.asInterface(service);
            try {
                mBookAdapter.setNewData(mBookManager.loadAll());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected:" + name);
        }
    };
        Intent intent = new Intent();
        intent.setPackage("cn.xdeveloper.book.stock");
        intent.setAction("cn.xdeveloper.book.stock.BookStockService");
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);

通过设置Service的package和action绑定,现在就可以通过远程接口访问对应的方法了。以上就是最基本的远程Service调用的过程,当然,为了让我们的程度更加健壮,还是有许多细节需要处理的,接下来我们继续看。

四、DeathRecipient死亡代理

当服务端进程意外终止的时候,我们可以通过DeathRecipient来接受到回调,在回调里面重新绑定服务。

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookManager = IBookManager.Stub.asInterface(service);
            try {
                //设置死亡代理
                service.linkToDeath(mDeathRecipient, 0);

                mBookAdapter.setNewData(mBookManager.loadAll());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected:" + name);
        }
    };
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if (mBookManager == null) return;

            mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);

            //重新绑定
            bindBookStock();
        }
    };
五、RemoteCallbackList

这里的跨进程通讯都是用客户端主动发起,但我们开发过程中经常需要服务端来主动通知客户端,比如:仓库没货了需要主动通知书店没货了。

这里来看看如果通过RemoteCallbackList来实现通知回调的:

首先定义通知接口的aidl

package cn.xdeveloper.model;

// Declare any non-default types here with import statements
import cn.xdeveloper.model.Book;

interface IOnStockCallback {

    //通知
    void onNotify(String msg);
}

这里定义了一个通知方法,发送一个字符串类型的通知。

在IBookManager.aidl中增加注册和解绑接口:

package cn.xdeveloper.model;

// Declare any non-default types here with import statements
import cn.xdeveloper.model.Book;  
import cn.xdeveloper.model.IOnStockCallback;

interface IBookManager {

     //加载所有
     List<Book> loadAll();

     //删除
     boolean delete(long id);

     //添加或更新
     boolean addOrUpdate(inout Book book);

     //注册回调
     void registerListener(IOnStockCallback callback);

     //解除回调
     void unregisterListener(IOnStockCallback callback);
 }

客户端修改:

    private IOnStockCallback mOnStockCallback = new IOnStockCallback.Stub() {
        @Override
        public void onNotify(String msg) throws RemoteException {
            Log.d(TAG,"onNotify,msg:" + msg);
        }
    };
private ServiceConnection mServiceConnection = new ServiceConnection() {  
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookManager = IBookManager.Stub.asInterface(service);
            try {
                //设置死亡代理
                service.linkToDeath(mDeathRecipient, 0);

                mBookAdapter.setNewData(mBookManager.loadAll());
                mBookManager.registerListener(mOnStockCallback);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected:" + name);
        }
    };

这里调用mBookManager.registerListener(mOnStockCallback);因为IOnStockCallback 也是一个Binder对象,可以通过跨进程传输,此时对于IOnStockCallback这个接口来说,BookStore这边是服务端了,因为它是真正的Stub类,在BookStockService中接受到的将会是IOnStockCallback代理类的。

现在看看BookStockService的修改:

//远程回调列表
private RemoteCallbackList<IOnStockCallback> mCallBackList = new RemoteCallbackList<>();

private IBinder mBinder = new IBookManager.Stub() {

       ... ...

        @Override
        public void registerListener(IOnStockCallback callback) throws RemoteException {
            mCallBackList.register(callback);
        }

        @Override
        public void unregisterListener(IOnStockCallback callback) throws RemoteException {
            mCallBackList.unregister(callback);
        }
    };

这里通过RemoteCallbackList保持着IOnStockCallback引用,这里有人会有疑问,为什么不直接用一个List来引用呢?因为对于IOnStockCallback这个接口来说,BookStoreService是客户端,客户端接收到的binder对象其实是binder的一个代理类,而且方法是运行在binder的线程池中的,所以registerListener传过来的IOnStockCallback对象和unregisterListener时候传过来的IOnStockCallback对象是不同的,虽然调用的时候传的是同一个。因为这个原因,两个不同的对象,当然不能unregisterListener了,因为List无法匹配原来的对象。

我们这里可以看看RemoteCallbackList里的实现,其实很简单:

public class RemoteCallbackList<E extends IInterface> {  
    /*package*/ ArrayMap<IBinder, Callback> mCallbacks
            = new ArrayMap<IBinder, Callback>();

    private Object[] mActiveBroadcast;
    private int mBroadcastCount = -1;
    private boolean mKilled = false;

    private final class Callback implements IBinder.DeathRecipient {
        final E mCallback;
        final Object mCookie;

        Callback(E callback, Object cookie) {
            mCallback = callback;
            mCookie = cookie;
        }

        public void binderDied() {
            synchronized (mCallbacks) {
                mCallbacks.remove(mCallback.asBinder());
            }
            onCallbackDied(mCallback, mCookie);
        }
    }


    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }


    public boolean unregister(E callback) {
        synchronized (mCallbacks) {
            Callback cb = mCallbacks.remove(callback.asBinder());
            if (cb != null) {
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
                return true;
            }
            return false;
        }
    }
}

可以看到RemoteCallbackList里面持有一个ArrayMap的容器,它的key是IBinder。 我们看看register方法:

        IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }

这里我们可以看到IBinder binder = callback.asBinder(); 虽然IOnStockCallback代理对象是不同的,但是他们对应的asBinder()是一样的,所亦可以通过这个IBander对象来区分引用。这里还为IBinder设置了死亡代理,Callback继承了DeathRecipient,我们看下Callback的实现:

    private final class Callback implements IBinder.DeathRecipient {
        final E mCallback;
        final Object mCookie;

        Callback(E callback, Object cookie) {
            mCallback = callback;
            mCookie = cookie;
        }

        public void binderDied() {
            synchronized (mCallbacks) {
                mCallbacks.remove(mCallback.asBinder());
            }
            onCallbackDied(mCallback, mCookie);
        }
    }

可以看到,当远程Binder死亡的时候会从Map中remove()掉,那么其实我们这里可以不需要调用unregister()来反注册了,只要让远程进程退出即可。

最后在我们的服务端只需要调用RemoteCallbackList中的回调函数就可以实现回调客户端了:

 /**
     * 发送通知到书店
     * @param msg
     */
    private void notifyStore(String msg){
        int size = mCallBackList.beginBroadcast();
        for (int i = 0; i < size; i++) {
            IOnStockCallback callback = mCallBackList.getBroadcastItem(i);
            if(callback != null){
                try {
                    callback.onNotify(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mCallBackList.finishBroadcast();
    }

这里逐个的取出IOnStockCallback对象并调用onNotify(msg)方法。

结束

好了,这样我们一个进程间通讯的小Demo就完成了,没讲太多原理性的东西,主要是让大家能熟悉使用,讲得有点乱,不太明白的朋友把Demo多跑几遍。

Demo地址:GitHub