美文网首页
Android 进程间通信(Inner Process Comm

Android 进程间通信(Inner Process Comm

作者: leilifengxingmw | 来源:发表于2019-01-13 22:59 被阅读9次

    使用AIDL实现进程间通信

    本文参考《Android开发艺术探索》,将书中AIDL的示例分为客户端和服务端。

    建议照着代码再看文章,不然肯定不知道说的是什么。

    GitHub-完整代码链接

    Binder的工作机制

    Binder工作机制.png

    服务端:服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,接着创建一个Binder类继承AIDL接口中的Stub类,并实现Stub中的抽象方法,最后在Service的onBind方法中返回这个Binder类的对象。

    客户端:客户端首先要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

    AIDL文件中支持的数据类型

    • 基本数据类型;
    • List:只支持ArrayList;
    • Map:只支持HashMap;
    • Parcelable:所有实现了Parcelable接口的对象;
    • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用;

    注意:

    1. 自定义的Parcelable对象和AIDL对象必须要显式的import进来,不管它们是否和当前的AIDL文件位于同一个包内。
    2. AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout。AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是指在客户端中调用远程方法的传入的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且在服务端对接收到的对象有任何修改之后客户端将会同步变动;详情请参考你真的理解AIDL中的in,out,inout么?
    3. 如果一个AIDL文件中用到了自定义的Parcelable对象类,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。例如下面的Book类:

    自定义的Parcelable对象Book类

    public class Book implements Parcelable {
    
        private int bookId;
        private String bookName;
    
        public Book(int bookId, String bookName) {
            this.bookId = bookId;
            this.bookName = bookName;
        }
        //...
    }
    
    

    为Book类声明AIDL文件

    IBookManager.aidl

    // Book.aidl
    package com.hm.aidlserver;
    
    parcelable Book;
    

    IBookManager.aidl

    // IBookManager.aidl
    package com.hm.aidlserver;
    
    import com.hm.aidlserver.Book;
    import com.hm.aidlserver.IOnNewBookArriveListener;
    
    interface IBookManager {
    
       List<Book>getBookList();
       void addBook(in Book book);
    }
    
    

    声明完了AIDL文件,重新build项目即可在build目录下看到系统为我们生成的AIDL辅助类

    ../build/generated/source/aidl/debug/com/hm/aidlserver/IBookManager.java
    

    远程服务端Service的实现

        //...
    
        /**
         * 实现IBookManager接口中的方法
         */
        private Binder mBinder = new IBookManager.Stub() {
    
            @Override
            public List<Book> getBookList() throws RemoteException {
                return mBookList;
            }
    
            @Override
            public void addBook(Book book) throws RemoteException {
                mBookList.add(book);
            }
        };
    
        //...
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    //...
    

    在BookManagerService中我们创建了一个Binder对象mBinder,mBinder实现了IBookManager.aidl中定义的方法。然后我们在onBind方法中返回mBinder对象即可。

    在AndroidManifest.xml文件中注册BookManagerService

    <service
            android:name=".BookManagerService"
            android:enabled="true"
            android:exported="true">
        </service>
    

    客户端的实现

    绑定远程服务

    private void bind() {
        binded = true;
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.hm.aidlserver", "com.hm.aidlserver.BookManagerService"));
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
     }
    

    绑定远程服务以后,我们就可以调用服务端的方法了

    获取书籍列表

    if (bookManager != null) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            bookList = bookManager.getBookList();
                            Log.e(TAG, "query book list,list type:" + bookList.getClass().getCanonicalName());
                            Log.e(TAG, "query book list,list:" + bookList.toString());
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            } else {
                Toast.makeText(this, "远程服务已断开", Toast.LENGTH_SHORT).show();
            }
    

    注意:当客户端调用服务端的方法时,如果是耗时方法的话,我们不能在主线程调用,否则可能会导致ANR。

    向服务端注册成为观察者

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

    首先声明一个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 {
    
       List<Book>getBookList();
       void addBook(in Book book);
       //注册观察者
       void registerListener(IOnNewBookArriveListener listener);
      //取消注册
       void unRegisterListener(IOnNewBookArriveListener listener);
    }
    
    

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

    public class BookManagerService extends Service {
    
        //...
        private RemoteCallbackList<IOnNewBookArriveListener> 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)方法是耗时方法的话
         * <p>
         * notifyBookArrived不能运行在服务端的住线程
         * 通知观察者有新书到来
         *
         * @param book
         * @throws RemoteException
         */
        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 mOnNewBookArriveListener = 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(mOnNewBookArriveListener);
                } 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线程池中。

    在AIDL中使用权限验证功能

    第一种验证方式

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

    <permission
            android:name="com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE"
            android:protectionLevel="normal"/>
    

    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中声明所需要的权限

        <uses-permission android:name="com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE"/>
    

    第二种验证方式

    在服务端的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的过程。

    在这种模式下,整个工作流程是这样的:服务端提供一个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;
            }
        }
    
    

    Android 进程间通信的其他方式

    1. 使用Bundle。

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

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

    4. 使用ContentProvider。

    5. 使用Socket。

    各种方式适用场景对比。

    IPC方式的优缺点和适用场景.png

    参考

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

    相关文章

      网友评论

          本文标题:Android 进程间通信(Inner Process Comm

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