Dilutions数据通信解耦框架
Dilutions
Dilutions是一个专门用于模块间数据协议通信的解耦协议框架,提供高性能数据分析和通信功能,解耦多项目多模块间的数据通信,简化代码逻辑成本。
通过一段URI字符串就能实现所有操作。
这个框架已经在 美柚 稳定 ,2016 年就开始使用,美柚总用户突破1亿,日活接近千万,Dilutions框架经过亿万用户的测试,代码的稳定性是可以放心的。有需求或者bug可以提issues,我会尽快回复。
跨模块UI跳转
通过Dilutions实现跨模块间的UI跳转。
基本UI跳转
假设此时需要从模块1中的界面A跳转到模块2中的界面B,并且携带一些数据,由于模块1和模块2之间互不依赖,想要跨模块打开某个界面是比较困难的。
Dilutions提供了跨模块间的UI跳转能力,通过定义一串共同的URI协议即可实现界面A到界面B的跳转。
假设我们约定这串跳转协议URI为:
String uri = "dilutions:///ui/atob"
那么,这时只要在界面B中加上注解:
@ActivityProtocol("/ui/atob")
public class ActivityB extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
} 
界面A调用如下语句即可实现UI跳转:
Dilutions.create().formatProtocolService(uri);
携带数据
如果这时候界面B需要接收一些参数,那么界面A如何传递呢?
我们假设界面B需要的参数如下:
int user_id; String user_name;
那么界面B只需要将界面代码改为:
@ActivityProtocol("/ui/atob")
public class ActivityB extends AppCompatActivity {
    @ActivityProtocolExtra("user_id")
    int user_id;
    @ActivityProtocolExtra("user_name")
    String user_name;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Dilutions.create().register(this);//注意,一定要注册dilutions
        Log.i(user_id + user_name);//使用传递过来的数据
    }
} 
界面A通过调用如下代码即可传递参数并且实现跳转:
    String uri = "dilutions:///ui/atob"
    HashMap<String,Object> map = new HashMap<>();
    map.put("user_id", 222);
    map.put("user_name", "二红");
    Dilutions.create().formatProtocolService(uri, map, null); 
携带对象数据
有的时候,我们传递的不一定是基础类型,而是对象,Dilutions也可以帮你完成传递
@ActivityProtocol("/ui/atob")
public class ActivityB extends AppCompatActivity {
    @ActivityProtocolExtra("test_object")
    TestObject test_object;
} 
界面A通过调用如下代码即可传递参数并且实现跳转:
    String uri = "dilutions:///ui/atob"
    HashMap<String,Object> map = new HashMap<>();
    map.put("test_object", new TestObject);
    Dilutions.create().formatProtocolService(uri, map, null); 
而界面B中可以直接拿到那个对象进行使用,但是需要注意的是传递的对象必须序列化。
转场动画
有时候需要动画转场来跳转界面,Dilutions也支持Activity的动画转场
只需要在对应的Activity加上注解即可
@ActivityProtocol({"/test","/test2"})
@CustomAnimation(enter = R.anim.enter, exit = R.anim.exit)
public class MainActivity extends AppCompatActivity {
} 
CustomAnimation注解中的enter表示进入Activity的动画,exit为退出
跨模块方法调用
我们上面学会了跨模块的UI跳转,下面将学到如何通过Dilutions进行跨模块的方法调用。
假设模块1想调用模块2的一个方法,模块1和模块2不互相依赖,你该怎么做呢?
再比如现在有方法A和方法B,想通过服务器决定来调用哪一个,怎么做比较好呢?
Dilutions帮你解决了这个问题,使用方式和UI跳转差不多,你只需要URI协议字符串即可。
基础的跨模块调用方法
假设模块2中存在一个原生方法,这个方法之前是被模块2内的其他类直接使用的,现在需要支持跨模块特性。
首先模块2的方法需要加上注解:
@MethodProtocol("/method")
public void method(){
     Log.d("test","method had being called.");
} 
这个方法可以在任何一个类中,任意地方,只需要注解。
接下来,你应该从UI跳转方法中学到了如何识别协议URI,没错,这个方法中的"/method"就是协议,因此协议应该长这样:
String uri = "dilutions:///method";
那么调用方依然是
Dilutions.create().formatProtocolService(uri);
这样就完成了跨模块的方法调用。
所以你可以看到入口是不变的,一个方法可以通过URI协议的不同,正确的调用实现方。
携带参数
那么这时候你肯定想到了,我的方法肯定不可能都是无参啊,Dilutions可以支持带参调用吗?
答案是肯定的,仍然是通过注解。
我们假设有参方法method2如下:
public void method2(String username, int userid, boolean open){
     Log.d("test","method had being called." + username + userid);
} 
那么需要添加注解,改造成如下代码:
@MethodProtocol("/method2")
public void method2(@MethodParam("username")String username, @MethodParam("userid")int userid, @MethodParam("open")boolean open){
     Log.d("test","method had being called." + username + userid);
} 
调用方只需要跟带数据跳转UI的操作方式一致就可以:
    String uri = "dilutions:///method2"
    HashMap<String,Object> map = new HashMap<>();
    map.put("user_id", 222);
    map.put("user_name", "二红");
    map.put("open", true);
    Dilutions.create().formatProtocolService(uri, map, null); 
