Dilutions

Project Url: HomHomLin/Dilutions
Introduction: 通过注解生成协议映射执行跨模块的界面跳转和方法调用,解耦的协议框架
More: Author   ReportBugs   Doc
Tags:

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

Developed By

License

Copyright 2016 LinHongHong

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools