Android Binder学习笔记(二)

Android Binder学习笔记(一)接上篇添加图书和获取图书列表的例子。

向服务端注册成为观察者

我们希望每当服务端有新书到来的时候能主动通知客户端,这样就不需要客户端去轮询了。

首先声明一个AIDL接口,用来注册、通知观察者。这里选择使用AIDL接口是因为在AIDL中无法使用普通接口。

IOnNewBookArriveListener.aidl

// IOnNewBookArriveListener.aidl
package com.hm.aidlserver;
import com.hm.aidlserver.Book;
// Declare any non-default types here with import statements

interface IOnNewBookArriveListener {

 void onNewBookArrived(in Book newBook);

}

修改IBookManager.aidl

// IBookManager.aidl
package com.hm.aidlserver;

import com.hm.aidlserver.Book;
import com.hm.aidlserver.IOnNewBookArriveListener;

interface IBookManager {

   ListgetBookList();
   void addBook(in Book book);
   //注册观察者
   void registerListener(IOnNewBookArriveListener listener);
  //取消注册
   void unRegisterListener(IOnNewBookArriveListener listener);
}

修改服务端的Service,实现registerListener和unRegisterListener方法

public class BookManagerService extends Service {

    //...
    private RemoteCallbackList mListenerList = new RemoteCallbackList<>();

    private Binder mBinder = new IBookManager.Stub() {

        //...
        @Override
        public void registerListener(IOnNewBookArriveListener listener) throws RemoteException {
            mListenerList.register(listener);
        }

        @Override
        public void unRegisterListener(IOnNewBookArriveListener listener) throws RemoteException {
            mListenerList.unregister(listener);
        }
       //...
    };
    //...

    /**
     * 如果IOnNewBookArriveListener#onNewBookArrived(Book newBook)方法是耗时方法的话
     * notifyBookArrived不能运行在服务端的住线程
     * 通知观察者有新书到来
     */
    private void notifyBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        final int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArriveListener l = mListenerList.getBroadcastItem(i);
            if (l != null) {
                l.onNewBookArrived(book);
            }
        }
        mListenerList.finishBroadcast();
    }
    //...
}

修改客户端的实现

private IBookManager bookManager;

private IOnNewBookArriveListener listener = new IOnNewBookArriveListener.Stub() {
    @Override
    public void onNewBookArrived(Book newBook) throws RemoteException {
        //当前线程是在客户端的Binder线程池中运行的
        Log.e(TAG, "onNewBookArrived: " + Thread.currentThread().getName());
        //如果需要更新UI的话,使用handler的方式来实现
        mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
    }
};

private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.e(TAG, "onServiceConnected:");
        bookManager = IBookManager.Stub.asInterface(service);
        try {
            getBookList();
            //向服务端注册成为观察者
            bookManager.registerListener(listener);
        } catch (RemoteException e) {
            e.printStackTrace();
            Log.e(TAG, "onServiceConnected: error" + e.getMessage());
        }
    }
};

注意两点:

  1. 如果明确知道某个远程方法是耗时的,客户端就要避免在UI线程中去调用远程方法。

  2. 客户端的onServiceConnected方法和onServiceDisconnected是运行在主线程的,所以也不能在这两个方法中调用远程方法。

  3. 当远程服务端调用客户端的listener中的方法时,如果被调用的方法也是耗时方法,那么也不能在服务端的主线程调用。例如:如果IOnNewBookArriveListener#onNewBookArrived(Book newBook)方法是耗时方法的话,我们也不能在服务端的主线程调用。

Binder死亡时,重新连接服务

  1. 使用IBinder.DeathRecipient
    /**
     * 当Binder死亡时,我们会收到通知,这个时候,我们可以重新发起连接请求从而恢复链接。
     */
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.e(TAG, "binderDied: " + Thread.currentThread().getName());
            if (bookManager != null) {
                bookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
                bookManager = null;
            }
            //重新连接
            bind();
        }
    };

    //...
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "onServiceConnected:");
            bookManager = IBookManager.Stub.asInterface(service);
            try {
                //在这里设置DeathRecipient监听
                service.linkToDeath(mDeathRecipient, 0);
                getBookList();
                bookManager.registerListener(mOnNewBookArriveListener);
            } catch (RemoteException e) {
                e.printStackTrace();
                Log.e(TAG, "onServiceConnected: error" + e.getMessage());
            }
        }
    };

如果我们设置了DeathRecipient监听,当Binder死亡的时候,我们会收到binderDied方法的回调,在binderDied方法中我们可以重连远程服务。

  1. 在ServiceConnection的onServiceDisconnected方法中重新连接
