宿主和插件间通信

插件化开发,宿主和插件间通信是核心功能,目前我们支持3种通信方式,OSGI、Dispatch和RPC通信,这3种各有优缺点,综合来看以RPC方式最佳,我们强烈推荐使用RPC方式,而不推荐使用OSGI方式。

RPC通信

Bundlerpc支持的功能

1. 支持代码注册和静态注册两种方式
2. 插件间通信需要提前约定接口(interface)但:
    1. 插件与宿主可以同时存在约定的接口
    2. 同时bundlerpc提供一套通用的回调接口,尽量简化约定接口数
    3. bundlerpc提供一个可序列化java对象池ObjectPool
        1. 利用ObjectPool可以将任意对象通过Intent和其他序列化方式传输
        2. 该功能对应的是Dispatch延时回复功能

3. 支持URL方式查询服务

简单示例

先提供一个简单示例来了解bundlerpc服务的调用方法
eg. 插件提供第三方登录和分享服务
    1. 通信协议如何设计
    2. JavaBean设计模式
    3. 如何使用《通用的回调接口》

接口定义

1. 分享JavaBean,通信间使用任何bean都需要先定义这个bean的接口,这是改良了许多问题后遗留的一个不便之处
public interface ShareBean {
    public String getTitle();
    public String getContent();
    public String getIconUrl();
}

2. 三方登录回调
public interface LoginCallback {
    public void onSuccess(String platform,String uid,String uname);
    public void onFail(String status,String errorMsg);
}

3. 通信接口
public interface ShareHander {
    public void share(ShareBean bean, Action1<String> success,Action2<String,String> fail);
    public void threePartyLogin(LoginCallback callback);
}

插件代码实现

public class ShareProcessor implements ShareHander {
    @Override
    public void share(ShareBean bean, Action1<String> success, Action2<String, String> fail) {
        Log.e("插件端",String.format("title: %s content: %s",bean.getTitle(),bean.getContent()));
        if(bean.getIconUrl()== null){
            fail.call("qq","share fail icon url is null");
        }else{
            success.call("weixin");
        }
    }

    @Override
    public void threePartyLogin(LoginCallback callback) {
        Intent intent=new Intent();
        //intent.setClass()....
        intent.putExtra("login_callback",new ObjectPool(callback));
        //startActivity(intent);

        //....以下模拟在Activity中的代码实现
        //Intent intent=Activity.getIntent()
        ObjectPool<LoginCallback> login_callback= (ObjectPool<LoginCallback>) intent.getSerializableExtra("login_callback");
        login_callback.popObject().onSuccess("qq","uid123131","张三");
    }
}

宿主中服务调用

//查询服务
BundleRPCAgent rpcAgent=new BundleRPCAgent(context);
final ShareHander share=rpcAgent.syncCall("apkplug://plug/share",ShareHander.class);
//得到服务后创建ShareBean 对象
ShareBean shareBean=new ShareBean() {
            @Override
            public String getTitle() {
                return "技术博客";
            }

            @Override
            public String getContent() {
                return "发表一篇文章";
            }

            @Override
            public String getIconUrl() {
                return null;
            }
        };
