Android跨进程通信IPC之14——其他IPC方式

2018-08-30 12:01:13 浏览数 (1)

本片文章的主要内容如下:

1 Bundle 2 文件共享 3 Messager 4 ContentProvider 5 Socket 6 AIDL 7 广播 8 Binder连接池 9 符合选择合适的IPC方式

前面几篇文章,我们介绍了IPC的基础知识和Binder机制,本篇文章主要讲解各种跨进程的通信方式。

一、Bundle

(一)、IPC中的Bundle

我们知道,四大组件中三大组件(Activity,Service,Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle中实现了Parcelable接口,所以它方便地在不同的进程间传输。基于这一点,当我们在一个进程中启动了另一个进程的Activity、Service和Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。当然,我们传输的数据必须能够序列化,比如基本类型、实现了Parcelable接口的对象、实现了Serializable接口的对象以及一些Android支持的特殊对象,具体内容可以看Bundle这个类,就是可以看到所有它支持的类型。Bundle不支持的类型我们无法通过他在进程间传递,这是最简单的进程间通信方式。

除了直接传递数据这种典型的使用场景,它还有一种特殊的使用场景。比如进程A正在进行一个计算,计算完成后它要启动B进程的一个组件并把计算结果传递给B进程,可是遗憾的是这个计算结果不支持放入Bundle中,因此无法通过Intent来传输,这个时候如果我们用其他IPC方式就会略显复杂。可以考虑如下方式:我们通过Intent启动进程B的一个Service组件(比如IntentService),让Service在后台进行计算:计算完毕后再启动B进程中真正要启动的目标组件,由于Service也运行在B进程中,所以目标组件就可以接获取计算结果,这样一来就轻松解决了跨进程的问题。这种方式的核心思想在于将原本需要在A进程的计算任务转移到B进程的后台Service中去执行,这样就成功避免了进程间通信问题,而且只用了很小的代价

(二)、Bundle类简介

根据google官方文档 https://developer.android.com/reference/android/os/Bundle.htmlBundle类是一个key-value对,"A mapping from String keys to various Parcelable values."

可以看出,它和Map类型有异曲同工之妙,其实它内部是使用ArrayMap来存储的,并且实现了Parcelable接口,那么它是支持进程间通信的。所以Bundle可以看做是一个特殊的Map类型,它支持进程间通信,保存了特定的数据。

PS:Bundle继承自BaseBundle ,而在BaseBundle 中有一个内部变量叫mMap就是ArrayMap<String, Object>类型

(三)、Bundle类的重要方法

  • clear():清楚此Bundle映射中的所有保存的数据
  • clone():克隆当前Bundle
  • containKey(String key):是否包含key
  • getString(String key):返回key对应的String类型的value
  • hasFileDescriptors():指示是否包含任何捆绑打包文件描述
  • isEmpty():判断是否为空
  • putXxx(String key,Xxx value):插入一个键值对
  • writeToParcel(Parcel parcel,int flags),写入一个parcel
  • readFromParcel(Parcel parcel) 读取一个parcel
  • remove(String key):移除特定key的值

二、文件共享

共享文件也是一种不错的进程间通信方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。由于Android是基于Linux的,所以并发读/写文件可以没有限制地进行,甚至两个线程同时对同一份文件进行读写操作都是允许的,尽管这可能出现问题。通过文件交换数据很方便使用,除了可以交换一些文本信息外,我们还可以序列化一个对象到文件系统中的同时从另一个进程中恢复这个对象。

PS:反序列化得到的对象只是在内容上和序列化之前的对象是一样的,但它们本质上还是两个对象。

通过文件共享这种方式共享数据对文件格式是没有具体要求的,比如可以是文本文件,也可以是XML文件,只要读/写双方约定数据格式即可。通过文件共享的方式也是有局限的,比如并发读/写的问题。如果并发读/写,那么我们读出的内容就有可能不是最新的,如果是并发写的话,那就更严重了。因此我们要尽量避免比规范法写这种情况发生或者考虑用线程同步来限制多个线程的写操作。通过上面的分析,我们可以知道,文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且妥善处理并发读/写的问题。

说到文件共享又不得不说下SharedPreferences(后面简称SP),当然SP是个特例,众所周知,SP是Android中提供的轻量级存储方案,它通过键值对的方式来存储数据,在底层实现上它采用XML文件来存储键值对,每个应用的SP文件都可以在当前包所在的data目录下查看到。一般来说,它的目录位于/data/data/package name/shared_prefs 目录下,其中package name表示的是当前应用的包名。从本质上来说,SP也属于文件的一种,但是由于系统对它的读/写有一定缓存策略,即在内存会有一份SP文件的缓存,因此在多进程模式下,系统对它的读/写就变的不可靠,当面对高并发的读/写访问,SP有很大几率会丢失数据,因此,不建议在进程间通信中使用SP。

三、Messenger

(一)、概述

前面Android跨进程通信IPC之11——AIDL讲解了AIDL,用于Android进程间的通信。大家知道用编写AIDL比较麻烦,有没有比较"好的"AIDL。那我们就来介绍Messenger。

Messenger是一种轻量级的IPC方案,其底层实现原理就是AIDL,它对AIDL做了一次封装,所以使用方法会比AIDL简单,由于它的效率比较低,一次只能处理一次请求,所以不存在线程同步的问题。

Messenger的官网地址是https://developer.android.com/reference/android/os/Messenger.html

看下官网描述

Reference to a Handler, which others can use to send messages to it. This allows for the implementation of message-based communication across processes, by creating a Messenger pointing to a Handler in one process, and handing that Messenger to another process.

大概的意思是说,首先Messenger要与一个Handler相关联,才允许以message为基础的会话进行跨进程通讯。通讯创建一个messenger指向一个handler在同一个进程内,然后就可以在另一个进程处理messenger了。

这里的重点是

代码语言:javascript复制
This allows for the implementation of message-based communication across processes

允许实现基于消息的进程间通讯方式。

那么什么是基于消息的进程间通信方式?看图理解下:

允许实现基于消息的进程间通讯方式.png

可以看到,我们可以在客户端发送一个Message给服务端,在服务端的handler会接受客户端的消息,然后进行队形的处理,处理完成后,在将结果等数据封装成Message,发送给客户端,客户端的handler中会接收到处理的结果。

这样对比AIDL就方便很多了

  • 基于 Message,详细大家都容易上手
  • 支持回调的方式,也就是服务端处理完任务可以和客户端交互
  • 不需要编写AIDL文件 此外,还支持,记录客户端对象的Messenger,然后可以实现一对多的通信;甚至作为一个转接处,任意两个进程都能通过服务端进行通信。
(二)、构造函数

通过我们分析Messenger类的结构,如下图

Messenger类的结构.png

我们发现Messager有两个构造函数

1 构造函数1

代码在Messenger.java) 43行