private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "onServiceConnected:");
            bookManager = IBookManager.Stub.asInterface(service);
            try {
                getBookList();
                bookManager.registerListener(mOnNewBookArriveListener);
            } catch (RemoteException e) {
                e.printStackTrace();
                Log.e(TAG, "onServiceConnected: error" + e.getMessage());
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bookManager = null;
            Log.e(TAG, "onServiceDisconnected: " + Thread.currentThread().getName());
            //重新连接
            bind();
        }
    };

这两者的区别在于ServiceConnection的onServiceDisconnected方法运行在客户端的主线程;IBinder.DeathRecipient 的binderDied方法运行在客户端的Binder线程池中。

跨进程通信使用权限验证功能

第一种验证方式

我们可以在服务端的Service的onBind方法中验证,验证不通过就返回null。这样客户端就无法绑定服务。验证方式有多种,比如使用permission验证。使用这种方式,首先要在AndridManifest.xml文件中声明绑定服务所需要权限,比如


BookManagerService的onBind方法

@Override
public IBinder onBind(Intent intent) {
    //在这里做权限验证,如果验证不通过就返回null
    int check = checkCallingOrSelfPermission("com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE");
    if (check == PackageManager.PERMISSION_DENIED) {
        Log.e(TAG, "onBind: permission denied");
        return null;
    }
    Log.e(TAG, "onBind: permission granted");
    return mBinder;
}

如果我们想要成功绑定服务端的服务,就需要在客户端的AndridManifest.xml中声明所需要的权限


第二种验证方式

在服务端的Binder中的onTransact方法中验证,如果验证不通过,返回false。这样服务端就不会执行AIDL中的方法,从而达到保护服务端的效果。验证方式有很多,我们在下面的方法中我们验证了permission和包名。

private Binder mBinder = new IBookManager.Stub() {
    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            /**
             * 验证permission
             */
            Log.e(TAG, "onTransact 验证权限");
            int check = checkCallingOrSelfPermission("com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE");
            if (check == PackageManager.PERMISSION_DENIED) {
                Log.e(TAG, "onTransact: permission denied");
                return false;
            }
            /**
             * 验证包名
             */
            String packageName;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
                if (!packageName.startsWith("com.hm")) {
                    Log.e(TAG, "onTransact: package verify failed");
                    return false;
                }
            } else {
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }
};

Binder连接池

Binder连接池的主要作用就是将客户端的每个业务模块的Binder请求统一发送到远程Service中去执行,从而避免了重复创建Service的过程,不然的话,每个Binder对象就得对应一个Service。

在这种模式下,整个工作流程是这样的:服务端提供一个queryBinder接口,这个接口能够根据业务模块的标志来返回相应的Binder对象给客户端,客户端拿到所需的Binder对象后就可以进行远程方法调用了。对服务端来说,只需要一个Service就可以了。

举个例子:客户端中的一个业务模块需要远程调用加解密字符串的功能,另一个业务模块需要使用计算加法的功能。

声明两个AIDL文件

ISecurityCenter.aidl

// ISecurityCenter.aidl
package com.hm.aidlserver;

// Declare any non-default types here with import statements

interface ISecurityCenter {

   String encrypt(String content);
   String decrypt(String password);
}

ICompute.aidl

// ICompute.aidl
package com.hm.aidlserver;

// Declare any non-default types here with import statements

interface ICompute {

   int add(int a,int b);
}

两个接口的实现

SecurityCenterImpl

public class SecurityCenterImpl extends ISecurityCenter.Stub {

    private static final String TAG = "SecurityCenterImpl";
    private static final char SECRET_CODE = '^';

    @Override
    public String encrypt(String content) throws RemoteException {
        char[] chars = content.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            chars[i] ^= SECRET_CODE;
        }
        return new String(chars);
    }

    @Override
    public String decrypt(String password) throws RemoteException {
        return encrypt(password);
    }
}

IComputeImpl

public class IComputeImpl extends ICompute.Stub {

    private static final String TAG = "IComputeImpl";

    @Override
    public int add(int a, int b) throws RemoteException {
        return a + b;
    }
}

现在不同业务模块需要的AIDL接口和实现已经完成了,注意这里并没有为每个模块的AIDL单独创建Service,接下来就是服务端和Binder连接池的工作了。

首先为Binder连接池创建AIDL接口IBinderPool.aidl

IBinderPool.aidl

// IBinderPool.aidl
package com.hm.aidlserver;

// Declare any non-default types here with import statements

interface IBinderPool {
  //根据不同的查询码返回相应的Binder对象
  IBinder queryBinder(int binderCode);
}

接下来在服务端定义一个Service用来返回IBinderPool

import com.hm.aidlserver.impl.IComputeImpl;
import com.hm.aidlserver.impl.SecurityCenterImpl;

public class BinderPoolService extends Service {

    private static final String TAG = "BinderPoolService";

    private Binder mBinderPool = new BinderPool();

