搭建IPC通信框架(RPC)

Android进程通讯方式有很多,如Socket、ContentProvider、共享文件(这种方式的缺点是不支持并发写,同时需要手动操作IO)、AIDL、Messenger(底层实现也是AIDL)等。关于AIDL和Messenger的使用详见Android知识点总结(四)进程间通信。
Android中IPC方式的使用不是有诸多限制,就是使用起来比较麻烦,所以搭建一套简单易用的IPC框架是很有必要的。
RPC:从客户端上通过参数传递的方式调用一个远程函数并得到返回的结果,隐藏底层的通讯细节。在使用形式上就像调用本地函数一样去直接调用远程函数

场景

在服务进程中开启定位服务(demo里对应GpsService),在App主进程或者其他APP中获得定位结果(demo对应MainAcivity)。

框架使用预览

搭建IPC通信框架(RPC)_第1张图片

1. 编写业务接口及接口实现
ILocationManager

    @ServiceId("LocationManager")
    public interface ILocationManager {
        Location getLocation();
    }

LocationManager

   @ServiceId("LocationManager")
    public class LocationManager {
        private static final LocationManager OUR_INSTANCE = new LocationManager();
    
        public static LocationManager getDefault() {
            return OUR_INSTANCE;
        }
    
        private LocationManager(){}
    
        private Location location;
    
        public Location getLocation() {
            return location;
        }
    
        public void setLocation(Location location) {
            this.location = location;
        }
    }

二者通过自定义注解的方式相关联,RPC框架主要就是通过反射业务接口的实现类LocationManager来完成通信。

2. 服务进程使用方法
将业务接口实现类注册到RPC框架即可,RPC框架内部通过反射的方式拿到LocationManager的相关信息,通过AILD将数据传递到主进程。

/**
 * 服务进程
 */
public class GpsService extends Service {
    public GpsService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //模拟定位
        LocationManager.getDefault().setLocation(new Location("岳麓区天之道", 1.1d, 2.2d));

        //将LocationManager注册到框架
        IPC.regist(LocationManager.class);
    }
}

3. 主进程使用方法

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
		//开启服务进程
        startService(new Intent(this, GpsService.class));
		//通过RPC框架连接服务 IPCService服务是框架内部的公共服务,IPCService0是内部服务的子类
		//和服务进程设置为同一进程
        IPC.connect(this, IPCService.IPCService0.class);

    }
    public void showLocation(View view) {
        //主进程利用框架获取业务实现类的代理对象,然后用代理对象直接调用接口方法
        //使用形式上就像调用本地函数一样去直接调用远程函数
       ILocationManager location = IPC.getInstanceWithName(IPCService.IPCService0.class, ILocationManager.class, "getDefault");
       Toast.makeText(this, "当前位置:" + location.getLocation(), Toast.LENGTH_LONG).show();
    }
}

以上就是整个框架的应用示例

RPC框架实现原理

接下来就从零开始手写整个RPC框架

搭建IPC通信框架(RPC)_第2张图片
1、业务注册

public class IPC {
    //服务端
    //==================================================================
    //注册
    public static void regist(Class service) {
        Registry.getInstance().regist(service);
    }
    ...
}

IPC.java只是对外的一个窗口,真正的注册实现在Registry


public class Registry {
    private static volatile Registry instance;
    /**
     * 服务表 记录业务接口实现类的Class对象 对应本例即LocationManager
     */
    private ConcurrentHashMap> mServices = new ConcurrentHashMap<>();
    /**
     * 方法表 记录业务接口实现类的相关方法
     */
    private ConcurrentHashMap, Map> mMethods = new ConcurrentHashMap<>();
    /**
     * 业务实现类的实例,要反射执行非静态方法,是需要一个实例对象的,所以把实例保存起来
     */
    private Map mObjects = new HashMap<>();

    private Registry(){}

    public static Registry getInstance(){
        if (instance == null){
            synchronized (Registry.class){
                if (instance == null){
                    instance = new Registry();
                }
            }
        }
        return instance;
    }

