05
2017
10

Android 中IPC实践

1、什么是进程间通信(IPC)

Android中的进程间通信(IPC)是指一个应用的组件运行在不同的进程中,组件之间需要进行数据交换;或多个应用的组件之间需要进行数据交换。

2、为什么使用进程间通信?

一般在APP的实际开发中基本一个应用一个进程。使用多进程会增加降低代码的可阅读性,还要编写额外的跨进程通信代码,还可能带来一些不可预知的bug。比如单例模式失效,线程同步失效等等。这些将大大增加了APP的开发难度和工作量。
但是,整个APP就一个进程就不会出现问题吗?特别是做航空母舰级别的APP,分分钟感觉内存被耗尽,随时会OOM!一般情况下每启动一个Android应用系统就启动一个Android虚拟机。Android系统各个版本对虚拟机分配的内存是有限制的,限制在16M,32M,64M等等,视具体机型而定。如果你的APP有大量的图片显示,使用频繁的消息系统、长连接推送、大量的网页加载等等。在这样一个应用场景中,试问你的内存还好吗?估计没多久就crash一次,查看日志就是OOM!!!
这样的应用场景微信和微博不也遇到了,看看他们是怎么解决的。
这里写图片描述
图1

:图1所示微博和微信都使用了多进程技术。包含图片推送通知核心业务相关的进程。所以遇到类似和服务端频繁交互的复杂业务模块、大量图片加载显示等业务场景,可以使用多进程的方式:
(1)获取更大的内存;(2)复杂和频繁的操作、业务逻辑转移到子进程,主进程只负责UI显示,这样子进程在处理业务逻辑时遇到不可预知的错误如 NPE ,子进程挂啦!对仅仅是子进程挂了,主进程完好无损,通过死忙代理完全可以重启子进程继续运行。这样就不会出现整个App挂掉的情形了,同时也一定程度上留住了用户。

3、跨进程通信的方式有哪些?

(1)、Activity、Service、Receiver 通过Bundle进行进程间通信。(因为Bundle实现了parcelable接口)。

(2)、多进程读写同一文件的内容实现进程间通信。不建议使用SharedPreferencess进行数据共享,因为它有内存缓存很容易出问题。

(3)、使用底层实现原理是AIDL的Messenger进行进程间通信,只适用于低并发场景。

(4)、AIDL进行进程间通信

(5)、使用数据共享型组件ContentProvider实现数据共享。

(6)、使用Socket进程进行IPC通信

:以上是进程间通信的几种实现。第一种利用Bundle的进程间通信方式只能支持Bundle所支持的数据格式,注定在某些场景的应用受限。文件方式的进程间通信不适用于高并发场景,无法做到进程间的实时通信。Messenger方式的进程间通信是串行的不能很好的处理高并发场景,但是底层实现也是基于AIDL方式。ContentProvider进行进程间通信底层也是基于AIDL原理。Socket主要用于网络的数据通信。AIDL方式的跨进程通信支持并发和实时通信的。通过比较在实时性和并发有要求的应用优先考虑AIDL进程间通信。所以本文只讲述AIDL方式的跨进程通信,包括传统的Activity 绑定service方式,进程回调、以及Binder池方式的进程间通信方式。本文默认的是一个应用多个进程的模式。当然也可以多个应用多个进程的方式,只需要将服务端的aidl文件复制一份到客户端即可。

4、如何使用AIDL进程间通信?

Android 的进程间通信是基于Binder的C/S模式。Binder是基于OpenBinder实现的 ,它采用的是CS通信方式,其中提供服务的是进程称为Server进程,访问服务的进程称为Client进程。本节首先实现客户端绑定远程服务并且调用远程服务的方法,了解整个流程;然后模拟服务端的耗时操作,客户端阻塞等待导致ANR以及处理方法;然后讲述回调方式的进程间通信方式,客户端主进程程调用了远程服务端的方法后,服务端的方法处理完后会通知客户端去做下一步处理。最后通过Binder池解决传统方式下的AIDL进程间通信不断创建Service组件导致应用重量级的问题。

