2020-02-13-Android跨进程通信Binder

2020-02-14  本文已影响0人  耿望

传统Linux的IPC通信

传统的IPC通信,由于不同进程间的隔离,用户空间的数据是不能共享的,需要通过内核空间实现数据交换。比如进程A和进程B想要通信,首先进程A将数据通过copy_from_user拷贝到内核空间,然后进程B通过copy_to_user从内核空间拷贝到用户空间。
一次通信需要两次数据拷贝。


IPC.jpg

Binder通信的好处

BInder通信在内核空间开辟了两块缓存区,一块区域负责数据接收,一块负责数据发送,两块内存区域的地址建立了映射关系,同时在用户空间B建立了一块区域映射到数据接收缓存区。这样当用户空间A通过copy_from_user将数据拷贝到内核空间的时候,用户空间B就接收到了同样的数据。
一次通信只需要一次数据拷贝。

IBinder对象实现数据传输

在本地服务中使用Binder比较简单,只是通过重写Binder的transact()和onTransact()方法实现数据传输。

  1. 服务端定义一个Binder的子类,在onBind()方法中返回这个Binder对象。
public class BinderService extends Service {

    public static final String INTERFACE_NAME = "IReporter";
    public static final int CODE_REPORT = 1;
    private static final String TAG = "BinderService";
    private Reporter mReporter = null;

    public BinderService() {
        mReporter = new Reporter();
    }

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

    public final class Reporter extends Binder {

        @Override
        protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
            switch (code) {
                case CODE_REPORT:
                    data.enforceInterface(INTERFACE_NAME);
                    String key = data.readString();
                    int value = data.readInt();
                    int result = report(key, value);
                    reply.writeInterfaceToken(INTERFACE_NAME);
                    reply.writeInt(result);
                    return true;
            }
            return super.onTransact(code, data, reply, flags);
        }

        public int report(String key, int values) {
            Log.d(TAG, "report key=" + key + " value=" + values);
            return 0;
        }
    }
}
  1. 客户端通过bindService()获取IBinder对象,由IBinder.transact()调用服务端的onTransact()方法,实现数据传输。
    实际上传输的数据是Parcel对象,创建Parcel对象的时候,不是通过new方法调用构造函数,而是使用了Parcel.obtain(),这里应该用到了对象的缓存池,类似Message的obtain()方法。Parcel的具体实现大部分是在native层,这里不深入,只是记得最后要调用recycle方法进行对象的回收。
public class BinderClient {

    private static final String TAG = "BinderClient";
    private IBinder mReporterBinder = null;

    public void bindReporter(Context context) {
        Intent intent = new Intent(context, BinderService.class);
        context.bindService(intent, new BinderConnection(), Context.BIND_AUTO_CREATE);
    }

    public void report(String key, int value) {
        if (mReporterBinder != null) {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            data.writeInterfaceToken(BinderService.INTERFACE_NAME);
            data.writeString(key);
            data.writeInt(value);
            try {
                mReporterBinder.transact(BinderService.CODE_REPORT, data, reply, 0);
                reply.enforceInterface(BinderService.INTERFACE_NAME);
                int result = reply.readInt();
                Log.d(TAG, "report result=" + result);
            } catch (RemoteException e) {
                Log.e(TAG, "Fail to transact e=" + e);
            } finally {
                data.recycle();
                reply.recycle();
            }
        }
    }

    private class BinderConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected ComponentName=" + name);
            mReporterBinder = service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mReporterBinder = null;
        }
    }
}

这里调用的transact()有两种方式,flag=0和flag=FLAG_ONEWAY。默认情况下,flag=0,客户端线程会阻塞直到服务端返回。

AIDL实现数据传输

Android Interface Definition Language (AIDL)是Android对进程间通信的封装。
Android Studio提供了自动生成aidl接口的方法,只需要新建一个aidl文件,编译项目后就得到一个同名的Java文件。
比如新建一个IReportInterface.aidl文件。

interface IReportInterface {
    int report(String key, int values);
}

编译后得到IReportInterface.java,里面有个内部静态抽象类继承了Binder,为我们做好了Parcel数据打包的过程,不需要再手动编码实现。
这里先不看IReportInterface的具体内容。先看下服务端和客户端的实现代码。
服务端的Binder类改成了继承IReportInterface.Stub,不需要再重写onTransact方法,因为aidl已经自动生成了代码。

public class BinderService extends Service {

    private static final String TAG = "BinderService";
    private Reporter mReporter = null;

    public BinderService() {
        mReporter = new Reporter();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
    }

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

    public final class Reporter extends IReportInterface.Stub {
        public int report(String key, int values) {
            Log.d(TAG, "report key=" + key + " value=" + values);
            return 0;
        }
    }
}

客户端也不再需要通过transact方法传输数据,而是通过IBinder对象直接获得Service的实例,直接调用report方法。

public class BinderClient {

    private static final String TAG = "BinderClient";
    private IReportInterface mReporter = null;

    public void bindReporter(Context context) {
        Intent intent = new Intent(context, BinderService.class);
        context.bindService(intent, new BinderConnection(), Context.BIND_AUTO_CREATE);
    }

    public void report(String key, int value) {
        Log.d(TAG, "report key = " + key + " value = " + value);
        if (mReporter != null) {
            try {
                mReporter.report(key, value);
            } catch (RemoteException e) {
                Log.e(TAG, "Fail report e=" + e);
            }
        }
    }

    private class BinderConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected ComponentName=" + name);
            mReporter = IReportInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //
        }
    }
}

最后再来看下自动生成的代码,是怎么实现数据传输的。
有三个内部类Default,Stub和Proxy。


Binder (1).jpg

先看下Stub.asInterface()方法,它的作用是把一个IBinder对象转化成IInterface对象。有三种返回结果,如果IBinder为空,返回null;如果能获取到本地的IInterface对象,返回本地IInterface;兜底方案是新构建一个Proxy对象返回。
实际上如果客户端和服务端在同一个进程,asInterface()方法返回的就是服务端创建的Stub对象,是不需要调用onTransact()方法对Parcel数据进行打包和解包操作的,因为不需要跨进程。

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

只有获取不到Stub对象,才会构造一个Proxy类,重写report()方法,对发送的数据进行打包。

            @Override
            public int report(java.lang.String key, int values) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(key);
                    _data.writeInt(values);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_report, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        return getDefaultImpl().report(key, values);
                    }
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

然后在服务端通过onTransact()对数据进行解包,这就是为什么客户端和服务端都需要保存一份AIDL文件。因为它负责数据的打包和解包,双方必须一模一样。

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_report: {
                    data.enforceInterface(descriptor);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.report(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }
上一篇 下一篇

猜你喜欢

热点阅读