Android中的IPC方式

Android中的IPC方式

  • 上一篇,我们探索了IPC的基本知识,序列化,和Binder的基本实现原理
  • 本篇我们来看看Android中的一些具体的跨进程通信的方式

使用Bundle

  • 我们知道,四大组件中的三大组件(Activity,Service,Broadcast)都是支持在Intent中传输Bundle数据的,由于Bundle实现了Parcelable接口,所以他可以很方便的在进程间传输,通过Bundle我们不就可以传输基本类型,也可以传输实现了Parcelable接口或者Serializable接口的对象

使用文件共享

  • 在Windows上,一个文件如果被加了排斥锁将会导致其他线程无法对其进行访问,包括读和写
  • 而Android由于是基于Linux系统,使得这个并发读写操作可以无限制的进行,尽管这可能出问题
  • 但是这种方式却是解决了不同进程数据传输的问题
  • 接下来简单的举个栗子示范一下
  • 在本进程中写数据
Data data = new Data(10,"这是主进程写的数据");
                try {
                    ObjectOutputStream out = new ObjectOutputStream(
                            new FileOutputStream("/storage/emulated/0/1/sina/sdadas.txt"));
                    out.writeObject(data);
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.d(TAG, "ssss: 序列化的时候失败" + e.toString());
                }
  • 在另一进程中读数据
Data data = null;
                try {
                    ObjectInputStream in = new ObjectInputStream(
                            new FileInputStream("/storage/emulated/0/1/sina/sdadas.txt"));
                    data = (Data) in.readObject();
                }catch (IOException e){
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }

                if(data != null){
                    Log.d(TAG, "onClick: 读取成功 , data.id = "  + data.id + "data.name = " + data.name);
                }else {
                    Log.d(TAG, "onClick: 错误");
                }
  • 这是正常并且操作成功的,说明这种方式是可行的
  • 不过我们这里虽然说的是传输数据,只不过我们保证的是内容一样,他们实际上还是两个不同的对象
  • 通过这种方式,我们应尽量控制读写时的时序问题

使用Messenger

  • Messenger可以翻译为信使,通过它可以在不同进程中传递Messenger对象,在Messenger中可以放入我们需要传递的对象,就可以轻松跨进程传输
  • Messenger是一种轻量级的IPC方案,他的底层实现是AIDL,为什么这么说呢,我们去看一下他的构造方法
public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }
  • 从上构造方法我们可以很容易想到底层是AIDl的
  • Messenger的用法非常简单,他对AIDL做了封装,使得我们可以更简单的跨进程通信
  • 同时,由于他一次处理一个请求,因此在服务端我们不同考虑线程同步的问题,这是因为服务端不承诺在并发执行的问题,下面我们来看看怎么实现一个Messenger

实现Messenger

  • 我们首先来看看服务端进程中怎么实现
  • 只需要在服务端创建一个Service来处理客户端的连接请求,并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可
public class MessengerService extends Service {

    private static final String TAG = "MessengerService";

    private static class Messengerhandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    Log.d(TAG, "handleMessage: receive msg from Client " + msg.getData().getString("msg"));
                    break;
            }
            super.handleMessage(msg);
        }
    }
    private final Messenger mMessenger = new Messenger(new Messengerhandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}
  • 可以看到,因为我们在上面的Messenger构造器中发现Messenger的构造器需要传入一个handler 类型的参数,很明显这个Handler就是用来处理客户端发来的Message消息的
  • 接下来看看客户端的代码
  • 客户端首先要绑定Service,然后发送Message类型的消息
void bindMyMessengerService(){
        ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mService = new Messenger(service);
                sendData();
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
                mService = null;
            }
        };
        Intent intent = new Intent(this,MessengerService.class);
        bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
    }

    void sendData(){
        Message msg = Message.obtain(null,1);
        Bundle data = new Bundle();
        data.putString("msg","这是客户端的消息");
        msg.setData(data);
        try{
            mService.send(msg);
        }catch (RemoteException e) {
            e.printStackTrace();
        }
    }
  • 这段代码相信没什么难度,不过这里问题又来了?诶?我们只是发送了消息给服务端,可是通信,通信,不是应该能互相发的吗?客户端是怎么接收服务端的呢?
  • 接下来我们看看服务端怎么给客户端发送消息,先来看看服务端怎么修改
private static class Messengerhandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    Log.d(TAG, "handleMessage: receive msg from Client " + msg.getData().getString("msg"));
                    Messenger client = msg.replyTo;
                    Message clientData = Message.obtain(null,1);
                    Bundle bundle = new Bundle();
                    bundle.putString("reply","嗯,我收到了你的消息,我是服务端");
                    clientData.setData(bundle);
                    try {
                        client.send(clientData);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
            super.handleMessage(msg);
        }
    }
  • 这里我将服务端接收消息的handler的Hand可Message方法改变了一下,让他能在接收到消息之后直接再给客户端发送一个消息
  • 这里可以看到,我们发送消息的Messenger是从客户端发送过来msg中得到的,能猜到吧,客户端将自己进程所在的Messenger通过Messenger发送给服务端,然后服务端就可以拿到客户端进程的Messenger句柄给客户端发送消息了
  • 看一下客户端的代码
private Messenger mCilentMessenger = new Messenger(new ClientMessenger());
    private static class ClientMessenger extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    Log.d(TAG, "handleMessage: 接收到服务端的消息  = " +msg.getData().getString("reply"));
                    break;
            }

            super.handleMessage(msg);
        }
    }


    void sendData(){
        Message msg = Message.obtain(null,1);
        msg.replyTo = mCilentMessenger;
        Bundle data = new Bundle();
        data.putString("msg","这是客户端的消息");
        msg.setData(data);
        try{
            mService.send(msg);
        }catch (RemoteException e) {
            e.printStackTrace();
        }
    }
  • 这里我添加了一个Messenger对象负责作为发送消息时候的replyTo参数,然后将这个参数在发送消息时添加到replyTo参数上即可
  • 具体代码其实也没啥难度,不太清楚的小伙伴认真分析一下即可