4.1 IPC的一般实现流程

(1)在main文件夹下新建一个aidl文件夹 然后编写AIDL文件,如有需要传递的Parcelable对象,需要和java文件夹下的bean目录对应。如下图2所示:
这里写图片描述

(2)AIDL文件支持的参数以及说明:JAVA的基本类型、List(只支持ArrayList)、Map、实现了Parcelable的对象,AIDL接口;
接口中的参数 除了基本类型 均需要加入标识是输入型参数(in)还是输出型参数(out)或者输入输出型参数(inout)。如下所示:

// IGetAllInfo.aidl
package com.yqq.androidmutilprocess;
import com.yqq.androidmutilprocess.MessageReceiver4Test;
interface IGetAllInfo {
            //请求获取所有信息
     void reqAllInfo(in String name, in String content);
     void registerReceiveListener(in MessageReceiver4Test allInfosCallBack);
     void unregisterReceiveListener(in MessageReceiver4Test allInfosCallBack);
}

除此之外,需要在IPC中进行传递的parcelable对象,需要显式申明aidl接口;所有的parcelable接口和aidl接口均需要显示的导包,4.2节对MessageReceiver4Test进行详细描述。如下所示:
parcelable对象

// TestInfo.aidl
//parcelable对象
package com.yqq.androidmutilprocess.bean;
    parcelable TestInfo;

说明:这里parcelable是个类型,首字母是小写的,和Parcelable接口不是一个东西
parcelable 类型导包

// MessageReceiver4Test.aidl
package com.yqq.androidmutilprocess;
//parcelable 接口导包
import com.yqq.androidmutilprocess.bean.TestInfo;
interface MessageReceiver4Test {
        //获取的信息用于主进程使用
        void onAllInfoReceiver4Test(in TestInfo testInfos,in String rs);
          //发送获取信息后的反馈主进程使用
        void onReqHotInfoCallBack(in String rs);
}

说明:无论是否在不在同一个包下,parcelable类型都需要导包。
aidl接口导包

// IGetAllInfo.aidl
package com.yqq.androidmutilprocess;
//aidl接口导包
import com.yqq.androidmutilprocess.MessageReceiver4Test;
interface IGetAllInfo {
            //请求获取所有信息
     void reqAllInfo(in String name, in String content);
     void registerReceiveListener(in MessageReceiver4Test allInfosCallBack);
     void unregisterReceiveListener(in MessageReceiver4Test allInfosCallBack);
}

说明:无论是否在不在同一个包下,aidl接口都需要导包。

(3)aidl文件编写好后编译项目,生成aidl文件对应的java文件。
aidl生成对应的java文件结构如下图3所示:
这里写图片描述
以MessageReceiver4Test为例对生成的Java类进行说明它由一个IInterface接口,Stub内部Binder抽象类以及静态代理类组成如下所示:
IInterface接口

public interface MessageReceiver4Test extends android.os.IInterface

内部类binder接口

public static abstract class Stub extends android.os.Binder implements com.yqq.androidmutilprocess.MessageReceiver4Test

MessageReceiver4Test.java包含静态代理内部类,该类负责ipc

private static class Proxy implements com.yqq.androidmutilprocess.MessageReceiver4Test {
//...
}

根据以上规律可以手写一个binder。

(4)编写服务端组件service并且在Service中实现stub这个binder类,如下所示

private IBinder mGetAllInfo=new IGetAllInfo.Stub(){
    @Override
    public void reqAllInfo(String name, String content) throws RemoteException {
    //....
    }
@Override
    public String reqAllInfo2(String name, String content) throws RemoteException {
    //....
    return null;
}
    @Override
    public void registerReceiveListener(MessageReceiver4Test allInfosCallBack) throws RemoteException {
//.....
    }

    @Override
    public void unregisterReceiveListener(MessageReceiver4Test allInfosCallBack) throws RemoteException {
//.....
    }
};