    public BinderPoolService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind: ");
        return mBinderPool;
    }
    //声明BinderPool类实现IBinderPool接口
    private static class BinderPool extends IBinderPool.Stub {

        private static final String TAG = "BinderPool";
        private static final int BINDERT_COMPUTE = 0;
        private static final int BINDERT_NOSECURITY_CENTER = 1;

        private BinderPool() {
            Log.e(TAG, "调用构造函数");
        }

        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode) {
                case BINDERT_NOSECURITY_CENTER:
                    binder = new SecurityCenterImpl();
                    break;
                case BINDERT_COMPUTE:
                    binder = new IComputeImpl();
                    break;
            }
            return binder;
        }
    }
}

接下来在客户端实现具体的Binder连接池,在它的内部首先要去绑定远程服务,绑定成功后,客户端就可以通过它的queryBinder方法去获取各自对应的Binder,拿到所需的Binder以后,不同的业务模块就可以进行各自的操作了。

/**
 * Created by dumingwei on 2017/5/7.
 * Binder连接池的具体实现
 */
public class BinderPoolHelper {

    private static final String TAG = "BinderPoolHelper";
    public static final int BINDERT_COMPUTE = 0;
    public static final int BINDERT_NOSECURITY_CENTER = 1;

    private Context mContext;
    private IBinderPool mBinderPool;
    private static volatile BinderPoolHelper sInstance;
    private CountDownLatch mConnectBinderPoolCountDownLatch;

    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mConnectBinderPoolCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.e(TAG, "binderDied: ");
            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
            mBinderPool = null;
            connectBinderPoolService();
        }
    };

    private BinderPoolHelper(Context context) {
        mContext = context.getApplicationContext();
        connectBinderPoolService();

    }

    public static BinderPoolHelper getsInstance(Context context) {
        if (sInstance == null) {
            synchronized (BinderPoolHelper.class) {
                if (sInstance == null) {
                    sInstance = new BinderPoolHelper(context);
                }
            }

        }
        return sInstance;
    }

    private synchronized void connectBinderPoolService() {
        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.hm.aidlserver", "com.hm.aidlserver.BinderPoolService"));
        mContext.bindService(intent, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
        try {
            mConnectBinderPoolCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public IBinder queryBinder(int binderCode) {
        IBinder binder = null;
        if (mBinderPool != null) {
            try {
                binder = mBinderPool.queryBinder(binderCode);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        return binder;
    }
}

使用

private ISecurityCenter mSecurityCenter;
private ICompute mComputer;
private BinderPoolHelper binderPoolHelper;

public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn_ISecurityCenter:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        binderPoolHelper = BinderPoolHelper.getsInstance(BinderPoolActivity.this);
                        IBinder securityBinder = binderPoolHelper.queryBinder(BinderPoolHelper.BINDERT_NOSECURITY_CENTER);
                        mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder);
                        Log.e(TAG, "doWork: visit ISecurityCenter");
                        String msg = "helloworld-安卓";
                        String password;
                        try {
                            password = mSecurityCenter.encrypt(msg);
                            Log.e(TAG, "doWork: encrypt" + password);
                            String content = mSecurityCenter.decrypt(password);
                            Log.e(TAG, "doWork: decrypt" + content);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
                break;
            case R.id.btn_ICompute:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        binderPoolHelper = BinderPoolHelper.getsInstance(BinderPoolActivity.this);
                        IBinder computeBinder = binderPoolHelper.queryBinder(BinderPoolHelper.BINDERT_COMPUTE);
                        mComputer = IComputeImpl.asInterface(computeBinder);
                        Log.e(TAG, "doWork: visit ICompute");
                        try {
                            Log.e(TAG, "doWork: " + mComputer.add(7, 14));
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
                break;
            default:
                break;
        }
    }

Binder中定向tag in out inout的结论

Binder中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是指在客户端中调用远程方法的传入的对象而言的。

  • in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;
  • out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;
  • inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且在服务端对接收到的对象有任何修改之后客户端将会同步变动;

Binder的优势

Android Binder学习笔记(二)_第1张图片
Binder的优点.png

Android 进程间通信的其他方式

  1. 使用Bundle。

  2. 使用文件共享,两个进程通过读/写同一个文件来交换数据。(并发读写不好处理同步问题)。

  3. 使用Messenger:Messenger可以译为信使。通过它可以在不同的进程中传递Message对象。在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。同时Messenger一次处理一个请求,在服务端不用考虑线程同步的问题。Messenger的主要作用是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法。这种情况下Messenger就无法做到了。

  4. 使用ContentProvider。

  5. 使用Socket。

各种方式适用场景对比。

Android Binder学习笔记(二)_第2张图片
IPC方式的优缺点和适用场景.png

参考链接

  • 《Android开发艺术探索》
  • 你真的理解AIDL中的in,out,inout么?

你可能感兴趣的:(Android Binder学习笔记(二))