代码语言:javascript复制
    /**
     * Create a new Messenger pointing to the given Handler.  Any Message
     * objects sent through this Messenger will appear in the Handler as if
     * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
     * been called directly.
     *
     * @param target The Handler that will receive sent messages.
     */
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

先翻译下注释

传入一个Handler作为参数创建一个新的Messenger,通过该Messenger发送的任何消息对象将会出现在这个Handler里面,就像Handler直接调用sendMessage(Message)一样。入参target接收这些已经发送的消息。

1.1 getIMessenger()方法

我们看到这个构造函数很简单,就是调用了getIMessenger()方法,我们跟踪一下,代码在Handler.java 708行

代码语言:javascript复制
    final IMessenger getIMessenger() {
        synchronized (mQueue) {
            if (mMessenger != null) {
                return mMessenger;
            }
            mMessenger = new MessengerImpl();
            return mMessenger;
        }
    }

这里使用了线程同步的懒汉式单例模式。并且这里面new了一个MessengerImpl()对象,MessengerImpl其实是Handler的内部类

1.2 MessengerImpl类

Handler.java 718行

代码语言:javascript复制
    private final class MessengerImpl extends IMessenger.Stub {
        public void send(Message msg) {
            msg.sendingUid = Binder.getCallingUid();
            Handler.this.sendMessage(msg);
        }
    }

  • 1、IMessenger,Stub 这样的结构大家有没有印象?是不是很想AIDL里面的形式,我们先搜一下IMessenger,发现没有对应的.java文件,只有一个IMessenger.aidl,所以说Messenger底层是基于AIDL的。
  • 2、当发送消息的时候,调用是Handler.this.sendMessage(msg);,其实就是自己调用的sendMessage(msg)方法而已。
2 构造函数2

代码在Messenger.java) 145行

