Android收藏集android应用框架

组件化方案:JIMU之UI路由(一)

2018-04-07  本文已影响511人  leobert

背景介绍:

张明庆老哥之前在得到工作时,开源了DDAndroidComponent项目,演示组件化思路及实现,本人当时作为协作者参与了一部分开发,现项目迁移到JIMU ,接下来将在新项目仓库进行维护。

张明庆老哥的几篇文章:
1、Android彻底组件化方案实践
2、Android彻底组件化demo发布
3、Android彻底组件化-代码和资源隔离
4、Android彻底组件化—UI跳转升级改造
5、Android彻底组件化—如何使用Arouter

为什么要有这一篇

首先,不在此处展开组件化核心思想“隔离与发现”,因为有了隔离的要求(其他模块的Activity在编译期不可见,无法创建显示Intent),和JIMU本身自有一套路由(我们称之为“路由”,“IOC容器”,“DI组件”都是可以的,本质就是用来注册和寻找实例的)的实际基础存在,这决定了在组件化中跨模块跳转页面,需要使用以下的一种技术:

其次,为什么Demo中没有直接使用ARouter等成型方案?ARouter是个很优秀的项目,但是对于JIMU而言,他有点over-weight而且功能重复,我前面简单提到:“JIMU本身自有一套路由”,而且在JIMU中自动注册的功能是注册到该路由的,再加入ARouter纯粹多余选择多了往往是麻烦事),以及在项目中使用ARouter的,可以参考链接中的第五篇,使用ARouter进行路由跳转,甚至是基于ARouter实现组件化,以达到项目的简洁

结合讨论群中一些朋友们提出的问题,以及github上的典型issue,本文进行一些扼要的总结,便于大家排错。

为了方便,我们下文将用UIRouter来代指JIMU中提供的UI路由。
以下是outline

UIRouter 1.0.0提供哪些东西

可供使用的注解:(代码取自新版本,和v1.0.0存在一定小差异)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface RouteNode {
    /**
     * path of one route
     */
    String path();
    /**
     * The priority of route.
     *
     * we inspect the path and throw exception when duplicated
     * paths were find, thus, it's useless and impossible to use priority
     */
    @Deprecated
    int priority() default -1;

    /**
     * description of the activity, user for gen route table
     */
    String desc() default "";
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Autowired {

    /**
     * @return param's name or service name.
     */
    String name() default "";

    /**
     * <em>primitive java type check will be ignore</em>
     * check the result of DI, if inject failed, the value of
     * the field will be null, if required, output log
     *
     * @return true for required,false otherwise
     */
    boolean required() default false;

    /**
     * throw exception when the required field is null after inject.
     * <p>
     * It can help developer find most data delivering bugs when developing.
     * but not suggest to open this function after release.
     * <p>
     * I suggest to define a Constant maintained manually
     * <p>
     * only activated when required = true and throwOnNull = true.
     *
     * @return true if throwing exception when null is required, false otherwise
     */
    boolean throwOnNull() default false;

    /**
     * @return field description
     */
    String desc() default "none desc.";
}

其他非暴露供使用的内容不做介绍。

UIRouter 1.0.0包含哪些已知问题

如何集成 UIRouter

首先:您应该以及集成了JIMU方案,使用了gradle plugin 并且集成了基础库:

以下演示代码均建立在java项目、gradle plugin版本<3.0 基础上

  1. 集成注解依赖库:
compile 'com.luojilab.ddcomponent:router-annotation:1.0.0'

注意:componentLib包中已经包含了注解依赖库,所以不需要再声明依赖库。

  1. 集成注解处理器:
annotationProcessor 'com.luojilab.ddcomponent:router-anno-compiler:1.0.0'

注意:在Module的build.gradle中声明,不要在底层库中声明;不声明无法使用UIRouter功能。

并指定Module的环境参数:

defaultConfig {
    javaCompileOptions {
        annotationProcessorOptions {
             arguments = [host: "share"]
        }
    }
}

注意:这里的环境参数会影响使用时编写的url(or URI),以此处代码为例,我们的url形式是这样的:

[schema]//[host][path]?[queryString]

举个例子,该Module中存在一个RouteNode:path为“/index”
那么对应的url为:

【任意协议】//share/index

如果未指定,那么将使用默认值“default”,即:

【任意协议】//default/index

3.将生成的映射注册到UIRouter
抱歉在之前的内容中,遗漏了这一块,给使用者带来了困扰
这一部分内容,在阅读上而言放在后面会更好一点,但是容易被忽视。
在使用中,我们会得到自动生成的路由映射,一定要注册到UIRouter;例如:

public class ShareApplike implements IApplicationLike {

    UIRouter uiRouter = UIRouter.getInstance();

    @Override
    public void onCreate() {
        uiRouter.registerUI("share");
    }

    @Override
    public void onStop() {
        uiRouter.unregisterUI("share");
    }
}

我们选择的是在组件的生命期入口进行注册和反注册。不多做赘述。

实际上这样我们就完成了集成,接下来就是使用了。

为路由节点(Activity)添加注解

注意,priority没有实质性意义,已废弃,Module中不允许出现同样的path

@RouteNode(path = "/main", desc = "首页")
public class MainActivity extends BaseActivity implements View.OnClickListener {
//...代码略去
}

以appModule中的MainActivity为例添加了一个节点。这样我们可以得到一个生成类:AppUiRouter:

public class AppUiRouter extends BaseCompRouter {
  @Override
  public String getHost() {
    return "app";
  }

  @Override
  public void initMap() {
    super.initMap();
    routeMapper.put("/main",MainActivity.class);
  }
}

一个用于辅助的Module路由表清单txt:AppRouterTable.txt

auto generated, do not change !!!! 

HOST : app

首页
/main

进行跳转

 UIRouter.getInstance().openUri({context},
                    "JIMU://app/main", {bundle});

关于参数

将在(二)中详细展开,这里简单介绍一下取参数,取参数使用了Autowired注解,进行DI。

以一个新版中的演示Demo为例:

@RouteNode(path = "/uirouter/demo/2", desc = "使用bundle传递参数")
public class Demo2Activity extends TestActivity {
    private static Bundle bundle = new Bundle();

    static {
        bundle.putString("foo", "foo string");
        bundle.putString("EXTRA_STR_BAR", "bar string");
    }

    @Autowired() //不指定名称时将使用变量名,若被混淆可能出现问题,
                // 建议使用name指定key,参考bar的使用
    String foo;

    @Autowired(name = "EXTRA_STR_BAR")
    String bar;

    public static final UiRouterDemoActivity.Case aCase
            = new UiRouterDemoActivity.Case(false,
            "使用bundle传递参数",
            "JIMU://app/uirouter/demo/2",
            bundle);

    @Override
    protected void displayInfo(TextView textView) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("使用bundle传递参数成功\r\n");
        stringBuilder.append("foo:").append(foo).append("\r\n");
        stringBuilder.append("bar:").append(bar).append("\r\n");

        textView.setText(stringBuilder.toString());
    }
}