说明:reqAllInfo方法 registerReceiveListener方法、unregisterReceiveListener方法均与AIDL定义的IGetAllInfo.aidl接口一一对应并且这些方法均运行在服务端或者客户端的进程的Binder线程池中。如下所示:
(5)编写服务端组件service并且在Service中实现stub这个binder类,如下所示

private IBinder mGetAllInfo=new IGetAllInfo.Stub(){
    @Override
    public void reqAllInfo(String name, String content) throws RemoteException {
    //....
    }
@Override
    public String reqAllInfo2(String name, String content) throws RemoteException {
    //....
    return null;
}
    @Override
    public void registerReceiveListener(MessageReceiver4Test allInfosCallBack) throws RemoteException {
//.....
    }

    @Override
    public void unregisterReceiveListener(MessageReceiver4Test allInfosCallBack) throws RemoteException {
//.....
    }
};

说明:reqAllInfo方法 registerReceiveListener方法、unregisterReceiveListener方法均与AIDL定义的IGetAllInfo.aidl接口一一对应并且这些方法均运行在服务端或者客户端的进程的Binder线程池中。如下所示:

// IGetAllInfo.aidl
package com.yqq.androidmutilprocess;
//aidl接口导包
import com.yqq.androidmutilprocess.MessageReceiver4Test;
interface IGetAllInfo {
            //请求获取所有信息
     void reqAllInfo(in String name, in String content);
      String reqAllInfo2(in String name, in String content);
     //接口回调注册
     void registerReceiveListener(in MessageReceiver4Test allInfosCallBack);
     //接口回调解注册
     void unregisterReceiveListener(in MessageReceiver4Test allInfosCallBack);

}

(5)在service组件的onBind方法返回Binder;如下所示:

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

(6)注册service组件并指定运行的进程,service组件将运行在指定的com.yqqtest.yqq进程中,多进程模式开启。

<service android:name=".seivice.CoreService2" android:process="com.yqqtest.yqq" />

服务端的工作基本完成,现在开始客户端的编写。
(7)编写客户端组件Activity,并启动服务端service。
首先编写ServiceConnection的对象conn,通过该对象与绑定方式启动服务,在onServiceConnected方法中通过回调的方式获取远程服务端的Binder,当与远程服务断开连接onServiceDisconnected方法也会回调通常可以在该方法中发起断线重连。如下所示:

ServiceConnection conn=new ServiceConnection(){


    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        //获取服务端返回的binder
        mAllInfo2=IGetAllInfo.Stub.asInterface(iBinder);
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {

    }
};

说明:onServiceConnected,onServiceDisconnected方法是运行在主线程的,不能执行耗时操作。
通过上面的步骤获取到ServiceConnection 的对象 conn后就可以通过它来启动服务了。启动服务代码如下所示:

Intent intent = new Intent(this, CoreService2.class);
bindService(intent, conn, Context.BIND_AUTO_CREATE);
客户端调用这两行代码即可完成远程服务的启动,一般会在Activity组件的onCreate方法中启动该服务。服务启动后即可调用远程服务的方法并传参数了,具体如下所示:
mAllInfo2.reqAllInfo("测试数据","测试数据222");

说明: mAllInfo2是在定义ServiceConnection的对象conn的onServiceConnected方法中获取的远程服务Binder。