//调用分享服务
share.share(shareBean,
    new Action1<String>() {
        @Override
        public void call(String platform) {
            Toast.makeText(MainActivity.this,platform,Toast.LENGTH_SHORT).show();
        }
    },
    new Action2<String, String>() {
         @Override
         public void call(String platform, String errorMsg) {
            Toast.makeText(MainActivity.this,errorMsg,Toast.LENGTH_SHORT).show();
         }
});
//调用第三方登陆服务
share.threePartyLogin(new LoginCallback() {
    @Override
    public void onSuccess(String platform, String uid, String uname) {
        Toast.makeText(MainActivity.this,String.format("平台: %s 昵称: %s",platform,uname),Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onFail(String status, String errorMsg) {
        Toast.makeText(MainActivity.this,String.format("错误: %s",errorMsg),Toast.LENGTH_SHORT).show();
    }
});

结构

该服务我们目前简称bundlerpc服务,您可以将通信理解为一次RPC调用。
它主要由俩方面构成
1. URI 
   bundlerpc服务路由
   uri是全局唯一的,优先注册的uri有效。
2. RPCProcessor
   服务的实现者

使用

1. 约定通信接口 (见以上示例)

2. ShareProcessor实现 (见以上示例)

3. 注册bundlerpc服务

为了让宿主能够找到实现的ShareProcessor服务,插件需要将ShareProcessor注册到系统中

1.代码方式注册:

BundleRPCAgent rpcAgent=new BundleRPCAgent(context);
rpcAgent.register(context,"apkplug://plug/share",ShareProcessor.class.getName());

2.plugin.xml中注册:

<rpc-processor
    uri=""apkplug://plug/login"
    className="com.apkplug.www.plug.LoginProcessor"
    />
  • 代码注册适合在宿主中注册服务,提供给插件调用的这种情况
  • plugin.xml中注册适合插件,宿主使用服务时不需要提前启动插件

4 调用服务时框架的处理流程:

1. 框架启动时会遍历所有插件plugin.xml注册的bundlerpc服务,并注册到系统中
2. 当查询bundlerpc服务时直接从系统查找
3. 当找到bundlerpc服务时先判断插件是否已启动(如未启动会先启动插件)
4. new一个插件的rpc-processor对象,并调用其对于的成员函数

5. 调用bundlerpc服务

1. 【同步查询】
BundleRPCAgent rpcAgent=new BundleRPCAgent(context);
final ShareHander share=rpcAgent.syncCall("apkplug://plug/share",ShareHander.class);

2. 【异步查询】
    BundleRPCAgent rpcAgent=new BundleRPCAgent(context);
    rpcAgent.asynCall("apkplug://plug/login",Login.class,new RPCCallback<Login>(){

        @Override
        public void reply(URI uri, ShareHander share) {
            //当找到LoginProcessor服务时会回调该接口
            share.share(....);
            share.threePartyLogin(...)
        }

        @Override
        public void Exception(URI uri, Throwable throwable) {
            System.err.println("未找到服务或异常");
        }
    });


 同步查询和异步查询方式各有好处
- 同步查询优点
    - 同步查询是比较符合编程习惯
- 同步查询缺点
    - 查询到服务时会启动插件,这是一个比较耗时的过程,同步查询会阻塞调用线程,因此不适合调用比较大的插件
- 异步查询的优点
    - 启动插件会放在其他线程中,完成以后通过回调返回对应的RPC对象,不会阻塞调用线程

6 通用回调与对象序列化池

bundlerpc内置了一套通用回调接口 Action,Action1,Action2...Action9具体使用见示例

ObjectPool是用于将不可序列化对象转化为可利用Intent进行传输的对象(请记住它只在同一个进程中有效)

eg.
    如上示例中第三方登录接口threePartyLogin需要启动一个Activity,然后在Activity进行登录后再调用LoginCallback回调。
ObjectPool的作用就是将LoginCallback包裹起来可以用Intent传输给Activity

7 传参注意事项

bundlerpc允许宿主与插件间约定的接口共同存在而不会引起类冲突异常是因为# bundlerpc内部使用代理机制 #。 而代理机制的条件是参数只能是interface,如果参数不是interface则需要考虑类冲突问题了。

接口定义的参数类型有要求:
1. 所传参数类型如果是interface则无需注意类冲突问题,只要通信双方接口的方法签名相同即可。
2. 接口参数中如果有自定义的java对象,则需要注意类冲突问题(如osgi.jar一样只能宿主保存而插件只能引用不能打包)
3. 系统类传值
    - String,View,Activity等这些都属于系统类
    - 因为系统类既不属于宿主和不属于插件,因为宿主与插件可以自由传递而不用考虑类冲突问题
    - 但前提是您不会将这些系统类强制转换为您的自定义对象
        - eg
            宿主(LoginView)-->View ---rpc--- 插件(只能当做View来用不能再将View--->LoginView)

8 原理说明

针对(7 传参注意事项)进行基本原理的讲解:

举例:

图1

(图1)

如上图1 宿主与插件约定了login接口,它们有login,logout两个接口,其中传参类型有:

String          系统类  
UserBean        插件宿主各有一套,UserBean类型为interface
LoginCallback   插件宿主各有一套,LoginCallback类型为interface

如上图这种情况,宿主与插件之间的通信无需考虑UserBean和LoginCallback类冲突问题,String为系统类同样无需考虑

再看下图: (图2) 图2与图1唯一不同的是UserBean类型由interface改成了class:

String          系统类  
UserBean        插件宿主各有一套,UserBean类型为class
LoginCallback   插件宿主各有一套,LoginCallback类型为interface

如图2这种情况,宿主与插件之间的通信则需要考虑UserBean类冲突问题