Android组件化专题 - 路由框架原理

微信公众号:Android研究院 关注可了解更多的Android知识,专注于移动领域,不知代码还有人生的哲学。

为什么需要路由路由框架实现思路路由设计的思路赞赏

在路由框架之前,我们先了解什么是APT,并实践ButterKnife绑定findById的小功能。为什么先要讲解apt,因为路由的实现apt是核心的代码.看下面链接 APT 实践。

本文项目地址

为什么需要路由

我们知道路由就是实现页面的跳转,然而Android原生已经支持app页面间的跳转。

一般来说我们会这样写:

Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("dataKey", "dataValue");
startActivity(intent);

如果在封装一层这样写:

public class ManagerActivity extends BaseActivity {
    public static void launch(Context context, int startTab) {
        Intent i = new Intent(context, ManagerActivity.class);
        i.putExtra(INTENT_IN_INT_START_TAB, startTab);
        context.startActivity(i);
    }
}

其实无论那种实现方式,本质上都是生成Intent,然后通过context.startActivity/context.startActivityForResult来实现页面的跳转。但是这种方法的不足是:当包含多个模块,且模块之间没有相互依赖的跳转就会变的很困难,如果知道模块对应的类名和路径,可以通过Intent.setComponent(Component)方法启动其他模块的页面。同时还要写大量重复代码,要记录每个模块的类名和路径,如果类名和路径发生改变的话。。。。。。我相信你的内心肯定是崩溃的。

存在的一些缺点:

  • 显示Intent:项目庞大以后,类依赖耦合太大,不适合组件化拆分
  • 隐式Intent:协作困难,调用时候不知道调什么参数
  • 每个注册了Scheme的Activity都可以直接打开,有安全风险
  • AndroidMainfest集中式管理比较臃肿
  • 无法动态修改路由,如果页面出错,无法动态降级
  • 无法动态拦截跳转,譬如未登录的情况下,打开登录页面,登录成功后接着打开刚才想打开的页面
  • H5、Android、iOS地址不一样,不利于统一跳转

页面路由的意义:

路由最先被应用于网络中,路由的定义可以通过互联的网络把信息从源地址传输到目的地址的过程。每个页面可以定义为一个统一的资源标示符,在网络当中能够被别人访问,也可以访问已经被定义了的页面。

路由常见的使用场景:

  • App接收到一个通知,点击通知打开App的某个页面
  • 浏览器App中点击某个链接打开App的某个页面
  • App的H5活动页面打开一个链接,可能是H5跳转,也可能是跳转到某一个native页面
  • 打开页面需要某些条件,先验证完条件,再去打开那个页面(如验证是否登录)
  • 不合法的打开App的页面被屏蔽掉
  • App内的跳转,可以减少手动构建Intent的成本,同时可以统一携带部分参数到下一个页面
  • App存在就打开页面,不存在就去下载页面下载,只有Google的App Link支持

路由框架实现思路

通过上述的路由的应用和APT开发,相信你对APT有了一定的了解,那么路由框架要如何实现呢?实现思路是怎样的?

路由设计的思路

image.png

  1. 通过注解 Activity 类,注解处理器处理注解(APT)动态生成路由信息。
  2. 收集路由:通过定义的包名,找到所有动态生成的类,将路由信息存储到本地仓库 (rootMap).
  3. 页面跳转:根据注解的路由地址,从本地仓库中找到相关的路由信息,获取到要跳转的类,然后实现跳转。

只需要在使用路由的页面定义一个类注解@Router即可,页面路由的使用也相当简单

下面我们看下具体代码的实现思路

  1. 首先实现自定义注解,新建一个java lib 取名:primrouter-annotation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Router {
    /**
     * 设置路由地址/main/text
     * @return 地址
     */
    String path();

    /**
     * 设置路由组,若为空以路由地址的第一个来作为组名main
     * @return 路由组
     */
    String group() default "";
}
  1. 然后我们要清楚动态生成的类,其实主要就是存储一些跳转的Activity的类信息和路由地址等,同时为了逻辑更加清晰,给不同的module的进行分组。当路由跳转的时候可以通过路由group 得到分组表,然后通过路由地址path得到分组表中存储的路由对象,来实现跳转。

如以下自动生成的类:

module 存储的分组信息

public class PrimRouter$$Root$$moudle1 implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routers) {
    routers.put("module", PrimRouter$$Group$$module.class);
  }
}

具体分组:

public class PrimRouter$$Group$$module implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouterMeta> atlas) {
    atlas.put("/module/test",RouterMeta.build(RouterMeta.Type.ACTIVITY,MainActivity.class,"/module/test","module"));
    atlas.put("/module/test2",RouterMeta.build(RouterMeta.Type.ACTIVITY,Main2Activity.class,"/module/test2","module"));
  }
}