Messenger总结

  • 我们发现Messenger的核心就是使用本进程的Messenger在另外一个线程发送(Message)消息就可以在本进程收到(Message)消息,所以说发送消息的过程是Messenger实现的,消息的数据是Message存储的

使用AIDL

  • Binder之间通信还是分为客户端和服务端
  • 先来看服务端,还是创建一个Service来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可
  • 先看看服务端AIDL接口的创建吧
// IBookManager.aidl
package com.example.learnretrofit.LearnMoreProcess;
import com.example.learnretrofit.LearnMoreProcess.Book;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}
  • 创建AIDL文件的方法上篇文章讲过,在文章最开始有链接
  • 在AIDL文件中,并不是所有类型都可使用,他所能使用的数据类型如下

    • 基本数据类型
    • String和CharSequence
    • List:只支持ArrayList,里面每个元素都必须能被AIDL支持
    • Map:只支持HashMap,里面每个元素都必须能被AIDL支持
    • Parcelable:所有实现了Parcelable接口的对象,使用时需要手动import
    • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用,使用时需要手动import
  • 这里由于在AIDL接口中使用了Book类,所以必须在前面import,另外如果AIDL文件用到了自定义的Parcelable对象,必须新建一个和他同名的AIDL文件,并在其中声明他为Parcelable类型,因为在上面的接口定义中,我们使用了Book类,所以我们必须定义一个aidl文件去声明他

// Book.aidl
package com.example.learnretrofit.LearnMoreProcess;

parcelable Book;
  • 不知道大家注意到接口定义的方法的参数声明前有个in,这个参数是什么意思呢?AIDL中除了基本数据类型之外,其他类型的参数都必须标上方向,in , out ,或者inout,in表示输入性参数,out表示输出型参数,inout表示输入输出型参数,因为这个参数代表着底层的操作,所以在标注的时候不能随便标
  • 最后要注意的是AIDL接口中只支持方法,不支持静态变量,还有,服务端和客户端的包结构必须一致,因为客户端需要反序列话服务器中和AIDL接口相关的所有类,如果包路径不同就会无法反序列化成功
  • 那么上面的接口定义好了,我们就来看看我们的服务端怎么实现
  • 注意,在编写好aidl文件之后记得build-make project
public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Apple> mAppleList = new CopyOnWriteArrayList<>();

    private Binder mBinder = new IAppleManager.Stub() {
        @Override
        public List<Apple> getAppleList() throws RemoteException {
            return mAppleList;
        }

        @Override
        public void addApple(Apple apple) throws RemoteException {
            mAppleList.add(apple);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mAppleList.add(new Apple(10,"红富士"));
        mAppleList.add(new Apple(10,"早熟苹果"));
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}
  • 这里的IAppleManager.Stub是系统给我们生成的东西,他实际上是一个Binder类,然后我们先在onCreate方法里面添加两个数据,注意到我们这里使用了CopyOnWriteArrayList,因为他在原理上是支持并发读写的,而AIDL支持的是抽象的List这个接口,虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端,所以我们在这里使用CopyOnWriteArrayList是完全合理的,类似的还有ConcurrentHashMap,然后在注册文件中注册这个Service
  • 接着来看看客户端的实现
  • 客户端的话我们就需要绑定服务,然后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调服务端的远程方法了,看看客户端的代码
void bindMyAppleService(){
        ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IAppleManager appleManager = IAppleManager.Stub.asInterface(service);
                try {
                    List<Apple> list = appleManager.getAppleList();
                    Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName());

                    for(Apple apple : list){
                        Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size);
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {}
        };
        bindService(new Intent(this,AppleManagerService.class),mConnection,Context.BIND_AUTO_CREATE);
    }
  • 在绑定成功之后直接跨进程获取数据,注意这里是为了方便才这么写,一般还是写在新线程中比较好,因为这个服务端响应时间是不定的,万一响应时间过长就会出现ANR
  • 如果你对AIDL还不太清楚,可能看这些有点模糊,烦请移驾androidIPC机制探索先把这个看看
  • 在启动程序后打印出来的log信息为
onServiceConnected: query apple list ,list type is java.util.ArrayList
onServiceConnected: apple.color = 红富士,apple.size = 10
onServiceConnected: apple.color = 早熟苹果,apple.size = 10
  • 可见客户端收到的确实是Arraylist类型,证实了我们上面的说法,同时下面的信息也证明了我们的跨进程操作成功
  • 我们再试一下他的addApple方法,修改onServiceConnection方法
public void onServiceConnected(ComponentName name, IBinder service) {
                IAppleManager appleManager = IAppleManager.Stub.asInterface(service);
                try {
                    List<Apple> list = appleManager.getAppleList();
                    Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName());
                    for(Apple apple : list){
                        Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size);
                    }
                    Apple apple = new Apple(10,"绿色的苹果");

                    appleManager.addApple(apple);
                    list = appleManager.getAppleList();
                    Log.d(TAG, "onServiceConnected: 重新接收");
                    for(Apple apple1 : list){
                        Log.d(TAG, "onServiceConnected: apple.color = " + apple1.color + ",apple.size = " + apple1.size);
                    }

                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
  • log信息
 onServiceConnected: query apple list ,list type is java.util.ArrayL
 onServiceConnected: apple.color = 红富士,apple.size = 10
 onServiceConnected: apple.color = 早熟苹果,apple.size = 10
 onServiceConnected: 重新接收
 onServiceConnected: apple.color = 红富士,apple.size = 10
 onServiceConnected: apple.color = 早熟苹果,apple.size = 10
 onServiceConnected: apple.color = 绿色的苹果,apple.size = 10
  • 没毛病
  • 我们接下来看看AIDL更多的用法