运行结果:
主进程日志中看出客户端调用方法运行在主进程(把调用方的进程看成主进程)的主线程中,如下所示:
这里写图片描述
子进程(远程服务的进程当成子进程)日志中可以看出服务端远程方法运行在子进程的binder线程池中,读者可以自行测试每次的binder线程都不一样。如下所示:
这里写图片描述
通过上面的步骤(1)到(7)客户端即可完成绑定服务并调用远程服务端的方法。
原来多进程通信这么简单啊!果真有这么简单就好了,事情还没完,我们一起来搞事情!!!
客户端调用远程服务端的方法会把客户端线程挂起!你说啥?听不清~,哦,挂起啊,那完蛋了,我在主线程里调用远程服务端的方法,会不会ANR啊,好紧张~还有更紧张的,我服务端挂了,客户端都不知道,只会一直等下去。所以这里存在两个问题,客户端调用远程服务端的方法,客户端挂起可能导致ANR的问题;服务端自己挂掉,客户端没做任何的监听的情况下是不知道的,这将导致客户端的死等。所以下面将说明如何解决这两个问题。
问题1客户端调用远程服务端的方法导致挂起等待服务端响应容易导致ANR
服务端延时10s,模拟耗时操作。如下所示:

@Override
public String reqAllInfo2(String name, String content) throws RemoteException {
...
    SystemClock.sleep(10000);
    return "耗时操作测试";
}

当点击按钮触发调用远程服务的方法时,客户端将处于“卡死”状态,怎么点都没有任何反应,然后就弹出万恶的ANR对话框。如下所示:
这里写图片描述
解决办法:客户端子线程调用远程服务端的方法即可。如下所示使用了自定义线程池调用远程服务端的方法:

ThreadPoolUtils.execute(new Runnable() {
    @Override
    public void run() {
        try {
            String rs=   mAllInfo2.reqAllInfo2("测试数据","测试数据222");
        Log.e(TAG,"返回数据"+rs);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
    }
});

说明:通过该方式客户端发起请求调用远程服务的方法将不会把主线程挂起,也就不会出现ANR的情况了。
问题2 服务端是否存活,客户端如何监控
解决办法 给binder设置死亡代理,远程服务意外死亡,客户端能够立刻感知到,并在回调方法里进行重连相关的操作。具体操作如下
首先在客户端里定义死亡代理如下所示:

IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        Log.d(TAG, "binderDied");
        if (mAllInfo2 != null) {
            mAllInfo2.asBinder().unlinkToDeath(this, 0);
            mAllInfo2 = null;
        }
        //重连服务
        setupService2();
    }
};

说明:主要是通过mAllInfo2.asBinder().unlinkToDeath(this, 0)移除之前绑定的Binder代理,其中的unlinkToDeath方法的第二个参数(标志位)设置为0即可。并通过重启服务实现断线重连。

:然后在编写ServiceConnection的对象conn的回调方法onServiceConnected方法中为binder设置上一步定义的死亡代理deathRecipient,这样就完成了客户端对远程服务端的监听工作。如下所示:

ServiceConnection conn=new ServiceConnection(){
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        //获取服务端返回的binder
        mAllInfo2=IGetAllInfo.Stub.asInterface(iBinder);
        //设置Binder死亡监听
            mAllInfo2.asBinder().linkToDeath(deathRecipient, 0);
    }

客户端通过绑定服务,设置死亡代理的方式监听远程服务并获取服务端的binder对象,通过Binder对象调用远程服务的方法完成了进程间通信(IPC)。但是,这种进程间通信,客户端必须等待服务端处理完成,发起请求的线程才能继续执行。如果有类似观察者模式就好了,我只要订阅了你服务端的这个方法,那你什么时候处理好,你就什么时候通知我去处理吧。答案当然是有的。接下来介绍回调方式获取服务端子进程的的处理结果。

4.2回调方式实现服务端子进程数据返回

本节主要说明主进程(客户端所在进程)调用子进程(服务端所在进程)的方法,等待服务端子进程处理完毕通过回调的方式把结果返回给主进程(客户端),其中客户端只需要注册监听接口就能获取服务端子进程的处理结果。
实现回调最重要的一个类:RemoteCallbackList专门用来管理多进程回调的接口。通过该接口给服务端注册回调的AIDL接口。虽然多次跨进程通信传输客户端的同一个对象会在服务端生成不同的对象,但是他们的底层Binder对象是同一个。所以RemoteCallbackList利用上述原理能够实现解注册功能。注意:RemoteCallbackList内部自动实现了线程同步功能,所以在注册和解注册时候不需要做额外的线程同步工作;特别的当客户端进程终止时,它能自动移除所注册的listener。
对RemoteCallbackList的使用以IGetAllInfo.aidl文件为例说明:
1、定义实现IGetAllInfo.stub的类并定义RemoteCallbackList接口。如下所示:

