Android AIDL开发Binder应用中注意事项

1 前言

在我们的应用开发中,我们常常有跨进程通信的需求,如果使用AIDL方式,就是用Binder进行通信,往往会新建AIDL文件来定义好服务,服务端实现这些服务,而客户端会具体调用这些服务。

关于怎么新建AIDL文件,以及如何实现客户端与服务端,这里就不做讨论了,网上由于大把的教程,这里主要讨论下需要开发中注意的事项。
需要注意的事项如下:

2 Binder通信注意事项

  • 1 AIDL文件路径问题

当我们在studio中新建AIDL文件时,由于Server与Client需要共享原始的Bean与AIDL文件,并且两端需要有共同的包名,因此,我们往往通过studio中的如下命令来新建AIDL文件:
Android AIDL开发Binder应用中注意事项_第1张图片
这样我们往往在src/main/目录下会新建一个aidl包,如下:
Android AIDL开发Binder应用中注意事项_第2张图片
这个包与java包同级,同时为了避免aidl包中的类我们在编译的时候找不到,应该在build.gradle中添加如下配置:

    sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
            aidl.srcDirs = ['src/main/aidl']
        }
    }

不管是客户端还是服务器,都需要以上的配置,这样就能保证进行AIDL部分在客户端与服务端都具有相同的包名,便于序列化与反序列化。

  • 2 服务端代码需要考虑多线程访问的同步问题
    一般来说,服务端需要处理多个客户端同时访问的情况,因此必须考虑多线程的问题,可以采用锁住数据源的方式同步,也可以采用一些类比如CopyOnWriteArrayList< T >来解决。
    例如下面的例子,使用CopyOnWriteArrayList定义了mBookList,这样服务端端对mBookList的操作都是线程安全的,比如添加,更新,删除等:
    private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList<>();
    ......
    /**
     * 运行在Binder线程池中
     */
    private Binder mBinder = new IBookManager.Stub(){
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public List getBookList() throws RemoteException {
            Log.d(TAG,"thread :" + Thread.currentThread().getName());
//            try {
//                Thread.sleep(10000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
        ......
     }
  • 3 Binder通信中的观察者模式
    在Binder通信中,一般来说是客户端主动去调用服务端的服务,但是在某些情况下,需要服务端去通知客户端,比如,某些数据已经准备好,客户端可以开始调用xxx服务了,这个时候就需要用到观察者模式了,一般来说的是这样实现的。

客户端作为观察者,服务端作为被观察者,客户端注册某个监听到服务端,服务端当数据准备好或者条件满足,通过监听的回调通知客户端。

由于Binder通信是跨进程的,一般来说监听是需要跨进程传输,为了便于客户端的注册与反注册监听器,需要使用RemoteCallbackList< E >辅助类来实现监听的注册与反注册。

一个简单的例子如下:

服务端与客户端AIDL文件:

interface IBookManager {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    /*
     * 获取所有的book
     * */
    List getBookList();

    /*
     * 添加一本书
     * */
    void addBook(in Book book);

    void registerListener(in IOnNewBookArrivedListener listener);

    void unRegisterListener(in IOnNewBookArrivedListener listener);
}
interface IOnNewBookArrivedListener {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
    //新书到来
    void onNewBookArrived(in Book book);
}

BookManagerService实现:

/**
 * Created by qiyei2015 on 2017/4/7.
 * [email protected]
 */
public class BookManagerService extends Service {
    private static final String TAG = "BMS";

    //需要同步
    private AtomicBoolean mIsServiceDestroy = new AtomicBoolean(false);
    private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList<>();
    //无法取消注册
    //private CopyOnWriteArrayList mListenerList = new CopyOnWriteArrayList<>();

    //使用这个来取消注册
    private RemoteCallbackList mRemoteCallbackList = new RemoteCallbackList<>();

    /**
     * 运行在Binder线程池中
     */
    private Binder mBinder = new IBookManager.Stub(){
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public List getBookList() throws RemoteException {
            Log.d(TAG,"getBookList thread :" + Thread.currentThread().getName());
            return mBookList;
        }

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

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mRemoteCallbackList.register(listener);
            Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast());
            mRemoteCallbackList.finishBroadcast();
        }

        @Override
        public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mRemoteCallbackList.unregister(listener);
            Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast());
            mRemoteCallbackList.finishBroadcast();
        }
    };

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

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG,"onCreate");
        init();
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG,"onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        mIsServiceDestroy.set(true);
        Log.d(TAG,"onDestroy");
        super.onDestroy();
    }

    private void init(){
        for (int i = 0;i < 10;i++){
            Book book = new Book(i+1,"book_" + (i+1));
            mBookList.add(book);
        }
    }

    public class ServiceWorker implements Runnable{
        @Override
        public void run() {

            while (!mIsServiceDestroy.get()){
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBookList.size() + 1;
                Book book = new Book(bookId,"book_" + bookId);
                try {
                    notifyClientNewBook(book);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 通知客户端新书到来
     * @param book
     * @throws Exception
     */
    private void notifyClientNewBook(Book book) throws Exception{
        mBookList.add(book);

        int size = mRemoteCallbackList.beginBroadcast();

        Log.d(TAG,"notifyClientNewBook: "+ book.toString());
        for (int i = 0;i "notify listener: "+ listener.toString());
            listener.onNewBookArrived(book);
        }
        mRemoteCallbackList.finishBroadcast();
    }

    /**
     * 检查BookServiceDe调用权限
     * @return
     */
    private boolean checkBookServicePermission(){
        int check = checkCallingOrSelfPermission("com.qiyei.ipc.book.ACCESS_BOOK_SERVICE");
        return check == PackageManager.PERMISSION_DENIED;
    }
}

客户端实现:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "Client";
    private static final int MESSAGE_NEW_BOOK = 1;

    private Context mContext;

    private TextView mTextView;
    private Button mButton;
    private Button mClear;
    private Button mButton2;

    private IBookManager mBookManager;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == MESSAGE_NEW_BOOK){
                Book book = (Book) msg.obj;
                mTextView.setText(book.toString());
            }
        }
    };

    /**
     * Binder死亡代理 ,Binder死亡时会回调该方法
     */
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if (mBookManager == null){
                return;
            }
            mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
            mBookManager = null;
            //重新连接服务
            bindBookService();
        }
    };

    /**
     * 运行在主线程中
     */
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG,"onServiceConnected thread :" + Thread.currentThread().getName());
            mBookManager = IBookManager.Stub.asInterface(service);
            try {
                mBookManager.asBinder().linkToDeath(mDeathRecipient,0);
                mBookManager.registerListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            try {
                mTextView.setText(mBookManager.getBookList().toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

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

    /**
     * Service调用,运行在Client端的Binder线程中
     */
    private IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub(){

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public void onNewBookArrived(Book book) throws RemoteException {
            Log.d(TAG,"onNewBookArrived thread :" + Thread.currentThread().getName());
            Message message = Message.obtain();
            message.what = MESSAGE_NEW_BOOK;
            message.obj = book;
            mHandler.sendMessage(message);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        initView();

    }

    private void initView(){
        mTextView = (TextView) findViewById(R.id.tv1);
        mClear = (Button) findViewById(R.id.btn0);
        mClear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mTextView.setText("");
            }
        });
        mButton = (Button) findViewById(R.id.btn1);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindBookService();
            }
        });
        mButton2 = (Button) findViewById(R.id.btn2);
        mButton2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(mServiceConnection);
                startActivity(new Intent(mContext,TestActivity.class));
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (mBookManager != null && mBookManager.asBinder().isBinderAlive()){
            try {
                mBookManager.unRegisterListener(mListener);
                Log.d(TAG,"unRegister listener ");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        unbindService(mServiceConnection);
    }

    /**
     * 绑定BookService
     */
    private void bindBookService(){
        Intent intent = new Intent();
        //这里需要指定Action 与packageName
        intent.setAction("android.intent.action.BOOK_MANAGER_SERVICE");
        intent.setPackage("com.qiyei.ipcserver");

        bindService(intent,mServiceConnection, BIND_AUTO_CREATE);
    }
}