代码语言:javascript复制
   /**
     * Create a Messenger from a raw IBinder, which had previously been
     * retrieved with {@link #getBinder}.
     *
     * @param target The IBinder this Messenger should communicate with.
     */
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }

先来翻译一下注释

根据原始的IBinder来创建一个Messenger对象,可以通过getBinder()方法来获取这个IBinder对象。入参target其实就是这个Messenger与之通信的

这个更简单了,其中IMessenger.Stub.asInterface(target);这个方法简直就是AIDL方式里面讲到的方法,有没有?

(三)、其他两个重要方法
1、getBinder()方法

代码在Messenger.java66行

代码语言:javascript复制
    /**
     * Retrieve the IBinder that this Messenger is using to communicate with
     * its associated Handler.
     * 
     * @return Returns the IBinder backing this Messenger.
     */
    public IBinder getBinder() {
        return mTarget.asBinder();
    }

注释翻译:返回这个Messenger使用Handler与之通讯的IBinder对象

返回一个IBinder对象,一般在服务端的onBinder()方法里面调用这个方法,返回给客户端一个IBinder对象。

2、send(Message msg)方法

代码在Messenger.java56行

代码语言:javascript复制
    /**
     * Send a Message to this Messenger's Handler.
     * 
     * @param message The Message to send.  Usually retrieved through
     * {@link Message#obtain() Message.obtain()}.
     * 
     * @throws RemoteException Throws DeadObjectException if the target
     * Handler no longer exists.
     */
    public void send(Message message) throws RemoteException {
        mTarget.send(message);
    }

注释翻译:发送消息给Messenger的Handler。入参message是要被发送的消息,通常通过Message.obtain()来获取,如果目标Handler不存在,就抛出RemoteException异常

发送一个message对象到 messagerHandler。这里我们传递的参数是一个Message对象

(四)、举例说明
1 服务端进程

首先我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBinder中返回这个Messenger对象底层的Binder即可。

代码如下

代码语言:javascript复制
public class MessageService extends Service {

    private  static class MessaengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case Constants.CLENT_MESSAGE:
                    Log.i("GELAOLITOU","收到消息:" msg.getData().get(Constants.KEY));
                    break;
                default:
                    break;
            }
        }
    }

    private Messenger messenger=new Messenger(new MessaengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }
}

当然别忘记在AndroidManifest.xml里面注册

代码语言:javascript复制
        <service android:name=".MessageService"
                      android:process=".remote" />
2 客户端进程

客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为Message对象。如果需要服务端能够回应客户端,就和服务端一样,我们还要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。这听起来可能还是有点抽象。那我们就直接上代码

代码语言:javascript复制
public class MainActivity extends AppCompatActivity {


    private Messenger mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent=new Intent(this,MessageService.class);
        bindService(intent,connection, Context.BIND_AUTO_CREATE);

    }


    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService=new Messenger(service);
            //创建一个what值为Constants.CLENT_MESSAGE的message
            Message message=Message.obtain(null,Constants.CLENT_MESSAGE);
            Bundle bundle=new Bundle();
            bundle.putString(Constants.KEY,"我是来自客户端的信息");
            message.setData(bundle);
            try{
                mService.send(message);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onDestroy() {
        unbindService(connection);
        super.onDestroy();
    }
}

运行app,得出下面的截图

结果.png

(五)、服务端响应客户端请求

上面的例子演示了如何在服务端接收客户端中发送的消息,但是有时候我们还需要能回应客户端,下面就介绍如何实现这种效果。还是采用上面的例子,但是稍微做一下修改,每当客户端发送来一条消息,服务端就会回复一条"你的信息我已经收到,现在就回复你"。

1、首先服务端的修改

服务端只修改MessengerHandler,当收到消息后,会立即回复一条消息给客户端。

代码语言:javascript复制
public class MessageService extends Service {


    private  static class MessaengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case Constants.CLENT_MESSAGE:
                    Log.i("GELAOLITOU","收到消息:" msg.getData().get(Constants.KEY));
                    Messenger msgr_client= msg.replyTo;
                    Message mes_reply=Message.obtain(null,Constants.SERVER_MESSAGE);
                    Bundle bundle=new Bundle();
                    bundle.putString(Constants.REPLY_KEY,"你的信息我已经收到,现在就回复你");
                    mes_reply.setData(bundle);

                    try {
                        msgr_client.send(mes_reply);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    break;
            }
        }
    }

    private Messenger messenger=new Messenger(new MessaengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }
}
2、其次客户端的修改