RouterMeta 就是存储了一些路由分组表的信息

/**
 * 存储的路由对象
 */
public class RouterMeta {

    /**
     * 路由的类型枚举
     */
    public enum Type {
        ACTIVITY, INSTANCE
    }

    /**
     * 路由的类型
     */
    private Type type;

    /**
     * 节点 activity
     */
    private Element element;

    /**
     * 注解使用的类对象
     */
    private Class<?> destination;

    /**
     * 路由地址
     */
    private String path;

    /**
     * 路由组
     */
    private String group;

    public static RouterMeta build(Type type, Class<?> destination, String path, String
            group) {
        return new RouterMeta(type, null, destination, path, group);
    }


    public RouterMeta() {
    }

    public RouterMeta(Type type, Router router, Element element) {
        this(type, element, null, router.path(), router.group());
    }

    public RouterMeta(Type type, Element element, Class<?> destination, String path, String group) {
        this.type = type;
        this.destination = destination;
        this.element = element;
        this.path = path;
        this.group = group;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public void setElement(Element element) {
        this.element = element;
    }

    public void setDestination(Class<?> destination) {
        this.destination = destination;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public Type getType() {
        return type;
    }

    public Element getElement() {
        return element;
    }

    public Class<?> getDestination() {
        return destination;
    }

    public String getPath() {
        return path;
    }

    public String getGroup() {
        return group;
    }
}
  1. 动态生成Java class类,新建一个 java lib 取名:prim_compiler 这一步通过APT来实现。这一步其实很简单分析完要生成的类,然后通过javaopt 来动态生成,如果还不懂APT 可以看这一篇文章APT 实践。

gradle 配置:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.google.auto.service:auto-service:1.0-rc4'
    implementation 'com.squareup:javapoet:1.11.1'
    implementation project(':primrouter-annotation')
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

新建一个processor类

//AnnotationProcessor register
@AutoService(Processor.class)

/**
 * compiled java version {@link AbstractProcessor#getSupportedSourceVersion()}
 */
@SupportedSourceVersion(RELEASE_7)

/**
 * Allow AnnotationProcessor process annotation,{@link AbstractProcessor#getSupportedAnnotationTypes()}
 */
@SupportedAnnotationTypes({"com.prim.router.primrouter_annotation.Router"})

/**
 * 注解处理器接收的参数 {@link AbstractProcessor#getSupportedOptions()}
 */
@SupportedOptions({Consts.ARGUMENTS_NAME})

/**
 * Router 注解处理器
 */
public class RouterProcessor extends AbstractProcessor {

    private Messager messager;

    /**
     * 文件生成器 类/资源
     */
    private Filer filer;

    /**
     *
     */
    private Locale locale;

    /**
     * 获取传递的参数
     */
    private Map<String, String> options;

    /**
     * compiled java version
     */
    private SourceVersion sourceVersion;

    /**
     * 类型工具类
     */
    private Types typeUtils;

    /**
     * 节点工具类
     */
    private Elements elementUtils;

    /**
     * 模块的名称
     */
    private String moduleName;

    /**
     * key 组名  value 类名
     */
    private Map<String, String> rootMap = new TreeMap<>();

    /**
     * 分组 key 组名  value 对应组的路由信息
     */
    private Map<String, List<RouterMeta>> groupMap = new HashMap<>();

    private Log log;

    /**
     * init process environment utils
     *
     * @param processingEnvironment
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        log = Log.newLog(messager);
        filer = processingEnvironment.getFiler();
        locale = processingEnvironment.getLocale();
        options = processingEnvironment.getOptions();
        sourceVersion = processingEnvironment.getSourceVersion();
        typeUtils = processingEnvironment.getTypeUtils();
        elementUtils = processingEnvironment.getElementUtils();

        // 获取传递的参数
        if (!options.isEmpty()) {
            moduleName = options.get(Consts.ARGUMENTS_NAME);
            log.i("module:" + moduleName);
        }

        if (Utils.isEmpty(moduleName)) {
            throw new NullPointerException("Not set Processor Parmaters.");
        }

    }

    /**
     * process annotation
     *
     * @param set              The set of nodes that support processing annotations
     * @param roundEnvironment Current or previous operating environment,annotation that can be found by this object
     * @return true already processed ,follow-up will not be dealt with
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (!set.isEmpty()) {
            //Set nodes annotated by Router
            Set<? extends Element> annotatedWith = roundEnvironment.getElementsAnnotatedWith(Router.class);
            if (annotatedWith != null) {
                processRouter(annotatedWith);
            }
            return true;
        }
        return false;
    }

    /**
     * 处理被注解的节点集合
     *
     * @param annotatedWith
     */
    private void processRouter(Set<? extends Element> annotatedWith) {
        RouterMeta routerMeta = null;
        //获得Activity类的节点信息
        TypeElement activity = elementUtils.getTypeElement(Consts.Activity);
        //单个的节点
        for (Element element : annotatedWith) {
            // 获取类信息 如Activity类
            TypeMirror typeMirror = element.asType();
            // 获取节点的注解信息
            Router annotation = element.getAnnotation(Router.class);
            //只能指定的类上面使用
            if (typeUtils.isSubtype(typeMirror, activity.asType())) {
                //存储路由相关的信息
                routerMeta = new RouterMeta(RouterMeta.Type.ACTIVITY, annotation, element);
            } else if (typeUtils.isSubtype(typeMirror, activity.asType())) {

            } else {
                throw new RuntimeException("Just Support Activity Router!");
            }

            //检查是否配置group如果没有配置 则从path中截取组名
            checkRouterGroup(routerMeta);
        }

        //获取 primrouter-core IRouterGroup 类节点
        TypeElement routeGroupElement = elementUtils.getTypeElement(Consts.ROUTEGROUP);
        //获取 primrouter-core IRouterRoot 类节点
        TypeElement routeRootElement = elementUtils.getTypeElement(Consts.ROUTEROOT);

        //生成 $$Group$$ 记录分组表
        generatedGroupTable(routeGroupElement);

        //生成 $$Root$$ 记录路由表
        generatedRootTable(routeRootElement, routeGroupElement);
    }


}

检查路由地址是否配置正确,这一步可以自己定义相关的路由地址规则

 /**
     * 检查设置路由组
     *
     * @param routerMeta
     */
    private void checkRouterGroup(RouterMeta routerMeta) {
        if (routerVerify(routerMeta)) {
            List<RouterMeta> routerMetas = groupMap.get(routerMeta.getGroup());
            if (Utils.isEmpty(routerMetas)) {
                routerMetas = new ArrayList<>();
                routerMetas.add(routerMeta);
                groupMap.put(routerMeta.getGroup(), routerMetas);
            } else {
                routerMetas.add(routerMeta);
            }
        } else {
            log.i("router path no verify,please check");
        }
    }

    /**
     * 验证路由地址配置是否正确合法性
     *
     * @param routerMeta 存储的路由bean对象
     * @return true 路由地址配置正确  false 路由地址配置不正确
     */
    private boolean routerVerify(RouterMeta routerMeta) {
        String path = routerMeta.getPath();
        if (Utils.isEmpty(path)) {
            throw new NullPointerException("@Router path not to be null or to length() == 0");
        }
        if (!path.startsWith("/")) {//路由地址必须以/开头
            throw new IllegalArgumentException("@Router path must / first");
        }
        String group = routerMeta.getGroup();
        if (Utils.isEmpty(group)) {
            String defaultGroup = path.substring(1, path.indexOf("/", 1));
            //截取的还是为空
            if (Utils.isEmpty(defaultGroup)) {
                return false;
            }
            //设置group
            routerMeta.setGroup(defaultGroup);
        }
        return true;
    }

生成分组表class 类

    /**
     * 生成分组表class 类
     *
     * @param routeGroupElement primrouter-core IRouterGroup 类节点
     */
    private void generatedGroupTable(TypeElement routeGroupElement) {
        //创建参数类型 Map<String,RouterMeta>
        ParameterizedTypeName atlas = ParameterizedTypeName.get(ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouterMeta.class));

        //创建参数 Map<String,RouterMeta> atlas
        ParameterSpec altlas = ParameterSpec
                .builder(atlas, Consts.GROUP_PARAM_NAME)//参数名
                .build();//创建参数

        //遍历分组 每一个分组 创建一个 $$Group$$类
        for (Map.Entry<String, List<RouterMeta>> entry : groupMap.entrySet()) {
            MethodSpec.Builder builder = MethodSpec.methodBuilder(Consts.GROUP_METHOD_NAME)//函数名
                    .addModifiers(PUBLIC)//作用域
                    .addAnnotation(Override.class)//添加一个注解
                    .addParameter(altlas);//添加参数

            //Group组中
            List<RouterMeta> groupData = entry.getValue();

            //遍历 生成函数体
            for (RouterMeta meta : groupData) {
                //$S = String
                //$T = class
                //添加函数体
                builder.addStatement(Consts.GROUP_PARAM_NAME+".put($S,$T.build($T.$L,$T.class,$S,$S))",
                        meta.getPath(),
                        ClassName.get(RouterMeta.class),
                        ClassName.get(RouterMeta.Type.class),
                        meta.getType(),
                        ClassName.get((TypeElement) meta.getElement()),
                        meta.getPath(),
                        meta.getGroup());
            }

            MethodSpec loadInto = builder.build();//函数创建完成loadInto();

            String groupClassName = Consts.GROUP_CLASS_NAME + entry.getKey();

            TypeSpec typeSpec = TypeSpec.classBuilder(groupClassName)//类名
                    .addSuperinterface(ClassName.get(routeGroupElement))//实现接口IRouteGroup
                    .addModifiers(PUBLIC)//作用域
                    .addMethod(loadInto)//添加方法
                    .build();//类创建完成

            //生成Java文件
            JavaFile javaFile = JavaFile
                    .builder(Consts.PAGENAME, typeSpec)//包名和类
                    .build();
            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            rootMap.put(entry.getKey(), groupClassName);
        }

    }

生成路由表class 类