客户端直接在MainActivty去连接服务,并且在绑定成功后的onServiceConnected(ComponentName name, IBinder service)去注册IOnNewBookArrivedListener ,客户端自己实现该监听器,并在Activity的onDestroy中去取消注册与解绑服务。

  • 4 Binder通信中的线程问题

对于服务端来说,客户端调用服务端的服务,具体的服务方法运行在服务端的Binder线程池中,并没有运行在服务端的主线程中。并且调用会阻塞。
在上面的服务BookManagerService中:

/**
     * 运行在Binder线程池中
     */
    private Binder mBinder = new IBookManager.Stub(){
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public List getBookList() throws RemoteException {
            Log.d(TAG,"getBookList thread :" + Thread.currentThread().getName());
            return mBookList;
        }

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

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mRemoteCallbackList.register(listener);
            Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast());
            mRemoteCallbackList.finishBroadcast();
        }

        @Override
        public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mRemoteCallbackList.unregister(listener);
            Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast());
            mRemoteCallbackList.finishBroadcast();
        }

    };

当运行时,可以看到如下输出:
Android AIDL开发Binder应用中注意事项_第3张图片
可以看到,确实运行在服务端的Binder线程中

对于客户端来说, 绑定服务成功后的回调ServiceConnection是运行在主线程(UI线程中的),同时由于调用服务端的服务可能会阻塞,因此尽量避免在主线程中去调用服务端的服务。另外,服务端回调客户端的监听Listener中的方法却是运行在客户端的Binder线程池中,因此不能直接更新UI,我们运行客户端的,可以看到如下:
Android AIDL开发Binder应用中注意事项_第4张图片

  • 5 客户端Binder意外死亡时重连的问题
    一般来说,我们需要考虑到服务端程序的意外终止,这个时候Binder就会意外死亡,这个时候我们就需要重新连接服务。一般来说有两种方法,一种在ServiceConnection的onServiceDisconnected中进行重新连接服务,但是需要区分正常的断开服务,另外一种是给Binder设置DeathRecipient监听,当Binder意外死亡时会回调监听中的binderDied()方法