为了接收服务端的回复,客户端也需要准备一个接收消息的Messenger和Handler。代码如下:

代码语言:javascript复制
public class MainActivity extends AppCompatActivity {


    private Messenger mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent=new Intent(this,MessageService.class);
        bindService(intent,connection, Context.BIND_AUTO_CREATE);

    }


    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService=new Messenger(service);
            //创建一个what值为Constants.CLENT_MESSAGE的message
            Message message=Message.obtain(null,Constants.CLENT_MESSAGE);
            Bundle bundle=new Bundle();
            bundle.putString(Constants.KEY,"我是来自客户端的信息");
            message.setData(bundle);
            try{
                mService.send(message);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };


    private Messenger mGetReplyMessenger=new Messenger(new MessengerClientHandler());

    private static class MessengerClientHandler extends Handler{

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case Constants.SERVER_MESSAGE:
                    Log.i("GEBILAOLITOU","收到 服务端的消息,消息内容是" msg.getData().getString(Constants.REPLY_KEY));
                    break;
            }

        }

    }


    @Override
    protected void onDestroy() {
        unbindService(connection);
        super.onDestroy();
    }
}

除了上述修改,还有很多关键的一点,当客户端发送消息的时候,需要把接收服务端的Messenger通过Message的replyTo参数传递给服务端,如下所示:

