前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android组件化专题 - 路由框架原理

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

作者头像
用户3045442
发布2018-09-11 16:21:19
1.7K0
发布2018-09-11 16:21:19
举报
文章被收录于专栏:Android研究院Android研究院

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

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

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

本文项目地址

为什么需要路由

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

一般来说我们会这样写:

代码语言:javascript
复制
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("dataKey", "dataValue");
startActivity(intent);

如果在封装一层这样写:

代码语言:javascript
复制
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
代码语言:javascript
复制
@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 存储的分组信息

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

具体分组:

代码语言:javascript
复制
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 就是存储了一些路由分组表的信息

代码语言:javascript
复制
/**
 * 存储的路由对象
 */
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 配置:

代码语言:javascript
复制
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类

代码语言:javascript
复制
//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);
    }


}

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

代码语言:javascript
复制
 /**
     * 检查设置路由组
     *
     * @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 类

代码语言:javascript
复制
    /**
     * 生成分组表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 类

代码语言:javascript
复制
    /**
     * 生成路由表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 中配置:

代码语言:javascript
复制
annotationProcessor project(':primrouter-compiler')

implementation project(':primrouter-annotation')

在MainActivity中添加注解

代码语言:javascript
复制
@Router(path = "/app/main")
public class MainActivity extends AppCompatActivity {
}

会生成以下两个类:

image.png

则说明动态生成类正确。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android研究院 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么需要路由
  • 路由框架实现思路
    • 路由设计的思路
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档