父类的代码:

abstract class TestActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        textView = findViewById(R.id.demo_tv_info);
        AutowiredService.Factory.getSingletonImpl()
                .autowire(this);
        displayInfo(textView);
    }

    protected abstract void displayInfo(TextView textView);
}

稍微细说一下:
需要被注入的Field为:

@Autowired() //不指定名称时将使用变量名,若被混淆可能出现问题,
                // 建议使用name指定key,参考bar的使用
String foo;

@Autowired(name = "EXTRA_STR_BAR")
String bar;

建议:指定name!

我们看看生成的内容中,多了哪些:


public class AppUiRouter extends BaseCompRouter {
  @Override
  public String getHost() {
    return "app";
  }

  @Override
  public void initMap() {
    super.initMap();
    routeMapper.put("/main",MainActivity.class);
    routeMapper.put("/uirouter/demo/2",Demo2Activity.class);
    paramsMapper.put(Demo2Activity.class,new java.util.HashMap<String, Integer>(){{put("foo", 8); put("EXTRA_STR_BAR", 8); }});
  }
}

相比于MainActivity,多了一些东西:paramMapper中添加了一些配置,这里不详细展开,在不hook系统API的情况下,都会回归到使用Intent启动Activity。而传参依旧需要使用Bundle,我们知道bundle的读写是需要知道类型的。UIRouter支持的类型下一篇展开

/**
 * Auto generated by AutowiredProcessor */
public class Demo2Activity$$Router$$Autowired implements ISyringe {
  private JsonService jsonService;

  @Override
  public void inject(Object target) {
    jsonService = JsonService.Factory.getSingletonImpl();
    Demo2Activity substitute = (Demo2Activity)target;
    substitute.foo = substitute.getIntent().getStringExtra("foo");
    substitute.bar = substitute.getIntent().getStringExtra("EXTRA_STR_BAR");
  }

  @Override
  public void preCondition(Bundle bundle) throws ParamException {
  }
}
使用bundle传递参数
/uirouter/demo/2
foo:String
EXTRA_STR_BAR:String

注意:多了参数信息,是 name的值 和 参数的类型

发起注入的核心代码:和v1.0.0的API有一定区别

  AutowiredService.Factory.getSingletonImpl().autowire(this);

UIRouter的特性概述

时间和篇幅原因,移到(二)中展开。

常见问题Q/A

  1. Q:为什么无法跳转?
    A:根本原因都是没有正确集成:排查次序
  1. Q:出现了ClassNotFoundException怎么办?
    A:应该是启用了混淆,添加免混淆配置:可能因为项目变动的原因,后期会修改生成类path,一切以项目主页为准
-keep class com.luojilab.router.** {*;}
-keep class com.luojilab.gen.** {*;}
-keep class * implements com.luojilab.component.componentlib.router.ISyringe {*;}

3.Q:出现了错误很难排查怎么办?
A:根本原因是我当时和张老哥没有协调好,导致1.0.0的代码过早发出,而迭代版本因为其他原因迟迟未发,四月份一定发版本,新版本中log的输入以及防御性代码比较完善,应该可以提供充足的纠错信息。

4.Q:gradle plugin >=3.0 集成问题?
A:首先注意,我最开始就将依赖库和注解处理库分开了,这样已经避免了重复使用api和annotationProcessor声明同一个库的各种问题,如果在底层库中集中添加注解依赖库,使用api(或者还未移除的compile),不要使用Implementation;若是在组件Module中,随意使用api或Implementation。但是必须使用annotationProcessor声明注解处理库使用compile已经不会自动添加到注解处理包路径下

  1. Q:kotlin是否可以用?
    A:可以使用,如何集成参考demo,注意,对java和kt的Activity都使用注解,仅需要使用kapt声明注解处理器即可,按照Demo集成kapt3即可,不需要声明annotationProcessor,禁止使用早已废弃的apt插件

(Q/A持续更新)


下一篇将详细展开UIRouter的基础功能特性、新版本特性,并会安排发一个迭代版本。

JIMU的讨论群,群号693097923,欢迎大家加入:

image

qq群中有很多热心的朋友,一些重要的讨论,往往从群里面展开,最后转移到项目的issue中展开讨论以及总结。

上一篇下一篇

猜你喜欢

热点阅读