public class AllInfoImpl extends IGetAllInfo.Stub {

    private static  final String TAG="AllInfoImpl";
    //RemoteCallbackList专门用来管理多进程回调接口
    private RemoteCallbackList<MessageReceiver4Test> listenerList = new RemoteCallbackList<>();
...
}

说明:RemoteCallbackList的泛型类型是用于回调的aidl 接口MessageReceiver4Test.aidl的生成类。listenerList是用于注册与解注册的对象。IGetAllInfo.Stub是IGetAllInfo.aidl文件生成的内部类。

2、在AllInfoImpl.java中注册回调接口以及解除回调接口如下所示:

@Override
public void registerReceiveListener(MessageReceiver4Test allInfosCallBack) throws RemoteException {
    listenerList.register(allInfosCallBack);
}
@Override
public void unregisterReceiveListener(MessageReceiver4Test allInfosCallBack) throws RemoteException {
    listenerList.unregister(allInfosCallBack);
}

说明:listenerList是第1步中的定义的AllInfoImpl 类的RemoteCallbackList属性,用于实现注册监听和解除注册。

3、对RemoteCallbackList的遍历必须按如下方式处理,否则会出错。

final int listenerCount = listenerList.beginBroadcast();

for (int i = 0; i < listenerCount; i++) {
 //回调客户端的逻辑 注册接口调用

    }
}
listenerList.finishBroadcast();

说明: listenerList.beginBroadcast()和listenerList.finishBroadcast()必须配对使用,listenerCount是注册的接口数。
介绍完回调的核心类使用后,接下来说明回调的具体实现。

(1)首先定义用于回调的 MessageReceiver4Test.aidl接口,如下所示:

// MessageReceiver4Test.aidl
package com.yqq.androidmutilprocess;
//parcelable 接口导包
import com.yqq.androidmutilprocess.bean.TestInfo;
interface MessageReceiver4Test {
        //获取的信息用于主进程使用
        void onAllInfoReceiver4Test(in TestInfo testInfos,in String rs);
          //发送获取信息后的反馈主进程使用
        void onReqHotInfoCallBack(in String rs);
}

说明:该接口用于回调,也是RemoteCallbackList的泛型类型,也是用于进程间通信的AIDL接口。
(2)做为远程服务端方法的aidl文件增加注册方法

// IGetAllInfo.aidl
package com.yqq.androidmutilprocess;
//aidl接口导包
import com.yqq.androidmutilprocess.MessageReceiver4Test;
interface IGetAllInfo {
            //请求获取所有信息
     void reqAllInfo(in String name, in String content);
      String reqAllInfo2(in String name, in String content);
     //接口回调注册
     void registerReceiveListener(in MessageReceiver4Test allInfosCallBack);
     //接口回调解注册
     void unregisterReceiveListener(in MessageReceiver4Test allInfosCallBack);

}

说明:对于运行在服务端子进程的reqAllInfo、reqAllInfo2方法进行定义并增加用于回调功能的相关注册解注册方法registerReceiveListener、unregisterReceiveListener。
(3)编译工程 服务端Service组件CoreService2类增加回调接口以及相应的逻辑如下所示:

private RemoteCallbackList<MessageReceiver4Test>  mList=new RemoteCallbackList<>();

说明:在Service组件中用于回调的接口。
在Service组件中实现MessageReceiver4Test.aidl用于回调的Binder 接口,注册回调接口、相关的业务逻辑处理方法中对接口回调的RemoteCallbackList进行遍历并调用相关方法,实现回调主进程客户端的相关方法。如下所示:

