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