    /**
     * 做两张表
     * 1、服务表 Class的标记 :Class<?>
     * 2、方法表 Class : ["getLocation":Method,"setLocation":Method]
     * @param clazz
     */
    public void regist(Class clazz) {
        // 注册服务表
        //使用注解,获取注解id,并作为key保存
        ServiceId annotation = clazz.getAnnotation(ServiceId.class);
        mServices.putIfAbsent(annotation.value(), clazz);

        // 注册服务对应的方法表 即将业务实现类的方法保存起来
        mMethods.putIfAbsent(clazz, new HashMap());
        Map methods = mMethods.get(clazz);
        //class中所有的方法
        for (Method method : clazz.getMethods()) {
            StringBuilder sb = new StringBuilder();
            sb.append(method.getName());
            sb.append("(");
            Class[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length!=0){
                sb.append(parameterTypes[0].getName());
            }
            for (int i = 1; i < parameterTypes.length; i++) {
                sb.append(",").append(parameterTypes[i].getName());
            }
            sb.append(")");
            methods.put(sb.toString(), method);
        }
    }

    public  Class getService(String serviceId){
        Class aClass = mServices.get(serviceId);
        return aClass;
    }

    public Method getMethod(Class clazz, String methodName, Parameters[] parameters) {
        Map methods = mMethods.get(clazz);
        StringBuilder sb = new StringBuilder();
        sb.append(methodName);
        sb.append("(");
        if (parameters.length!=0){
            sb.append(parameters[0].getType());
        }
        for (int i = 1; i < parameters.length; i++) {
            sb.append(",").append(parameters[i].getType());
        }
        sb.append(")");
        Method method = methods.get(sb.toString());
        return method;
    }
    public void putObject(String serviceId, Object object){
        mObjects.put(serviceId, object);
    }
    public Object getObject(String serviceId){
        return mObjects.get(serviceId);
    }
}

@Retention(RetentionPolicy.RUNTIME) //给反射用
@Target(ElementType.TYPE)
public @interface ServiceId {
    String value();
}
  • 利用自定义注解 @ServiceId 对业务接口和实现类,都形成约束, 利用注解@ServiceId的value和服务实现类的Class对象进行注册服务表和方法表
  • 如果业务变动,只需修改业务实现或增加新的业务接口

2、自定义通信协议
因为框架本身是不能随意变动的,而不同的进程通信业务数据又是各类各样的,所以为了统一处理这些通信业务数据,制定一套统一的规则。就像http一样,设置一套简单的请求(Request)和响应(Response)。即请求的时候将请求参数都封装成一个Request,响应的时候将返回结果封装成一个Response。因为框架本质上还是一个Binder通信,所以传输实体要求实现Parcelable接口。

public class Request implements Parcelable {
    //获得单例对象
    public static final int GET_INSTANCE = 0;
    //执行方法
    public static final int GET_METHOD = 1;

    //请求类型
    private int type;
    //请求哪个服务 (ServiceId)
    private String serviceId;
    //请求的方法名
    private String methodName;
    //执行方法的参数
    private Parameters[] parameters;

    public Request(int type, String serviceId, String methodName, Parameters[] parameters) {
        this.type = type;
        this.serviceId = serviceId;
        this.methodName = methodName;
        this.parameters = parameters;
    }

    protected Request(Parcel in) {
        type = in.readInt();
        serviceId = in.readString();
        methodName = in.readString();
        parameters = in.createTypedArray(Parameters.CREATOR);
    }
    ...
}
public class Response implements Parcelable {

    //执行远程方法的返回 json 结果
    private String source;
    //是否成功执行远程方法
    private boolean isSuccess;

    protected Response(Parcel in) {
        source = in.readString();
        isSuccess = in.readByte() != 0;
    }

    public Response(String source, boolean isSuccess) {
        this.source = source;
        this.isSuccess = isSuccess;
    }
        ...
}

执行方法的参数Parameters

public class Parameters implements Parcelable {
    //参数类型 class
    private String type;
    //参数值 json序列化后的字符串
    private String value;
        ...
}

有了注册方法,有了参数,接下来就要考虑怎么实现,将这些信息进行跨进程传输。

3、binder连接封装
框架本质上还是一个Binder通信,只不过我们对其进行了封装,只需在框架内搭建一套公用的AIDL通信,外部使用就不用再去编写繁琐的AILD了。接下来就来展示如果搭建:

// IIPCService.aidl
package com.scy.component.mylibrary;

// Declare any non-default types here with import statements
import com.scy.component.mylibrary.model.Response;
import com.scy.component.mylibrary.model.Request;
interface IIPCService {
        Response send(in Request request);
}