AIDL深入探索

  • 我们看到,前面我们的跨进程通信都是一问一答式,就是客户端是主导,服务器是被主导,那么会不会有一种需求,我们不想去实时的查询了,因为这样太耗费时间,每次还不一定都有信息更新,所以,能不能有一种操作,客户端去问服务器:当有信息更新的时候你能不能直接给我说呢?
  • 以上需求是一种典型的观察者模式,服务器在有数据更新的时候通知每一位想知道这个消息的客户端,可是怎么用AIDL实现呢?
  • 首先,我们需要提供一个AIDL接口,每个用户都需要实现这个接口并对服务器申请数据更新的通知功能,当然用户也可以随时取消这种功能,这里我们创建一个
// InewDataListener.aidl
package com.example.learnretrofit.LearnMoreProcess;
import com.example.learnretrofit.LearnMoreProcess.Apple;
interface InewDataListener {
    void DataUpdata(in Apple apple);
}
  • 当然不止增加这个接口,我们还需要在原来接口做改动
// IBookManager.aidl
package com.example.learnretrofit.LearnMoreProcess;

import com.example.learnretrofit.LearnMoreProcess.Apple;
import com.example.learnretrofit.LearnMoreProcess.InewDataListener;

interface IAppleManager {
    List<Apple> getAppleList();
    void addApple(in Apple apple);

    void registerListener(InewDataListener listener);
    void unRegisterListener(InewDataListener listener);    
}
  • 这两个方法很容易理解
  • 然后我们build -> make project
  • 然后服务端的Binder对象就会提示我们有两个接口方法没实现,手动实现,然后看看此时的服务端代码有哪些改动吧
private AtomicBoolean mAtomicBoolean = new AtomicBoolean(false);
private CopyOnWriteArrayList<InewDataListener> mListeners = new CopyOnWriteArrayList<>();

private Binder mBinder = new IAppleManager.Stub() {
        @Override
        public List<Apple> getAppleList() throws RemoteException {
            return mAppleList;
        }

        @Override
        public void addApple(Apple apple) throws RemoteException {
            mAppleList.add(apple);
        }

        @Override
        public void registerListener(InewDataListener listener) throws RemoteException {
            if(!mListeners.contains(listener)){
                mListeners.add(listener);
            }
        }

        @Override
        public void unRegisterListener(InewDataListener listener) throws RemoteException {
            if(mListeners.contains(listener)){
                mListeners.remove(listener);
            }
        }
    };


public void onCreate() {
        super.onCreate();
        mAppleList.add(new Apple(10,"红富士"));
        mAppleList.add(new Apple(10,"早熟苹果"));
        new Thread(new ServiceWorker()).start();
    }

private class ServiceWorker implements Runnable{