private IBinder mGetAllInfo=new IGetAllInfo.Stub(){
    @Override
    public void reqAllInfo(String name, String content) throws RemoteException {


        /** * RemoteCallbackList的遍历方式 * beginBroadcast和finishBroadcast一定要配对使用 */
        final int listenerCount = mList.beginBroadcast();
        Log.d(TAG, "listenerCount == " + listenerCount);
        for (int i = 0; i < listenerCount; i++) {
            MessageReceiver4Test messageReceiver = mList.getBroadcastItem(i);
            if (messageReceiver != null) {
                try {
                    messageReceiver.onReqHotInfoCallBack("======测试回调====");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mList.finishBroadcast();

    }

    @Override
    public void registerReceiveListener(MessageReceiver4Test allInfosCallBack) throws RemoteException {

        mList.register(allInfosCallBack);
    }

    @Override
    public void unregisterReceiveListener(MessageReceiver4Test allInfosCallBack) throws RemoteException {

        mList.unregister(allInfosCallBack);
    }


};

说明:在Service组件中实现,其中registerReceiverListener()方法和unregisterReceiverListener()方法进行回调接口的注册和解注册。reqAllInfo方法对实现在子进程处理的具体业务逻辑以及遍历RemoteCallbackList获取客户端主进程注册MessageReceiver4Test的接口并调用它的相关方法完成回调。
到此服务端子进程的相关工作基本完成。接下来进行客户端主进程的编写使得支持回调。
(4)客户端为了实现回调功能需要实现用于回调的MessageReceiver4Test.aidl接口编译生成的MessageReceiver4Test.Stub类,并且推荐以静态内部类的方式实现(避免内存泄漏)。定义完回调的类后需要进行相关的注册,如下所示:

static  class MessageReceiver4Test4Sample extends MessageReceiver4Test.Stub{


    @Override
    public void onAllInfoReceiver4Test(TestInfo testInfos, String rs) throws RemoteException {
    }
    @Override
    public void onReqHotInfoCallBack(String rs) throws RemoteException {
        Log.e(TAG,"onReqHotInfoCallBack ======= "+rs);

    }
}

说明:客户端静态内部类实现的回调接口,服务端遍历后调用的回调方法和该实现类的回调方法一一对应即服务端调用的回调方法是客户端该接口实现所对应的方法。其中onAllInfoReceiver4Test、onReqHotInfoCallBack方法均运行在客户端的Binder线程池中。
客户端定义好用于回调的静态内部类后需要对服务端返回的Binder对象进行回调接口注册。如下所示:”

    ServiceConnection conn=new ServiceConnection(){


    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        //获取服务端返回的binder
        mAllInfo2=IGetAllInfo.Stub.asInterface(iBinder);

        //设置Binder死亡监听
        try {
            mAllInfo2.asBinder().linkToDeath(deathRecipient, 0);
        //注册回调接口
            mAllInfo2.registerReceiveListener(mMessageReceiver4Test4Sample);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {

    }
};

说明:在定义连接远程服务的Serviceconnection对象中获取到远程服务端返回的Binder对象mAllInfo2,并注册回调接

`mAllInfo2.registerReceiveListener(mMessageReceiver4Test4Sample);`

其中mMessageReceiver4Test4Sample是步骤(4)定义的远程回调接口的实现类。
启动服务调用远程服务的方法后的运行结果如下所示:
这里写图片描述
说明:客户端主进程通过回调方式收到了远程服务端的数据“测试回调”。
这里写图片描述
说明:服务端收到了客户端发来的数据 “测试数据”,“测试数据222”

通过RemoteCallbackList类可以实现客户端调用远程服务端的方法,远程服务端子进程通过回调的方式调用客户注册的回调接口方法实现回调。这种方式的进程间通信一般都是Activity组件做客户端进程,Service组件做服务端子进程。试想一下,如果我的项目有多个模块,多个模块都用到进程间通信,那将会有多组这种客户端、服务端模式。由于Service这些都是Android系统的组件,他们消耗的系统资源都是比较大的,这将导致你的应用变得“重量级”,“重量级
”让用户觉得这是一个耗内存耗电的APP,直接卸载!这就尴尬了,如何给应用在使用多进程技术的时候减负将变得非常重要!接下来将介绍多进程技术的减负方法的实现。

4.3 使用Binder池的方式进行IPC

本节通过定义一个单例模式的管理连接(服务连接、连接监听)获取Binder的工具类,通过客户端的不同查询方式(使用了不同的查询参数)来获取不同业务的Binder对象,通过调用这些Binder对象实现调用远程服务的方法。这个工具类可以有多个业务模块的Binder,所以就称为Binder池。接着将介绍Binder池工具类的具体实现。

(1)定义查询Binder池并获取Binder的AIDL接口如下所示:

package com.yqq.androidmutilprocess;

//Binder连接池 IBinderPool4Test.aidl
interface IBinderPool4Test {

    /** * @param binderCode, the unique token of specific Binder<br/> * @return specific Binder who's token is binderCode. */
    IBinder queryBinder(in int binderCode);
}

说明:客户端通过调用该实现类的远程服务的方法queryBinder方法获取对应的binder对象。
(2)生成Binder,实现用于进程间通信的aidl接口用独立的类实现,任何用于进程间通信的aidl接口均可采用如下方式实现,生成基于业务的独立binder。如下所示:

public class AllInfoImpl extends IGetAllInfo.Stub {

    private static  final String TAG="AllInfoImpl";

    //RemoteCallbackList专门用来管理多进程回调接口
    private RemoteCallbackList<MessageReceiver4Test> listenerList = new RemoteCallbackList<>();

    @Override
    public void reqAllInfo(String name, String content) throws RemoteException {
        //子进程发接口并且回调到主进程 运行在子进程的binder线程池中
      //......具体业务逻辑
    }

    @Override
    public void registerReceiveListener(MessageReceiver4Test allInfosCallBack) throws RemoteException {
    //回调接口注册
        listenerList.register(allInfosCallBack);
    }

    @Override
    public void unregisterReceiveListener(MessageReceiver4Test allInfosCallBack) throws RemoteException {
    //回调接口解注册
        listenerList.unregister(allInfosCallBack);
    }

    @Override
    public String reqAllInfo2(String name, String content) throws RemoteException {
    //具体业务逻辑
        return null;

    }
}

说明:将4.1,4.2节介绍的生成Binder的方式独立出来便于扩展,形成独立的业务Binder。该binder类包含了远程服务方法以及回调注册的相关方法。IGetAllInfo.Stub是用于进程间通信的IGetAllInfo.aidl接口编译生成的内部类,实现binder只需要实现这个内部类IGetAllInfo.Stub即可。
(3)实现查询Binder池接口
查询Binder池获取业务相关的binder作为binder池工具类的内部类使用,实现如下所示:

public static class BinderPoolImpl extends IBinderPool4Test.Stub {

    public BinderPoolImpl() {
        super();
    }

    @Override
    public IBinder queryBinder(int binderCode) throws RemoteException {
        IBinder binder = null;
        switch (binderCode) {
        case BINDER_ALL_INFO:
            //所有相关
            binder = new AllInfoImpl();
            break;
        default:
            break;
        }

        return binder;
    }
}

说明:IBinderPool4Test.Stub 是查询binder即步骤(1)定义的IBinderPool4Test.aidl接口编译后生成的内部类。它本身也是一个binder,客户端通过IPC的方式拿到这个远程服务的Binder后就可以通过调用这个远程服务的queryBinder方法获取客户端参数对应的binder对象。增加的binder将在queryBinder方法的分支语句中添加。
(4)远程服务连接以及连接监听启动,具体步骤在前面两节已经做了详细说明。这里将不再说明。

private ServiceConnection mBinderPoolConnection = new ServiceConnection() {

    @Override
    public void onServiceDisconnected(ComponentName name) {
        // ignored.
    }

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

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

private synchronized void connectBinderPoolService() {
    mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
    Intent service = new Intent(mContext, CoreService.class);
    mContext.bindService(service, mBinderPoolConnection,
            Context.BIND_AUTO_CREATE);
    mContext.startService(service);
    try {
        mConnectBinderPoolCountDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

(5)客户端调用。客户端通过获取Binder池对象,调用查询Binder的方法获取远程服务的对应的Binder,通过该binder即可实现调用binder池中对binder的远程方法。通过给远程服务的binder注册回调接口使得客户端能够用观察者模式的方式处理远程服务端返回的数据。如下所示:

BinderPool binderPool = BinderPool.getInsance(MainActivity.this);
IBinder securityBinder = binderPool
        .queryBinder(BinderPool.BINDER_ALL_INFO);
mAllInfo =  AllInfoImpl
        .asInterface(securityBinder);
try {
    mAllInfo.registerReceiveListener(mMessageReceived);
} catch (RemoteException e) {
    e.printStackTrace();
}

说明:通过queryBinder的参数获取了对应的binder 对象mAllInfo,并给mAllInfo注册了回调接口用于回调。
本节通过抽离aidl接口为独立binder实现类,形成binder池,通过查询binder获取相应的binder对象,从而达到调用远程服务端的方法。通过binder的池的方式进一步提高的IPC过程中的可扩展性。至此完成了Binder池的IPC 通信,项目可以就开启一个服务就完成了多模块的进程间通信功能。事情还没完,试想一下你的这远程模块任何一个人都可以使用,然后连上你的模块,使用你的服务,这太危险了,所以需要权限校验。4.4节对IPC权限校验的使用进行说明。

4.4权限校验

通过权限认证,可以拒绝未授权的客户端访问服务,提高安全性。权限认证有两种方式在:第一在Service 组件中的onBind方法进行认证。第二重写aidl接口的实现类的onTransact方法进行认证。
(1)在Service的onBind方法进行权限认证,一般的方式是通过自定义权限进行认证。
首先在清单文件中定义并申明权限如下所示:

<permission  android:name="com.yqq.aidl.permission.REMOTE_SERVICE_PERMISSION" android:protectionLevel="normal" />

<uses-permission android:name="com.yqq.aidl.permission.REMOTE_SERVICE_PERMISSION" />

然后在service的onBind方法中进行权限校验,通过则返回binder给客户端调用。如下所示:

@Override
public IBinder onBind(Intent intent) {
    Log.d(TAG, "onBind");
    //自定义permission方式检查权限
    if (checkCallingOrSelfPermission("com.yqq.aidl.permission.REMOTE_SERVICE_PERMISSION") == PackageManager.PERMISSION_DENIED) {
        Log.e(TAG,"权限校验失败");
        return null;
    }
    return mBinderPool;
}

(2) 重写binder的onTransact方法进行权限认证一般检查包名是否合法如下所示:

@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];
    }
    if (packageName == null || !packageName.startsWith("应用的包名")) {
        Log.e("onTransact", " packageName:" + packageName);
        return false;
    }

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

至此AIDL方式的进程间通信整体流程完成。

总结:本文通过介绍什么是进程间通信、使用进程间通信要解决什么问题、以及怎样使用AIDL方式的进程间通信、优化进程间通信为自己的开发服务。实现了基于Binder池的可回调的AIDL进程间通信。该方式的进程间通信可以使得binder的管理更加易于扩展,使得多进程技术的使用让应用看起来不“重量级”。通过该方式能获取到更大的内存空间,复杂逻辑以及类似长连接推送相关的业务转移到子进程提高应用的稳定性。

具体见https://github.com/1010540672/AndroidMutilProcess

参考:Android艺术探索

上一篇:Android内存泄漏与优化(下) 下一篇:类加载器ClassLoader