public abstract class IPCService extends Service {
    static Gson gson = new Gson();
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new IIPCService.Stub() {
            @Override
            public Response send(Request request) throws RemoteException {
                //执行客户端的请求
                String serviceId = request.getServiceId();
                //从服务表中获得 对应的Class对象。
                //具体类型  Class
                Class instanceClass = Registry.getInstance().getService(serviceId);
                Parameters[] parameters = request.getParameters();
                Object[] objects = restoreParameters(parameters);
                //从方法表中获得 对应的Method对象
                String methodName = request.getMethodName();
                Method method = Registry.getInstance().getMethod(instanceClass, methodName, (Parameters[]) objects);
                Response response;
                //客户端的请求类型
                switch (request.getType()){
                    //单例方法
                    case Request.GET_INSTANCE:
                        try {
                            Object instance = method.invoke(null, objects);
                            // 单例类的serviceId与 单例对象 保存
                            Registry.getInstance().putObject(serviceId, instance);
                            response = new Response(null, true);
                        } catch (Exception e) {
                            e.printStackTrace();
                            response = new Response(null, false);
                        }
                        break;
                    //普通方法
                    case Request.GET_METHOD:
                        try {
                            Object object = Registry.getInstance().getObject(serviceId);
                            // getLocation 返回Location
                            Object returnObject = method.invoke(object, objects);
                            response = new Response(gson.toJson(returnObject), true);
                        } catch (Exception e) {
                            e.printStackTrace();
                            response = new Response(null, false);
                        }
                        break;
                    default:
                        response = new Response(null, false);
                        break;

                }
                return response;
            }
        };
    }

    private Object[] restoreParameters(Parameters[] parameters) {
        Arrays.stream(parameters).forEach((p -> {
            try {
                gson.fromJson(p.getValue(), Class.forName(p.getType()));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }));
        return parameters;
    }

    public static class IPCService0 extends IPCService{}
    public static class IPCService1 extends IPCService{}
}

客户端调用send方法,告诉框架,我要执行哪个类的哪个方法,我传什么参数
IPC框架层反射执行服务端业务类的指定方法,并且视情况返回不同的回应

外部使用IPCService时,主要是使用其子类。设计多个内部子类的意义是,考虑到服务端存在多个 业务接口的存在,让每一个业务接口的实现类 都由一个专门的IpcService服务区负责通信。

public class Channel {

    /**
     * 单例
     */
    private static volatile Channel instance;

    //已经绑定过的
    private ConcurrentHashMap, Boolean> mBinds =
            new ConcurrentHashMap<>();
    //正在绑定的
    private ConcurrentHashMap, Boolean> mBinding =
            new ConcurrentHashMap<>();
    //已经绑定的服务对应的ServiceConnect
    private final ConcurrentHashMap, IPCServiceConection> mServiceConnections =
            new ConcurrentHashMap<>();
    private final ConcurrentHashMap, IIPCService> mBinders =
            new ConcurrentHashMap<>();

    private Channel() {
    }

    public static Channel getInstance() {
        if (null == instance) {
            synchronized (Channel.class) {
                if (null == instance) {
                    instance = new Channel();
                }
            }
        }
        return instance;
    }

    private Gson gson = new Gson();
    public void bind(Context context, String packageName, Class service){
        IPCServiceConection ipcServiceConection;
        //是否已经绑定
        Boolean isBound = mBinds.get(service);
        if (isBound != null && isBound){
            return;
        }
        //是否正在绑定
        Boolean isBinding = mBinding.get(service);
        if (isBinding != null && isBinding) {
            return;
        }
        //要绑定了
        mBinding.put(service, true);
        ipcServiceConection = new IPCServiceConection(service);
        mServiceConnections.put(service, ipcServiceConection);
        Intent intent;
        if (TextUtils.isEmpty(packageName)){
            intent = new Intent(context, service);
        }else {
            intent = new Intent();
            intent.setClassName(packageName, service.getName());
        }
        context.bindService(intent, ipcServiceConection, Context.BIND_AUTO_CREATE);
        Log.e("--==", "bind");
    }

    public void unbind(Context context, Class service){
        Boolean bound = mBinds.get(service);
        if (bound != null && bound){
            IPCServiceConection conection = mServiceConnections.get(service);
            if (conection != null){
                context.unbindService(conection);
            }
            mBinds.put(service, false);
        }
    }

    private class  IPCServiceConection implements ServiceConnection{
        private final Class mService;
        public IPCServiceConection(Class mService) {
            this.mService = mService;
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IIPCService iipcService = IIPCService.Stub.asInterface(service);
            mBinders.put(mService, iipcService);
            mBinds.put(mService, true);
            mBinding.remove(mService);
            Log.e("--==", "connected");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBinders.remove(mService);
            mBinds.remove(mService);
            Log.e("--==", "Disconnected");
        }
    }