代码语言:javascript复制
    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService=new Messenger(service);
            //创建一个what值为Constants.CLENT_MESSAGE的message
            Message message=Message.obtain(null,Constants.CLENT_MESSAGE);
            Bundle bundle=new Bundle();
            bundle.putString(Constants.KEY,"我是来自客户端的信息");
            message.setData(bundle);
            
            //新增,这是重点
            message.replyTo=mReplyMessenger;
            try{
                mService.send(message);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

通过上述修改,我们再运行程序,然后看一下log,很显然,客户端收到了服务端的回复。请看截图:

结果截图.png

(六)、总结

通过上面的例子可以看出,在Messenger中进行数据传递必须将数据放入Message中,而Messenger和Message都实现了Parcelable接口,因此可以跨进程传输。简单来说,Message中所支持的数据类型就是Messenger所支持的传输类型。实际上,通过Messenger来传输Message,Message中只能使用的载体只有what、arg1、arg2、Bunder以及replyTo。Messag中的另一个字段object在同一个进程中是很实用的,但是在进程间通信的时候,在Android2.2以前object字段不支持跨进程传输。即便是2.2以后,也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。这就意味着我们自定义的Parcelable对象是无法通过object字段来传输的,不过所幸我们还有Bundle,Bundle中可以支持大量的数据类型。

下面给出一张Messenger的工作原理图,以便大家更好地理解Meseenger.

Messenger的工作原理.png

四、ContentProvider

ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,从这一点来看,它天生就适合进程间通信。和Messenger一样,ContentProvider的底层同样也是Binder,由此可见Binder在Android系统是何等重要。虽然Content Provider的底层实现是Binder,但是它的使用过程要比AIDL简单许多,这是因为系统已经为我们做了封装,使得我们无须关心底层细节即可轻松实现IPC。ContentProvider虽然使用起来很简单,包括自己创建一个ContentProvider也不是什么难事,尽管如此,它的细节还是很多的。比如CRUD操作等。

系统预置了许多ContentProvider,比如通讯录信息、日程表信息等,要跨进程访问这些信息,只需要通过ContentResolver的query、update、insert和delete方法即可。受篇幅的限制,我这里就不粘贴ContentProvicder的代码了,这块代码网上很多,大家可以自行去搜索

PS:这里需要注意的是query、update、insert、delete四大方法是存在多线程并发访问的,因此方法内部要做好线程同步。而大多数android手机上采用的是SQLite,并且只有一个SQLiteDatabase的时候,要正确应对多线程的情况。因为SQLiteDatabase内部对数据库的操作有同步处理。如果ContentProvider的底层数据是一块内存的话,比如是List,在这种情况下同List遍历、插入、删除操作就需要进行线程同步,否则会一起并发错误。这点要特别注意。

五、Socket

(一) Socket 简述

我们也可以通过socket来实现进程间通信。Socket也称为"套接字",是网络通信中的概念。它分为流式套接字和用户数据报套接字两种。分别对应网络传输控制层的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过"三次握手"才能完成,为了提供稳定的数据传输功能,其本身提供的超时重传机制,因此具有很高的稳定性;而UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能。在性能上,UDP具有更好的效率,其确定啊是不保证数据一定能够正确传输,尤其是在网络拥塞的情况下。关于TCP和UDP的介绍就这么多,更详细的资料请查看相关网络资料。

PS:在使用Socket进行跨进程通信的时候,有两点需要注意

  • 1 首先要在AndroidManifest上声明权限 <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  • 2 其次不能在主线程中访问网络,这回导致在我们在Android 4.0及其以上设备,会抛出异常NetworkOnMainThreadException异常。而且进行网络操作很可能是耗时的,如果放在主线程中,会影响响应效率,这方面来说也不应该在主线程中访问网络。
(二) 举例说明

这块的例子很多,大家上网搜一下,推荐这边博客[Android IPC机制(五)用Socket实现跨进程聊天程序(https://link.jianshu.com/?t=http://blog.csdn.net/itachi85/article/details/50667740)

六、AIDL:

具体请参考Android跨进程通信IPC之11——AIDL

七、使用广播(Broadcast)

广播是一种被动跨进程的通讯方式。当某个程序向系统发送广播时,其他的应用程序只能被动的接受广播数据。就像电台进行广播一样,听众只能被动地收听,而不能主动的与电台进行沟通。

BroadcastReceiver本质上是一种系统界别的监听器,他专门监听各种程序发出的Broadcast,因此他拥有自己的进程,只要存在与之匹配的Intent被广播出来,BroadcastReceiver总会被激发。我们知道,只有先注册了某个广播之后,广播接收者才能收到该广播。广播注册的一个行为将是自己感兴趣的IntentFliter注册到Android系统的AMS(ActivityManagerService)中,里面保存了一个IntentFilter列表。广播发送者将自己的IntentFilter的action行为发送到AMS中,然后遍历AMS中的IntentFilter列表,看谁订阅了该广播,然后将消息遍历发送到注册了相应的IntentFilter的Activity或者Service中中,也就是会调用抽象方法onReceive()方法。其中AMS起到了中间桥梁的作用。

程序启动的BroadcastReceiver只需要两步:

  • 1 创建需需启动的BroadcastReceive的Intent
  • 2 调用Context的sendBroadcast()或sendOrderBroadcast()方法来启动指定的BroadcastReceiver。

每当Broadcast事件发生后,系统会创建对应的BroadcastReceiver实例,并自动触发onReceiver()方法。onReceiver()方法执行完后,BroadcastReceiver实例就会被销毁。

注意:onReceiver()方法中尽量不要做耗时操作,如果onReceiver()方法不能在10s内完成事件处理,Android会认为该程序无响应,也就弹出我们熟悉的ANR对话框。如果我们需要在接收到广播小猴进行一些耗时的操作,我们可以考虑通过Intent启动一个Server来完成操作,不应该启动一个新的线程来完成操作,因为BroadcastReceiver生命周期很短,可能新建线程还没有执行完,BroadcastReceiver已经销毁了,而如果BroadcastReceiver结束了,它所在的进程中虽然还有启动的新线程执行任务,可是由于该进程中已经没有任何组件,因此系统会在内存紧张的情况下回收该进程,这就导致BroadcastReceiver启动的子线程不能执行完成。

八、Binder连接池

上面我们介绍了不同的IPC方式,我们知道不同的IPC方式有不同特点和使用场景,这里还是要在说一下AIDL,因为AIDL是一种常见的进程间通信方式,是日常开发中设计进程通信时的首选。

如何使用AIDL在Android跨进程通信IPC之11——AIDL中已经详细介绍了,现在回顾一下大致流程:首先创建一个Service和AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,在Service的onBinder方法中返回这类的对象,然后客户端就可以绑定服务端的Service,建立连接后,就可以访问远程服务端的方法了。

上面的过程就是典型的AIDL的使用流程。这本来也没什么问题,但是现在考虑一种情况:公司项目越来越大了,现在有10个不同的业务模块都需要使用AIDL来进行进程间通信,那我们该怎么处理?或许有人会说按照AIDL昂视一个一个来吧,如果有100个地方需要用到AIDL,难道就要创建100个Service?大家应该明白随AIDL数量的增加,我们不能无限制的增加Service,Service是四大组件之一,本身就是一种系统资源。而且太多的Service会使得我们的应用看起来很重量级,因此正在运行的Service可以在应用详情页看到,而且让用户看到有10个服务正在运行,也很坑,针对上面的问题,我们需要减少Service的数量,将所有的AIDL放到同一个Service中去管理。

在这种某事下,整个工作机制是这样的:每个业务模块都创建自己的AIDL接口并实现此接口,这时候不同的业务模块之间是不能耦合的,所有实现细节我们都要单独来看,然后向服务端提供自己的唯一标示和对应的Binder对象;对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Bidner对象后就可以进行远程方法调用了。由此可见,Binder连接池的主要作用就是将每个业务模块的Binder请求转发到远程Service中去执行,从而避免了重复创建Service的过程。原理如下图:

Binder连接池.png

上面是理论,也许不是很好理解,现在我们用代码的形式给大家弄一个demo。既然是demo,我们就不弄100个接口了,就弄3个好了,一个是登录的用户模块的IUser,一个是订单模块的IOrder,一个是给交易模块的IPay,其中IUser提供登录,和登出功能,IOrder提供下单和取消订单功能,IPay提供支付和取消支付功能,具体代码如下:

1、编写相应的AIDL文件

编写 IOrder.aidl文件

代码语言:javascript复制
interface IOrder {
    void doOrder(long orderId);
    void cancelOrder(long orderId);
}

编写IPay.aidl文件

代码语言:javascript复制
interface IPay {
    void doPay(long payId,int amount);
    void cancelPay(long payId);
}

编写IUser.aidl文件

代码语言:javascript复制
interface IUser {


    void login(String userName,String passwork);
    void logout(String username);
}
2、编写具体的的AIDL中Stub的具体实现类

当然在具体写实现类的时候,别忘记同步项目,否则你是找不到相应的Stub类的

(1) 编写IPayImpl.java类
代码语言:javascript复制
/**
 * Created by gebilaolitou on 2017/8/26.
 * 支付模块的实现
 */

public class IPayImpl extends IPay.Stub  {
    @Override
    public void doPay(long payId, int amount) throws RemoteException {
        Log.i("GEBILAOLITOU","IPayImpl  doPay, payId=" payId ",amount=" amount);
    }

    @Override
    public void cancelPay(long payId) throws RemoteException {
        Log.i("GEBILAOLITOU","IPayImpl  doPay, payId=" payId);
    }
}
(2) 编写IUserImpl.java类
代码语言:javascript复制
/**
 * Created by gebilaolitou on 2017/8/26.
 * 用户模块的实现
 */

public class IUserImpl extends IUser.Stub {


    @Override
    public void login(String userName, String password) throws RemoteException {
        Log.i("GEBILAOLITOU","IUserImpl  login, userName=" userName ",password=" password);
    }

    @Override
    public void logout(String username) throws RemoteException {
        Log.i("GEBILAOLITOU","IUserImpl  logout, userName=" username);
    }
}
(3) 编写IUserImpl.java类
代码语言:javascript复制
/**
 * Created by gebilaolitou on 2017/8/26.
 * 订单模块的实现
 */

public class IOrderImpl extends IOrder.Stub {
    @Override
    public void doOrder(long orderId) throws RemoteException {
        Log.i("GEBILAOLITOU","IOrderImpl  doOrder, oderId=" orderId);
    }

    @Override
    public void cancelOrder(long orderId) throws RemoteException {
        Log.i("GEBILAOLITOU","IOrderImpl  cancelOrder, oderId=" orderId);
    }
}
3、定义IBinderPool接口并实现之。
(1) 编写IBinderPool.aidl
代码语言:javascript复制
interface IBinderPool {
    IBinder queryBinder(int binderCode);
}
(2) 编写IBinderPool的具体实现类
代码语言:javascript复制
/**
 * Created by gebilaolitou on 2017/8/26.
 * 连接池
 */

public class BinderPoolImpl extends IBinderPool.Stub {

    /**
     * query  方法就是根据请求功能模块的代码,返回相应模块的实现
     * @param binderCode
     * @return
     * @throws RemoteException
     */
    @Override
    public IBinder queryBinder(int binderCode) throws RemoteException {
        Log.d("GEBILAOLITOU", "BinderPoolImpl queryBinder  binderCode=" binderCode);
        switch (binderCode){
            case Constans.TYPE_ORDER:
                return new IOrderImpl();
            case Constans.TYPE_PAY:
                return new IPayImpl();
            case Constans.TYPE_USER:
                return new IUserImpl();
        }
        return null;
    }
}
4、编写相应的Service。
代码语言:javascript复制
public class BinderPoolService extends Service {

    private BinderPoolImpl mBinderPoolImp = new BinderPoolImpl();
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinderPoolImp;
    }
}

在AndroidManifest.xml作相应的配置

代码语言:javascript复制
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.gebilaolitou.binderpooldemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".BinderPoolService"
            android:process=".binderpoolservice"
            />
    </application>

</manifest>
5、客户端的实现
(1)实现一个BinderPool的管理器

为了操作方便,将所有的BinderPool操作放在同一个类里面。这个类的大体结构和标准的AIDL客户端基本相同。首先要有一个IBinderPool类型的私有变量,然后定义一个ServiceConnection类型的私有变量,并在它的onServiceConnected 方法里面给IBinderPool类型的变量赋值,然后定义一个bindBinderPoolService函数,在里面bindService。完整的代码如下:

代码语言:javascript复制
public class BinderPoolMananger {


    private Context mContext;
    private static BinderPoolMananger sInstance;
    private IBinderPool mBinderPool;
    private CountDownLatch mCountDownLatch;

    private BinderPoolMananger(Context context){
        this.mContext = context;
        bindBinderPoolService();
    }

    public static BinderPoolMananger getInstance(Context context){
        if(sInstance == null){
            sInstance = new BinderPoolMananger(context);
        }
        return sInstance;
    }


    private ServiceConnection mServiceConnection = new ServiceConnection(){
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("GEBILAOLITOU", "onServiceConnected() 被调用了");
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                //死亡通知
                mBinderPool.asBinder().linkToDeath(new IBinder.DeathRecipient() {
                    @Override
                    public void binderDied() {
                        mBinderPool.asBinder().unlinkToDeath(this, 0);
                        mBinderPool = null;
                        bindBinderPoolService();
                    }
                }, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private void bindBinderPoolService(){
        Log.d("GEBILAOLITOU", "bindService()  被调用");
        mCountDownLatch = new CountDownLatch(1);
        Intent intent = new Intent(mContext,BinderPoolService.class);
        Log.d("GEBILAOLITOU", "开始 bind service");
        mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        try {
            mCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d("GEBILAOLITOU", "finish to bind service");
    }


    public IBinder query(int code){
        Log.d("GEBILAOLITOU", "query() 被调用, code = "   code);
        IBinder binder = null;
        try {
            binder = mBinderPool.queryBinder(code);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return binder;
    }
}
(2) 调用BinderPool管理器
代码语言:javascript复制
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                doBinder();
            }
        }).start();
    }

    private void doBinder() {
        BinderPoolMananger bm = BinderPoolMananger.getInstance(MainActivity.this);
        
        IOrder order = IOrder.Stub.asInterface(bm.query(Constans.TYPE_ORDER));
        try {
             order.doOrder(123L);
             order.cancelOrder(123L);
        } catch (RemoteException e) {
            e.printStackTrace();
            Log.d("GEBILAOLITOU", "order 模块出现异常,e=" e.getMessage());
        }

        IPay pay = IPay.Stub.asInterface(bm.query(Constans.TYPE_PAY));

        try {
            pay.doPay(123L,1000);
            pay.cancelPay(123L);
        } catch (RemoteException e) {
            e.printStackTrace();
            Log.d("GEBILAOLITOU", "pay 模块出现异常,e=" e.getMessage());
        }


        IUser user = IUser.Stub.asInterface(bm.query(Constans.TYPE_USER));

        try {
            user.login("gebilaolitou","123456");
            user.logout("gebilaolitou");
        } catch (RemoteException e) {
            e.printStackTrace();
            Log.d("GEBILAOLITOU", "user 模块出现异常,e=" e.getMessage());
        }
    }
}
6、其他代码
代码语言:javascript复制
public class Constans {

    public static final int TYPE_USER=1;  //用户模块
    public static final int TYPE_PAY=2;   //支付模块
    public static final int TYPE_ORDER=3;  //订单模块
}
7、项目结构

如下图:

项目结构.png

8、结果输出

结果输出.png

七、选择合适的IPC方式

receiver其实本质是用的Bundle来实现的,Binder连接池其实本质是AIDL,所以我们大概可以分为 Bundle、AIDL、Messenger、ContentProvider、Socket。

合适IPC方案.png

0 人点赞