        @Override
        public void run() {
            while (!mAtomicBoolean.get()){
                try{
                    Thread.sleep(5000);
                    int appleId = mAppleList.size() + 1;
                    Apple apple = new Apple(appleId,"苹果 " + appleId + " 号");

                    addNewApple(apple);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void addNewApple(Apple apple) {
        mAppleList.add(apple);
        for(int i = 0;i < mListeners.size() ; i ++) {
            InewDataListener listener = mListeners.get(i);
            try {
                listener.DataUpdata(apple);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
  • 分析起来不难理解,这里就不再说了,我们再看看客户端的改动
private InewDataListener mInewDataListener = new InewDataListener.Stub() {
        @Override
        public void DataUpdata(Apple apple) throws RemoteException {
            mHandler.obtainMessage(10,apple).sendToTarget();
        }
    };

    void bindMyAppleService(){
        ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IAppleManager appleManager = IAppleManager.Stub.asInterface(service);
                try {
                    List<Apple> list = appleManager.getAppleList();
                    Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName());
                    for(Apple apple : list){
                        Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size);
                    }
                    Apple apple = new Apple(10,"绿色的苹果");

                    appleManager.addApple(apple);
                    list = appleManager.getAppleList();
                    Log.d(TAG, "onServiceConnected: 重新接收");
                    for(Apple apple1 : list){
                        Log.d(TAG, "onServiceConnected: apple.color = " + apple1.color + ",apple.size = " + apple1.size);
                    }
                    appleManager.registerListener(mInewDataListener);

                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {}
        };
        bindService(new Intent(this,AppleManagerService.class),mConnection,Context.BIND_AUTO_CREATE);
    }


    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 10:
                    Log.d(TAG, "handleMessage: receive The new Data " + msg.obj);
            }
            super.handleMessage(msg);
        }
    };
  • 定义了接口,声明了当被回调时候的动作,接着在onServiceConnection中注册接口
  • 这里要注意的是接口回调是在Binder线程池中执行的,这一点可以通过我的上篇博客看到,这里不再多说,因此我们要进行UI操作的话,还是尽量使用Handler将他弄到主线程使用
  • 这里运行程序看到log信息为
onServiceConnected: query apple list ,list type is java.util.ArrayList
onServiceConnected: apple.color = 红富士,apple.size = 10
onServiceConnected: apple.color = 早熟苹果,apple.size = 10
onServiceConnected: 重新接收
onServiceConnected: apple.color = 红富士,apple.size = 10
onServiceConnected: apple.color = 早熟苹果,apple.size = 10
onServiceConnected: apple.color = 绿色的苹果,apple.size = 10
handleMessage: receive The new Data 苹果 4handleMessage: receive The new Data 苹果 5handleMessage: receive The new Data 苹果 6
  • 没问题了
  • 又解决了一个问题,我们接着往下看
  • 一般书写的时候,我们会在onDestroy方法中去解除已经注册到服务端的listener,解除绑定的方法我们已经在服务端写好了,如果不出意外会按照我们所想的在客户端终止的时候解除绑定
  • 可是?你有没有想过这个问题?我们在上一篇说过的Binder原理中讲过,它传递的并非是对象本身,而是通过序列化将对象的信息传递过去,然后在另一端重新new出的对象与我们要传递的对象仅仅是数据相同而已
  • 也就是说,我们在客户端注册方法中传入一个listener对象,通过binder传到服务端进程的并不是同一个对象,而只是数据相同的新对象,那么我们在取消注册的方法里面当然也是,传过去的也是一个新对象,这个对象当然不能不在服务端的已注册listener列表里面,所以这个时候的取消注册并不能成功,有兴趣的话可以亲自试一下
  • 虽然我们在注册和取消注册的方法里面传的是同一个对象,不过当通过Binder传到服务端进程的时候就已经变成了两个全新的对象,那么到底我们应该怎么样才能正确的取消注册呢?

AIDL解决跨进程取消注册问题

  • Android系统为我们提供了一个专门用于删除跨进程listener的接口–RemoteCallbackList,他是一个泛型,支持管理任意的AIDL接口,这点从他的声明就可以看出来,因为所有的AIDL接口都继承自IInterface接口,这点如果是因为我们写的aidl接口都会由系统为我们自行生成一个java文件,而标准格式就是都会继承自IInterface接口,下面是这个RemoteCallbackList接口的声明
public class RemoteCallbackList<E extends IInterface>
  • 它的工作原理很简单,在他的内部有一个Map结构专门用来保存所有的AIDL回调,这个Map的key是IBinder类型,value是Callback类型
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();

其中Callback中封装了真正的远程Listener,当客户端注册listener的时候,他会把这个listener信息存入mCallback中,其中key和value分别通过下面的方式获得
这里写图片描述

  • 到这里的时候有没有点明悟了,还记得我们负责注册的过程是谁来完成的吗?是服务端给我们返回的一个Binder对象,而我们的跨进程通信都是基于这个对象的,而一般一个组件都是只有一个Binder的,所以?我们每个客户端与服务器之间的底层Binder是一一对应的,这点是不会变的,所以就有了上面的写法,我们存binder就行了,到时候你想取消注册,那就看看你的底层是哪个binder
  • RemoteCallbackList这个接口不光可以解决这个问题,他还有一个很有用的功能,那就是当客户端进程终止时,他会自动移除客户端所注册的listener
  • 另外,RemoteCallbackList内部自动实现了线程同步的功能,所以我们用它的时候不需要做额外的线程同步工作
  • 说了这么多,我们来看看他到底怎么用吧
  • 服务端的改变
  • 先用RemoteCallbackList创建新的集合代替原来的集合
private RemoteCallbackList<InewDataListener> mCallbackList = new RemoteCallbackList<>();

//    private CopyOnWriteArrayList<InewDataListener> mListeners = new CopyOnWriteArrayList<>();
  • 修改注册和取消注册两个接口的实现
public void registerListener(InewDataListener listener) throws RemoteException {
//            if(!mListeners.contains(listener)){
//                mListeners.add(listener);
//            }

            mCallbackList.register(listener);
        }

        @Override
        public void unRegisterListener(InewDataListener listener) throws RemoteException {
//            if(mListeners.contains(listener)){
//                mListeners.remove(listener);
//            }
            mCallbackList.unregister(listener);
        }
  • 接下来修改通知的方法
private void addNewApple(Apple apple) {
        mAppleList.add(apple);
//        for(int i = 0;i < mListeners.size() ; i ++) {
//            InewDataListener listener = mListeners.get(i);
//            try {
//                listener.DataUpdata(apple);
//            } catch (RemoteException e) {
//                e.printStackTrace();
//            }
//        }
        final int N = mCallbackList.beginBroadcast();
        for (int i = 0;i < N;i++){
            InewDataListener listener = mCallbackList.getBroadcastItem(i);
            if(listener != null){
                try{
                    listener.DataUpdata(apple);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mCallbackList.finishBroadcast();

    }
  • 是不是非常简单呢,我们来看一下具体效果吧,我在客户端进程的activity中添加一个取消注册的按钮
  • 运行之后的log
unRegisterListener: 取消之前的数量是 1
unRegisterListener: 取消之后的数量是 0
  • 注意:使用RemoteCallbackList的时候,我们无法像操作List一样去操作它,他并不是一个List,而且,遍历RemoteCallbackList的时候,必须要将代码写在CallbackList.beginBroadcast()和CallbackList.finishBroadcast()这两个方法之间,无论是遍历,还是哪怕只是获取个元素个数
final int N = mCallbackList.beginBroadcast();
        for (int i = 0;i < N;i++){
            InewDataListener listener = mCallbackList.getBroadcastItem(i);
            if(listener != null){
                try{
                    listener.DataUpdata(apple);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
mCallbackList.finishBroadcast();
  • 到这里关于AIDL的基本东西就说完了,这里说几点注意

    • 我们必须清楚什么方法执行在主线程,什么方法执行在Binder线程池,一些耗时的或者不可知的操作尽可能放在多线程中解决
    • 涉及到UI操作的,如果当前线程非主线程,要使用Handler切换到主线程再进行操作
    • 还有,binder是有可能意外死亡的,为了我们程序的健壮性,我们有必要给Binder设置死亡监听

AIDL拓展

  • 如果我们的服务想要更健全的话,我们有必要给他加上权限验证功能,因为在默认情况下,我们的远程服务是任何人都能连接的,这是我们不愿意看到的,所以我们必须给服务加入权限验证功能
  • 怎么加这个功能呢?我们想一下,服务最早接触客户端的方法是哪个方法?
  • 当然是onBind方法了,我们在绑定服务的时候,会使用onBind方法返回的对象来拿到我们操作服务的句柄,那么我们的权限验证就可以在这里做
  • 不过在做之前我们首先应该定义自己的权限
  • 这里简单说一下自定义权限

自定义权限简单说明

  • 在AndroidManifest.xml中,像这个样子
<permission android:description="string resource"
           android:icon="drawable resource"
           android:label="string resource"
           android:name="string"
           android:permissionGroup="string"
           android:protectionLevel=["normal" | "dangerous" |"signature" | "signatureOrSystem"] />
  • 这里对各个属性做个说明
属性 说明 是否必须
name 自定义的权限名称,需要遵循Android权限定义命名方案:.permission.
protectionLevel 与权限相关的”风险级别”。必须是以下值之一:normal, dangerous, signature, signatureOrSystem ,取决于保护级别,在确定是否授予权限时,系统可能采取不同的操作。
permissionGroup 可以将权限放在一个组中,但对于自定期义权限,应该避免设置此属性。如果确实希望设置此属性,可能使用以下属性代替:android.permisson-group.SYSTEM_TOOLS
label 对权限的一个简短描述
description 对权限的描述,一般是两句话,第一句话描述这个权限所针对的操作,第二句话告诉用户授予app这个权限会带来的后果
icon 权限可以与资源目录以外的图标相关联 ( 比如@drawable/myicon)
  • protectionLevel权限等级的说明
protectionLevel权限 说明
normal 权限被声明为Normal级别,任何应用都可以申请,在安装应用时,不会直接提示给用户,点击全部才会展示。
dangerous 权限被声明为Dangerous级别,任何应用都可以申请,在安装应用时,会直接提示给用户。
signature 权限被声明为Signature级别,只有和该apk(定义了这个权限的apk)用相同的私钥签名的应用才可以申请该权限。
signatureOrSystem 权限被声明为SignatureOrSystem级别,有两种应用可以申请该权限。1) 和该apk(定义了这个权限的apk)用相同的私钥签名的应用;2) 在/system/app目录下的应用
  • 然后我们在这里在我们的AndroidManifest.xml声明权限
<permission
        android:name="com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE"
        android:protectionLevel="normal"/>
  • 然后在我们的onBind方法添加权限验证
public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE");
        if(check == PackageManager.PERMISSION_DENIED){
            return null;
        }
        return mBinder;
    }
  • 如果我们自己的应用想要绑定我们的服务,只需要在他的AndroidManifest.xml中声明权限即可
<uses-permission android:name="com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE"/>
  • 当然,权限验证不仅仅局限于AIDL,他可以用在所有你想用的地方
  • 第二种权限验证的方法
  • 我们可以在服务端的Binder的onTransact方法中进行权限验证,如果验证失败,就直接返回false,这样服务端就不会去执行客户端想要的逻辑 ,也达到了权限验证的效果
  • 我们甚至还可以采用UID和PID来做验证,通过getCallingUID和getCallingPid来拿到客户端所属进程的UID和Pid,这样我们就可以验证包名,等具体的操作大家具体去尝试吧
  • 下面我们继续学习另外的IPC方式

使用ContentProvider

  • ContentProvider是Android中提供的专门用于不同应用间数据共享的方式,从这一点来看,他肯定是支持进程间通信的,和Messenger一样,他的底层实现也是Binder,由此可见,Binder在Android系统中的重要性
  • ContentProvider比Messenger使用起来要简单的多了,因为系统已经为我们做了封装,我们无需关心底层即可轻松实现IPC
  • 系统预置了好多ContentProvider,比如通讯录信息,日程表信息,等,要跨进程访问这些信息,只需要通过ContentProvider的query,updata,insert,delete方法即可
  • 下面我们就来看看,如何实现一个自己的ContentProvider,然后在其他进程中获取数据达到 IPC 的目的
  • 首先,创建一个继承自ContentProvider抽象类的自己的类即可,并实现六个抽象方法
public class MyDataProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}
  • 这里说一下六个方法的含义
  • onCreate()代表ContentProvider的创建,一般做一些初始化工作
  • insert,query,update,delete,分别根据我们不同的参数执行插入,查询,更新,删除数据 操作
  • getType方法用来返回一个Uri请求所对应的MIME(媒体类型,比如图片,视频)类型,官方的解释是:返回在ContentProvider中的数据类型,
  • 根据Binder的工作原理,我们知道这六个方法除onCreate都运行在ContentProvider所在进程的BInder线程池中
  • onCreate方法由系统回调并运行在主线程里
  • ContentProvider主要以表格的形式来组织数据,并且可以包含多个表,对于每个表来说,他们都具有行和列的层次性,行往往对应一条记录,列往往对应一条记录中的一个字段,这其实和数据库是很类似的
  • 除了表格形式,ContentProvider还支持文件数据,比如图片,视频等,处理非表格形式的数据的时候,可以在ContentProvider中返回文件的句柄给外界
  • Android系统所提供的MediaStore功能就是文件类型的ContentProvider
  • ContentProvider对数据的存储方式没有任何限制,我们可以使用数据库,也可以使用文件或者别的任何东西
  • 当我们使用ContentProvider首先需要在AndroidManifest.xml文件中注册(因为他也是属于四大组件)
<provider
       android:authorities="com.example.learnretrofit.LearnMoreProcess.MyContentProvider.MyDataProvider"
       android:name=".LearnMoreProcess.MyContentProvider.MyDataProvider"
       android:permission="com.example.permission.MyContentProvider.MyDataProvider"
       android:process=":provider"
       />
  • 这里的authorities是我们的ContentProvider的唯一标识,所以这里推荐直接使用包名
  • 这里声明的权限也是我们自定义的,如果外部应用想要使用我们这ContentProvider,就必须声明这个权限,这里需要注意的是,虽然这里使用的是我们自定义的权限,但还是要在AndroidManifest.xml中声明这个权限
  • 然后给他指定了进程
简单使用ContentProvider
  • 在我们的客户端中(也就是我们自己写的一个与ContentProvider不在同一个进程的Activity)
Uri uri = Uri.parse("content://com.example.learnretrofit.LearnMoreProcess.MyContentProvider.MyDataProvider");
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
  • 这里的uri就是我们在注册的时候写的那个唯一标识(authorities参数)
  • 为了不让我们的查询没有任何效果,这里我在query方法里面输出此时的线程名,log信息,在onCreate同样输出线程名
provider D/MyDataProvider: onCreate: Thread.currentThread().getName() = main
provider D/MyDataProvider: query: current Thread : Binder:18054_3
provider D/MyDataProvider: query: current Thread : Binder:18054_3
provider D/MyDataProvider: query: current Thread : Binder:18054_3
  • 可见,他是运行在Binder线程池中,也验证了他的内部是Binder实现
  • onCreate方法在第一次调用Uri.parse(“content://com.example.learnretrofit.LearnMoreProcess.MyContentProvider.MyDataProvider”);方法时候执行,并且执行在该方法所在线程,所以一般我们不要再onCreate方法中做耗时操作
  • 那么再详细的操作我们就可以给我们的ContentProvider添加一个数据库来保管我们的数据,这里就不再多说,关于android对本地数据的保存方案可以参考LitePal学习Android 数据库学习总结

使用Socket

  • Socket称之为套接字,是网络通信的概念,它分为流式套接字和用户数据报套接字两种,分别对应网络的传输控制层的TCP和UDP协议
  • TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过三次握手才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性
  • 而UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能,在性能上,UDP具有更好的效率,但是传输数据的准确性不能保证 ,尤其是在网络拥塞的情况下
  • 使用Socket来进行通信,首先需要声明权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
  • 然后来看看我们的目标,我们打算实现一个类似多人聊天室这种
  • 首先看一下服务端的设计
  • 这里用到Socket的操作我就不细说了,不太明白这个机制的小伙伴可以移驾这里
public class SocketService extends Service {



    private final static String TAG = "SocketService";

    private List<Socket> mSockets = new ArrayList<>();
    private ServerSocket mServerSocket = null;

    @Override
    public void onCreate() {
        new Thread(new TcpServer()).start();
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {

        return null;
    }

    private class TcpServer implements Runnable{

        @Override
        public void run() {
            //先启动服务端的30000端口号,如果不能启动就直接退出
            try {
                mServerSocket = new ServerSocket(30000);
                Log.d(TAG, "run: 服务端启动成功");
            } catch (IOException e) {
                Log.d(TAG, "run: 不能使用30000端口");
                e.printStackTrace();
                return;
            }
            //创建完成服务端之后就等着客户端来请求连接了
            while (!mServerSocket.isClosed()){
                try {
                    final Socket socket = mServerSocket.accept();
                    Log.d(TAG, "run: 收到了一个客户端");
                    //将服务器这边的Socket添加到集合
                    mSockets.add(socket);
                    //当来一个客户端连接成功,就会启动一个线程来监听客户端发送消息
                    new Thread(new myThread(socket)).start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //监听客户端发消息的线程
    private class myThread implements Runnable{

        Socket mSocket = null;

        public myThread(Socket socket) {
            mSocket = socket;
        }

        @Override
        public void run() {
            try {
                InputStream in = mSocket.getInputStream();

                byte[] b = new byte[1024];
                int i = -1;
                while (true) {
                    if ((i = in.read(b)) > 0) {
                        String s = new String(b, 0, i);
                        Log.d(TAG, "服务端接收: " + s);
                        //一旦收到某个客户端发送消息,就将这条消息发送到所有的客户端,以达到多人聊天的操作
                        for (Socket socket : mSockets){
                            OutputStream out = socket.getOutputStream();
                            out.write("我给你回个消息吧,免得你尴尬".getBytes());
                        }
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 代码注释写的很清楚
  • 接下来看一下客户端的代码
public class SocketService extends Service {
    private final static String TAG = "SocketService";

    private List<Socket> mSockets = new ArrayList<>();
    private ServerSocket mServerSocket = null;

    @Override
    public void onCreate() {
        new Thread(new TcpServer()).start();
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {

        return null;
    }

    private class TcpServer implements Runnable{

        @Override
        public void run() {
            //先启动服务端的30000端口号,如果不能启动就直接退出
            try {
                mServerSocket = new ServerSocket(30000);
                Log.d(TAG, "run: 服务端启动成功");
            } catch (IOException e) {
                Log.d(TAG, "run: 不能使用30000端口");
                e.printStackTrace();
                return;
            }
            //创建完成服务端之后就等着客户端来请求连接了
            while (!mServerSocket.isClosed()){
                try {
                    final Socket socket = mServerSocket.accept();
                    Log.d(TAG, "run: 收到了一个客户端");
                    //将服务器这边的Socket添加到集合
                    mSockets.add(socket);
                    //当来一个客户端连接成功,就会启动一个线程来监听客户端发送消息
                    new Thread(new myThread(socket)).start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //监听客户端发消息的线程
    private class myThread implements Runnable{

        Socket mSocket = null;

        public myThread(Socket socket) {
            mSocket = socket;
        }

        @Override
        public void run() {
            try {
                InputStream in = mSocket.getInputStream();

                byte[] b = new byte[1024];
                int i = -1;
                while (true) {
                    if ((i = in.read(b)) > 0) {
                        String s = new String(b, 0, i);
                        Log.d(TAG, "服务端接收: " + s);
                        //一旦收到某个客户端发送消息,就将这条消息发送到所有的客户端,以达到多人聊天的操作
                        for (Socket socket : mSockets){
                            OutputStream out = socket.getOutputStream();
                            out.write(("你发来了 "+ s+"\n 我给你回个消息吧,免得你尴尬").getBytes());
                        }
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 这里的Socket我就不细说了,这部分不难,不太理解的话自行百度吧
  • log信息
D/SocketService: run: 服务端启动成功
D/SocketService: run: 收到了一个客户端
D/SocketActivity: run: 连接成功 socket 30000,/127.0.0.1:56200
D/SocketService: 服务端接收: 我来啦
D/SocketActivity: 客户端接收: 我给你回个消息吧,免得你尴尬
D/SocketActivity: onClick: 给服务器发送 : baccalaureate
D/SocketService: 服务端接收: baccalaureate
D/SocketActivity: 客户端接收: 我给你回个消息吧,免得你尴尬
D/SocketActivity: onClick: 给服务器发送 : baccalaureate
D/SocketService: 服务端接收: baccalaureate
D/SocketActivity: 客户端接收: 我给你回个消息吧,免得你尴尬
  • 可以看到,我们利用Socket可以很方便的实现进程间的通信

再探AIDL-BinderPool连接池

  • 上面我们大概过了一下几种常用的IPC方式,在这里要再介绍一下AIDL,因为AIDL是一种最常用的进程间通信的方式,是日常开发中涉及进程间通信时的首首选
  • 在这里再回顾一下AIDL的使用流程,首先创建一个Service和AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,这里的Stub就是我们用到的Binder,在Service的onBind方法中返回这个类的实例,然后客户端通过绑定服务就可以跨进程通信了
  • 现在考虑一下假如一下子有好多个地方使用到AIDL通信,根据我们的用法,那是不是要创建很多个AIDL接口呢?甚至说创建很多个服务呢?
  • 理性的来思考,这肯定是不可行的,那么怎么办呢?针对上述问题,我们需要减少Service的数量,将所有的AIDL放在同一个Service中去管理
  • 大概是这样一个过程

    • 每个业务模块(也就是需要AIDL 的模块),创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务器提供自己唯一标识和其对应的Binder对象
    • 对于服务端来说,只需要一个Service就够了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回响应的Binder对象给他们,不同的业务模块拿到所需的Binder对象就可以进行远程方法调用了
      这里写图片描述
  • 虽然看起来挺好看的,可是对这个真正实现还是听迷糊的吧,接下来我们就来看看具体代码怎么实现
  • 我们先来实现所有我们直接操作的aidl接口吧,比方说,我们有两个客户端,一个读书客户端,一个玩耍客户端,(我在后面直接称这里两个客户端为子客户端或者子功能)接下来编写他们各自的aidl接口
  • 玩耍客户端
// play.aidl
package com.example.learnretrofit.LearnMoreProcess.BinderPool;

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

interface Play {

    int playGames(String gameName);

}
  • 读书客户端
// study.aidl
package com.example.learnretrofit.LearnMoreProcess.BinderPool;

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

interface Study {

    String readBook(String bookName);
}
  • 然后在实现我们的BinderPool接口(也就是操作上面子客户端的终极接口)
// IBinderPool.aidl
package com.example.learnretrofit.LearnMoreProcess.BinderPool;

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

interface IBinderPool {
    IBinder queryBinder(int BinderCode);
}
  • 这里先让系统生成AIDL的java文件,build - > make project
  • 然后先把我们的分客户端接口实现,也就是玩耍和读书接口实现
public class PlayImpl extends Play.Stub {

    public static final int PlayVeryGood = 1;
    private static final String TAG = "playImpl";

    @Override
    public int playGames(String gameName) throws RemoteException {
        Log.d(TAG, "playGames: 玩 " + gameName + " 很爽!");
        return PlayVeryGood;
    }
}
  • 这里的返回数字并没有独特的意义,只是为了展现服务端能够传出东西给客户端
public class StudyImpl extends Study.Stub {

    private static final String TAG = "studyImpl";

    @Override
    public String readBook(String bookName) throws RemoteException {
        Log.d(TAG, "readBook: 来到这里读书了");
        return "读 " + bookName + " 这本书真是太充实了!";
    }
}
  • 接下来就到重头戏了,我们这里要重点分析一下BinderPool是怎么来操作我们的分功能
  • 首先,既然这个BinderPool是用来全权操作分功能的,所以说,我们在客户端直接得到的BinderPool的跨进程Binder
  • 那么,这里为了用起来方便,我们直接使用单例模式封装好BinderPool的跨进程Binder
  • 看一下代码(这段代码是Binderpool类中的,这个类就是我们单独写的操作子客户端的类)

    • 先看一下单例模式部分
public static BinderPool getInstance(Context context) {
        if(sInstance == null){
            synchronized (BinderPool.class){
                if(sInstance == null){
                    Log.d(TAG, "getInstance: 实例化单例对象");
                    sInstance = new BinderPool(context);
                    Log.d(TAG, "getInstance: 准备返回到客户端");
                }
            }
        }
        return sInstance;
    }

private BinderPool(Context context){
        mContext = context.getApplicationContext();
        connectBinderService();
    }

private synchronized void connectBinderService() {
        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
        Intent service = new Intent(mContext,BinderPoolService.class);
        Log.d(TAG, "connectBinderService: 绑定服务");
        mContext.bindService(service,mBinderPoolConnection,Context.BIND_AUTO_CREATE);
        try {
            Log.d(TAG, "connectBinderService: mConnectBinderPoolCountDownLatch.await()");
            mConnectBinderPoolCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d(TAG, "connectBinderService: 绑定方法执行完毕");
    }
  • 这里我们不用管其他的,只管单例模式直接去绑定服务去了,这里会先执行服务端的onBind方法
public class BinderPoolService extends Service {

    private static final String TAG = "BinderPoolService";

    private Binder mBinderPool = new BinderPool.BinderPoolImpl();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind: 给客户端返回BinderPool");
        return mBinderPool;
    }
}
  • 这里返回的是BinderPool(Binder连接池的自身Binder),看一下这个对象的定义
public static class BinderPoolImpl extends IBinderPool.Stub{

        public BinderPoolImpl() {
            super();
        }

        @Override
        public IBinder queryBinder(int BinderCode) throws RemoteException {
            IBinder binder = null;
            switch (BinderCode){
                case BINDER_STUDY:
                    //
                    Log.d(TAG, "queryBinder: 实例化学习功能的Binder");
                    binder = new StudyImpl();
                    break;
                case BINDER_PLAY:{
                    binder = new PlayImpl();
                    break;
                }
            }
            return binder;
        }
    }
  • 然后是
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //拿到跨进程操作BinderPool的对象
            Log.d(TAG, "onServiceConnected: 拿到跨进程操作BinderPool的对象");
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                Log.d(TAG, "connectBinderService: 绑定死亡监听");
                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipent,0);
            } catch (RemoteException e) {
                Log.d(TAG, "onServiceConnected: 错误了");
                e.printStackTrace();
            }
            Log.d(TAG, "onServiceConnected: 连接完毕");
            mConnectBinderPoolCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
  • 可以看到,通过单例模式我们直接生成了操作BinderPool的跨进程Binder
  • 这个时候我们就可以去用这个单例模式返回的对象去操作各个子功能了
  • 还记得我们那会写的时候只在IBinderPool这个接口中提供了一个queryBinder方法
  • 这个方法的实现在上面已经见过了
public static class BinderPoolImpl extends IBinderPool.Stub{
        public BinderPoolImpl() {
            super();
        }
        @Override
        public IBinder queryBinder(int BinderCode) throws RemoteException {
            IBinder binder = null;
            switch (BinderCode){
                case BINDER_STUDY:
                    //
                    Log.d(TAG, "queryBinder: 实例化学习功能的Binder");
                    binder = new StudyImpl();
                    break;
                case BINDER_PLAY:{
                    binder = new PlayImpl();
                    break;
                }
            }
            return binder;
        }
    }
  • 可见这里是根据参数返回不同子功能的操作Binder
  • 那查询到了我们肯定不能直接用这里返回的这个东西来操作我们的子功能啊,还需要对他再包装一下
  • 比如说
IBinder playBinder = binderPool.queryBinder(BinderPool.BINDER_PLAY);
        Play play = PlayImpl.asInterface(playBinder);
        try {
            play.playGames("率土之滨");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
  • 好了到这里这个Binder连接池的内容就完了,慢慢过其实还是蛮清晰的
  • 我们再来看一下客户端里面的代码
private void doThings(){
        Log.d(TAG, "doThings: 准备获取binder单例对象");
        BinderPool binderPool = BinderPool.getInstance(BinderPoolActivity.this);

        Log.d(TAG, "doThings: 获取学习Binder对象");
        IBinder studyBinder = binderPool.queryBinder(BinderPool.BINDER_STUDY);
        Log.d(TAG, "doThings: 包装studyBinder 跨进程传输对象");
        Study study = StudyImpl.asInterface(studyBinder);
        String bookName = "《鬼吹灯》";
        try {
            study.readBook(bookName);
        } catch (RemoteException e) {
            e.printStackTrace();
        }

        IBinder playBinder = binderPool.queryBinder(BinderPool.BINDER_PLAY);
        Play play = PlayImpl.asInterface(playBinder);
        try {
            play.playGames("率土之滨");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
  • 如果还不明白,上张图
    这里写图片描述
  • 好了,到这里BinderPool连接池的原理,利用一个中间者来协调服务端和多客户端的联系
  • 这里要注意的是客户端的操作要尽可能放在线程中解决,因为不管他是否耗时,都不是我们可预知的
  • 下面,贴一下我得log信息,这样应该就没问题了吧
D/BinderPoolActivity: doThings: 准备获取binder单例对象
D/BinderPool: getInstance: 实例化单例对象
D/BinderPool: connectBinderService: 绑定服务
D/BinderPool: connectBinderService: mConnectBinderPoolCountDownLatch.await()
D/BinderPoolService: onBind: 给客户端返回BinderPool
D/BinderPool: onServiceConnected: 拿到跨进程操作BinderPool的对象
D/BinderPool: connectBinderService: 绑定死亡监听
D/BinderPool: onServiceConnected: 连接完毕
D/BinderPool: connectBinderService: 绑定方法执行完毕
D/BinderPool: getInstance: 准备返回到客户端
D/BinderPoolActivity: doThings: 获取学习Binder对象
D/BinderPool: queryBinder: 实例化学习功能的Binder
D/BinderPoolActivity: doThings: 包装studyBinder 跨进程传输对象
D/studyImpl: readBook: 来到这里读书了
D/playImpl: playGames: 玩 率土之滨 很爽!
  • 那么到这里,BinderPool连接池的原理就讲完了,在大型项目中使用这种机制可以很大的提高各个功能之间的协调性,又精简了程序逻辑代码

总结

  • 那么在上面,我们介绍了很多种IPC方式,在最后就来张表总结一下他们各自的特点
名称 优点 缺点 适用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件的进程间通信
文件共享 简单易用 不适合高并发场景,并且无法做到进程间的即时通信 无并发访问情形,交换简单的数据,实时性不高的场景
AIDL 功能强大,支持一对多并发通信 使用稍复杂,需要处理好线程同步 一对多通信且有RPC需求
Messenger 功能一般,支持一对多串行通信,支持实时通信 不能很好处理高并发类型,不支持RPC,数据通过Message传输,因此只能传输Bundle支持的数据类型 低并发的一对多即时通信,无RPC需求,或者无需要返回结果的RPC需求
ContentProvider 在数据访问方面功能强大,支持一对多并发数据共享,也可用Call方法扩展其他操作 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 一对多的进程间数据共享
Socket 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 实现细节稍微有点繁琐,不支持直接的RPC 网络数据交换

猜你喜欢

转载自blog.csdn.net/asffghfgfghfg1556/article/details/81352831