如果传递的数据不存在,比如没有传递open这个数据,那么对应的实现方法仍然会被执行,但是对应的入参会以默认值传入。
携带对象数据
跟跳转UI一样,Dilutions也可以携带对象数据跨模块调用方法。
@MethodProtocol("/method2")
public void method2(@MethodParam("username")TestObject username){
     Log.d("test","method had being called.");
} 
方法实现方相关
Dilutions对实现方的方法会进行自动映射,实现方不一定需要全部将入参标注参数,并且对注解顺序没有任何要求,比如:
@MethodProtocol("/method2")
public void method2(@MethodParam("username")String username, int userid, @MethodParam("open")boolean open){
     Log.d("test","method had being called." + username + userid);
} 
方法返回值
那么你肯定还会提到,有的方法还有返回值,那么怎么办呢?
Dilutions同样解决了这个问题。
将方法method2改为:
@MethodProtocol("/method2")
public int method2(@MethodParam("username")String username, int userid, @MethodParam("open")boolean open){
     Log.d("test","method had being called." + username + userid);
     return 0;
} 
调用方改为
Dilutions.create().formatProtocolServiceWithCallback(uri,new DilutionsCallBack(){
    public void onDilutions(DilutionsData data){
        Object result = data.getResult();
        //result即为方法调用返回值结果
    }
}); 
URI协议
在Dilutions中,我们知道所有的跨模块操作都是基于协议,因此,我们通过一串URI字符串即可实现跨模块的数据交互。
这个URI协议可以是从服务器下发的,也可以是客户端直接写好的,通过这个方式可以实现动态的方法调用和界面跳转。
在Dilutions中,协议的格式如下:
dilutions:///circles/group?params=e2dyb3VwSUQ6Myx0ZXN0OiLmnpflro/lvJgifQ==
其中dilutions:// 是协议头,这个是可以通过Dilutions自定义的,你可以给不同的app、业务设置不同的协议头,用来区分。
其中/circles/group这种,你已经知道了,他就是主要协议path,只有实现方实现了才会响应。
后面的params其实是固定样式,params=后面跟的是协议的数据参数,这个数据参数是base64过的json。
所以如果要实现动态跳转或者动态方法调用,服务器下发协议字符串需要采用这个逻辑构造。
在客户端中,Dilutions提供了一系列的工具类来帮助使用者生成,正常来说,我们只需要使用Dilutions.formatService相关方法即可,但是需求总是会变的,所以接下来会展示如何生成一个Dilutions的URI协议。
客户端生成URI协议
String uri = DilutionsUriBuilder.buildUri("dilutions://", "/testmap","{ \"path\":\"bi_information\", \"tt\" : {\"action\":1,\"floor\":2}}");
Dilutions.create().formatProtocolService(uri); 
以上代码片段是生成生成一个URI协议,然后再执行它。
你可以通过DilutionsUriBuilder这个类进行协议的生成和解析操作。
拦截协议
有的时候你可能需要拦截部分协议,在它们执行前进行额外的操作,那么拦截器是你的不二之选。
Dilutions.create().formatProtocolServiceWithInterceptor(uri, new DilutionsInterceptor(){
    public boolean interceptor(DilutionsData data){
       return false;
    }
}) 
在拦截器的DilutionsData回调对象中存在很多获得协议信息的方法,比如执行的intent等,你可以对其进行修改,你甚至可以在里面重新定向到另一个协议实现,通过更改boolean返回值来决定(true=拦截,不继续执行原本的协议,false=继续执行)。
添加协议头
你可以通过动态添加协议头来决定你当前应用需要支持哪些协议。
Dilutions.create().getAppMap().add("dilutions2"); 
代理跳转UI
上面已经介绍过使用URI协议进行跳转UI,但实际上在Dilutions中,你还可以通过动态代理的方式进行跳转。
首先需要编写代理接口
public interface DebugService {
    @ProtocolPath("/test")
    void renderPage(@ExtraParam("test") String test, @ExtraParam("id") Object obj);
} 
其中ProtocolPath内的是协议的path,方法名随意,ExtraParam注解对应的是参数名,后面跟类型。
编写完代理接口后,通过调用代理接口即可实现协议跳转执行。
Dilutions.create().formatProtocolService(DebugService.class).renderPage(参数);
PS:当前版本动态代理只能跳转UI,后续会实现跳转方法实现。
获取协议数据
注解获取
当你发起了一个协议打开一个UI的时候,你需要获得传递过来的协议数据,通过Dilutions注解可以在Activity或者Fragment中获得协议数据。
在Activity中:
    /**
     * 读取test参数
     */
    @ActivityProtocolExtra("test")
    TestObj st;
    /**
     * 读取query参数
     */
    @ActivityProtocolExtra("query")
    int query;
    @ActivityProtocolExtra("groupID")
    int groupID; 