    /**
     * 生成路由表class 类
     *
     * @param routeRootElement
     */
    private void generatedRootTable(TypeElement routeRootElement, TypeElement routeGroupElement) {
        ////类型 Map<String,Class<? extends IRouteGroup>> routes>
        ParameterizedTypeName atlas = ParameterizedTypeName.get(ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(routeGroupElement))));

        //创建参数  Map<String,Class<? extends IRouteGroup>>> routes
        ParameterSpec altlas = ParameterSpec
                .builder(atlas, Consts.ROOT_PARAM_NAME)//参数名
                .build();//创建参数

        //public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
        //函数 public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
        MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder
                (Consts.ROOT_METHOD_NAME)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(altlas);

        //函数体
        for (Map.Entry<String, String> entry : rootMap.entrySet()) {
            loadIntoMethodOfRootBuilder.addStatement(Consts.ROOT_PARAM_NAME + ".put($S, $T.class)", entry
                    .getKey(), ClassName.get(Consts.PAGENAME, entry.getValue
                    ()));
        }
        //生成 $Root$类
        String rootClassName = Consts.ROOT_CLASS_NAME + moduleName;
        try {
            JavaFile.builder(Consts.PAGENAME,
                    TypeSpec.classBuilder(rootClassName)
                            .addSuperinterface(ClassName.get(routeRootElement))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfRootBuilder.build())
                            .build()
            ).build().writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
  1. 检查动态生成的类是否正确