    public Response send(int type,Class service, Class classType,
                         String methodName, Object[] parameters) {
        // ipcService: 绑定的服务中onbind返回的binder对象
        IIPCService iipcService = mBinders.get(service);
        if (iipcService == null){
            //没有绑定服务
            return new Response(null, false);
        }
        //发送请求给服务器
        ServiceId annotation = classType.getAnnotation(ServiceId.class);
        String serviceId = annotation.value();
        Request request = new Request(type, serviceId, methodName, makeParameters(parameters));
        try {
            return iipcService.send(request);
        } catch (RemoteException e) {
            return new Response(null, false);
        }
    }

//    //将参数制作为Parameters
    private Parameters[] makeParameters(Object[] parameters) {
        Parameters[] p;
        if (null != parameters) {
            p = new Parameters[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                Object object = parameters[i];
                p[i] = new Parameters(object.getClass().getName(), gson.toJson(object));
            }
        } else {
            p = new Parameters[0];
        }
        return p;
    }
}

Channels是框架内真正实现bindService、unbindService的工具类,这里写上一次,客户端就不用再重复写bindService、ServiceConnection等。客户端只需通过框架窗口类IPC直接调用IPC.connect(this, IPCService.IPCService0.class);即可
IPC实现

//客户端
    //==================================================================

    /**
     * 连接本APP其他进程 服务
     *
     * @param context
     * @param service
     */
    public static void connect(Context context, Class service){
        connect(context, null, service);
    }
    /**
     * 连接其他APK进程服务
     *
     * @param context
     * @param packageName apk包名
     * @param service
     */
    public static void connect(Context context, String packageName,
                               Class service) {
        Channel.getInstance().bind(context.getApplicationContext(), packageName, service);
    }

    public static void disConnect(Context context, Class service) {
        Channel.getInstance().unbind(context.getApplicationContext(), service);
    }

客户端完成bindService后,就需要调用send方法发送相关信息
客户端

    public void showLocation(View view) {
        //代理对象
        ILocationManager location = IPC.getInstanceWithName(IPCService.IPCService0.class, ILocationManager.class, "getDefault");
        
        Toast.makeText(this, "当前位置:" + location.getLocation(), Toast.LENGTH_LONG).show();
    }

IPC


    public static  T getInstanceWithName(Class service, Class instanceClass, String methodName, Object...
            parameters) {
        if (!instanceClass.isInterface()){
            throw new IllegalArgumentException("必须以接口进行通信。");
        }
        //服务器响应
        Response response = Channel.getInstance().send(Request.GET_INSTANCE, service, instanceClass, methodName, parameters);
        // response: 成功
        if (response.isSuccess()){
            //返回一个假的对象 动态代理
            return getProxy(instanceClass, service);
        }
        return null;
    }

最终走Channel的send方法,而Channel的send方法最终在IPCService的onBind方法里得以执行,最后返回一个Response响应。如果相应成功,getInstanceWithName方法内部就会通过getProxy(Class instanceClass, Class service)方法,用动态代理的手段拿到接口代理对象。


    private static  T getProxy(Class instanceClass, Class service) {
        ClassLoader classLoader = instanceClass.getClassLoader();
        return (T) Proxy.newProxyInstance(classLoader, new Class[]{instanceClass}, new IPCInvocationHandler(instanceClass, service));
    }

    private static class IPCInvocationHandler implements InvocationHandler {
        private final Class instanceClass;
        private final Class service;
        static Gson gson = new Gson();

        public  IPCInvocationHandler(Class instanceClass, Class service) {
            this.instanceClass = instanceClass;
            this.service = service;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            /**
             * 请求服务端执行对应的方法。
             */
            Response response = Channel.getInstance().send(Request.GET_METHOD, service, instanceClass, method.getName(), args);
            if (response.isSuccess()){
                Class returnType = method.getReturnType();
                //不是返回null
                if (returnType != Void.class && returnType != void.class) {
                    //获取Location的json字符
                    String source = response.getSource();
                    //反序列化 回 Location
                    return gson.fromJson(source, returnType);
                }
            }
            return null;
        }
    }

客户端拿到接口实现类代理对象,就可以直接调用接口方法获取数据了,如:

搭建IPC通信框架(RPC)_第3张图片
当然location.getLocation的真正实现是通过上面动态代理的方式,在IPCInvocationHandler里实现。Channel
如果服务端和客户端是两个app(test_client、test_service),具体操作见Demo。

Demo地址

RPC框架代码地址:https://github.com/chaoyangsun/IPCFrame

你可能感兴趣的:(Android)