在Fragment中:
    /**
     * 读取test参数
     */
    @FragmentArg("test")
    TestObj st;
    /**
     * 读取query参数
     */
    @FragmentArg("query")
    int query;
    @FragmentArg("groupID")
    int groupID; 
最后需要在对应的Activity或者Fragment的onCreate()方法中注册
Dilutions.create().register(this);
注册完毕后,这些数据参数就可以使用了。
PS:Dilutions直接任意数据对象传递。
原始获取
你可以不通过注解方式获取,你可以通过getIntent来获取注解,比如上述的数据通过下面的方式也可以获取到:
int query = getIntent().getIntExtra("query",0); 
额外参数获取
有时候你可能还想获得传递过来的完整协议,或者其他信息来处理一些业务需求,Dilutions定义了一些参数名,你可以通过注解方式也可以通过原始方式获得对应的数据。
class DilutionsInstrument{
    //协议目标class类
    public static final String URI_CALL_CLASS = "uri-call-clazz";
    //协议的path
    public static final String URI_CALL_PATH = "uri-call-path";
    //协议的参数
    public static final String URI_CALL_PARAM = "uri-call-param";
    //完整协议
    public static final String URI_CALL_ALL = "uri-call-all";
    //如何跳转的,是代理还是协议
    public static final String URI_FROM = "uri-from";
} 
初始化
Dilutions需要初始化才能够被使用,只需要一次即可。
Dilutions.init(Context);
gradle
当前最新版本1.0.8
在主工程最外层配置gradle :classpath 'linhonghong.lib:dilutions-compiler:1.0.6'
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'
        //添加这个
        classpath 'linhonghong.lib:dilutions-compiler:1.0.8'
    }
} 
在需要dilutions的地方添加:
compile 'linhonghong.lib:dilutions:1.0.8'
Dilutions依赖阿里巴巴fastjson的json解析,因此需要在你的工程中依赖fastjson
compile 'com.alibaba:fastjson:1.1.68.android'
主工程apply插件
apply plugin: 'dilutions'
Dilutions在Gradle2.x以及以上版本测试通过。
混淆
-keep public class com.linhonghong.dilutions.inject.support.DilutionsInjectUIMetas -keep public class com.linhonghong.dilutions.inject.support.DilutionsInjectMeta