/**
     * Binder死亡代理 ,Binder死亡时会回调该方法
     */
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if (mBookManager == null){
                return;
            }
            mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
            mBookManager = null;
            //重新连接服务
            bindBookService();
        }
    };
/**
     * 运行在主线程中
     */
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG,"onServiceConnected thread :" + Thread.currentThread().getName());
            mBookManager = IBookManager.Stub.asInterface(service);
            try {
                mBookManager.asBinder().linkToDeath(mDeathRecipient,0);
                mBookManager.registerListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            try {
                mTextView.setText(mBookManager.getBookList().toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //bindBookService();
        }
    };
这样当Binder意外死亡时,会回调binderDied,我们在里面去重连服务即可。
  • 6 Binder通信的权限问题
    我们在Binder通信中不可避免的会用到权限的问题,有时服务端对服务的访问有限制,比如需要指定的客户端才能访问。这个时候我们就可以采取以下两种办法来实现:
    (1) public IBinder onBind(Intent intent) 中验证权限,有权限才返回Binder,没有就返回null
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG,"onBind");
        if (checkBookServicePermission()){
            return mBinder;
        }else {
            return null;
        }
    }

    /**
     * 检查BookServiceDe调用权限
     * @return
     */
    private boolean checkBookServicePermission(){
        int check = checkCallingOrSelfPermission("com.qiyei.ipc.book.ACCESS_BOOK_SERVICE");
        return check == PackageManager.PERMISSION_DENIED;
    }

其中com.qiyei.ipc.book.ACCESS_BOOK_SERVICE定义在服务端的mainfest文件中:

    
        <permission android:name="com.qiyei.ipc.book.ACCESS_BOOK_SERVICE"
            android:protectionLevel="normal"/>
        ......
       <service android:name=".service.BinderPoolService"
                 android:exported="true"
                 android:permission="com.qiyei.ipc.book.ACCESS_BOOK_SERVICE">
            <intent-filter>
                <action android:name="android.intent.action.BINDER_POOL_SERVICE"/>
            intent-filter>
        service>

这样只有客户端有这个权限时才能访问这个服务。

(2)在服务端的Binder对象中onTransact()根据Uid与pid来验证客户端的权限
比如,我们在实现返回的Binder对象中时,重写onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException方法,通过getCallingUid和getCallingPid方法来得到客户端的包名,根据具体的包名来决定是否给予访问的权限,注意,这一条也可以在IBinder onBind(Intent intent)中使用。

    /**
     * 运行在Binder线程池中
     */
    private Binder mBinder = new IBookManager.Stub(){
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public List getBookList() throws RemoteException {
            Log.d(TAG,"getBookList thread :" + Thread.currentThread().getName());
            return mBookList;
        }

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

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mRemoteCallbackList.register(listener);
            Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast());
            mRemoteCallbackList.finishBroadcast();
        }

        @Override
        public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mRemoteCallbackList.unregister(listener);
            Log.d(TAG,"mListenerList size:" + mRemoteCallbackList.beginBroadcast());
            mRemoteCallbackList.finishBroadcast();
        }

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0){
                packageName = packages[0];
            }
            Log.d(TAG,"packageName:" + packageName);
            if (!packageName.startsWith("com.qiyei.ipc")){
                return false;
            }

            return super.onTransact(code, data, reply, flags);
        }
    };

上面就是判断包名是否以”com.qiyei.ipc”开头,符合才给予权限访问。

本篇的介绍就到此结束,后续会继续补充,相关代码下载见我的github
源代码下载

你可能感兴趣的:(android,应用开发)