在app的gradle 中配置:

annotationProcessor project(':primrouter-compiler')

implementation project(':primrouter-annotation')

在MainActivity中添加注解

@Router(path = "/app/main")
public class MainActivity extends AppCompatActivity {
}

会生成以下两个类:

image.png

则说明动态生成类正确。

原文发布于微信公众号 - Android研究院(androidlinksu)

原文发表时间:2018-08-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Flutter知识集

Flutter 实践 MVVM

在做Android或iOS开发时,经常会了解到MVC,MVP和MVVM。MVVM在移动端一度被非常推崇,虽然也有不少反对的声音,不过MVVM确实是不错的设计架构...

3.4K50
来自专栏向治洪

Android 几种网络请求的区别与联系

HttpUrlConnection 最开始学android的时候用的网络请求是HttpUrlConnection,当时很多东西还不知道,但是在android...

23850
来自专栏coolblog.xyz技术专栏

Spring IOC 容器源码分析 - 余下的初始化工作

本篇文章是“Spring IOC 容器源码分析”系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的...

10010
来自专栏破晓之歌

Django框架下admin.py的中文修改 原

#所以更改setttings.py 下 LANGUAGE_CODE = 'zh-Hans' 

11420
来自专栏Scott_Mr 个人专栏

RxSwift 实战操作【注册登录】

40760
来自专栏游戏杂谈

bat与jscript开发工具时遇到的一些问题

之前使得bat调用luac进行编译时,会弹出一个“黑色的界面”,闪烁一下,感觉不太好。而脚本vbs或者jscript调用bat是可以利用Run方法,将其第二个参...

13220
来自专栏blackpiglet

Go 装饰器模式在 API 服务程序中的使用

  Go 语言是由谷歌主导并开源的编程语言,和 C 语言有不少相似之处,都强调执行效率,语言结构尽量简单,也都主要用来解决相对偏底层的问题。因为 Go 简洁的语...

20520
来自专栏跟着阿笨一起玩NET

C# 地磅串口编程

然后最近有一个项目用到了地磅,这里也是通过串口通讯方式进行数据交互,说实话,地磅这东西,实在有点不方便。

22720
来自专栏JackieZheng

探秘Tomcat——启动篇

tomcat作为一款web服务器本身很复杂,代码量也很大,但是模块化很强,最核心的模块还是连接器Connector和容器Container。具体请看下图: ? ...

50070
来自专栏Spark学习技巧

Spark源码系列之spark2.2的StructuredStreaming使用及源码介绍

一,概述 Structured Streaming是一个可扩展和容错的流处理引擎,并且是构建于sparksql引擎之上。你可以用处理静态数据的方式去处理你的流计...

1.4K70

扫码关注云+社区

领取腾讯云代金券