手撸自定义仿Arouter跳转路由一

Arouter模块选自享学derry老师的项目

各大链接分享

 1.开源最佳实践:Android平台页面路由框架ARouter-阿里云开发者社区 (aliyun.com)

​ 2.中文ARouter使用API

 3.通俗易懂的文章

​ 4.同学们,想深入研究的同学,可以看这篇博客(可能是最详细的ARouter源码分析):https://www.jianshu.com/p/bc4c34c6a06c

 5.字节码插桩 https://juejin.cn/post/6844903709101522952 (Gradle自定义插件,热修复asm字节码插桩 )

 6.​ ARouter解析

 7.Github的ARouter地址:https://github.com/alibaba/ARouter

 8.中文ARouter使用API:https://github.com/alibaba/ARouter/blob/master/README_CN.md

 9.官方解释,最全的,同学们一定要看:https://developer.aliyun.com/article/71687

10、ButterKnife 原理解析

 

 

 

butterknife以及Arouter所使用的技术的都有自定义注解+反射+apt技术,本文就是大概了解一下黄油刀的技术原理就,然后手撸arouter的初步架构

撸完一边基本就了解了Arouter的基本原理了

一、butterknife view绑定实现的大致逻辑与思路

1、首先要晓得在框架里,是存在一个自定义的注解处理器,下面这个是注解处理器的部分代码

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
        TypeElement typeElement = entry.getKey();
        BindingSet binding = entry.getValue();

        JavaFile javaFile = binding.brewJava(sdk, debuggable);
        try {
            javaFile.writeTo(filer);
        } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
        }
    }

    return false;
}

然后对注解进行循环处理,目的就是为了findviewbyid将获取到的viewid赋值给注解的id,具体详细的代码就不粘贴了

通过反射获取了相应的注解,拿到注解以后,for循环对注解进行处理生成对应的java文件,一般的命名格式都是classname+_ViewBinding.可以看一下生成的是什么文件

在这里面通过ButterKnife的bind方法会通过两个参数的构造函数进行实例化,在构造函数中,可以看出最终还是会调用findViewById方法,并对view进行点击事件的监听,DebouncingOnClickListener是View.OnclickListener的子类,用于防止一定时间内对View的多次点击,在onClick方法中执行抽象方法doClick,在MainActivity_ViewBinding的构造函数中,可以看到为Button的点击时会调用MainActivity的clickButton方法,也就是在MainActivity中通过注解@OnClick定义的方法。

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.btn_test)
    Button mButton;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        mButton.setText("测试");
    }

    @OnClick(R.id.btn_test)
    public void clickButton() {
        Toast.makeText(this, "test", Toast.LENGTH_SHORT).show();
    }
}


public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view2131427415;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;

    View view;
    view = Utils.findRequiredView(source, R.id.btn_test, "field 'mButton' and method 'clickButton'");
    target.mButton = Utils.castView(view, R.id.btn_test, "field 'mButton'", Button.class);
    view2131427415 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.clickButton();
      }
    });
  }

  @Override
  @CallSuper
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.mButton = null;

    view2131427415.setOnClickListener(null);
    view2131427415 = null;
  }
}


public abstract class DebouncingOnClickListener implements View.OnClickListener {
  static boolean enabled = true;

  private static final Runnable ENABLE_AGAIN = new Runnable() {
    @Override public void run() {
      enabled = true;
    }
  };

  @Override public final void onClick(View v) {
    if (enabled) {
      enabled = false;
      v.post(ENABLE_AGAIN);
      doClick(v);
    }
  }

  public abstract void doClick(View v);
}

2、上面注解的方式完成以后,当程序初始化以后,就会通过注解将每一个的view赋上对应的id,自此就完成了对viewId的动态赋值。

二、Arouter-动态生成懒加载参数接收

通过上面的启发,我们可以同样用这样的原理,动态生成懒加载参数接收

下面开始记录整个过程,遵循一下四步

路由管理器的编写

 

项目结构

1.)为要传递的参数添加注解arouter_api->Parameter,给参数带上注解,方便进行接收

@Target(ElementType.FIELD) // 该注解作用在类之上 字段上有作用
@Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作,注解会在class文件中存在
public @interface Parameter {

    // 不填写name的注解值表示该属性名就是key,填写了就用注解值作为key
    // 从getIntent()方法中获取传递参数值
    String name() default "";
}

2.)标准规则制定 arouter_api->ParameterGet

这里所谓的规则,就是我们通过apt生成的java文件统一实现的方法,这样我们获取了类以后就可以统一使用方法接收参数,维持统一的格式

等下可以在第四步使用

public interface ParameterGet {

    /**
     * 目标对象.属性名 = getIntent().属性类型... 完成赋值操作
     * @param targetParameter 目标对象:例如:MainActivity 中的那些属性
     */
    void getParameter(Object targetParameter);

}

3.)自定义注解处理器arouter_compiler,通过javapoet生成了我们想要的类文件

@AutoService(Processor.class) // 开启
@SupportedAnnotationTypes({ProcessorConfig.PARAMETER_PACKAGE}) // 我们服务的注解
@SupportedSourceVersion(SourceVersion.RELEASE_7) // 同学们:这个是必填的哦
public class ParameterProcessor extends AbstractProcessor {

    private Elements elementUtils; // 类信息
    private Types typeUtils;  // 具体类型
    private Messager messager; // 日志
    private Filer filer;  // 生成器

    // 临时map存储,用来存放被@Parameter注解的属性集合,生成类文件时遍历
    // key:类节点, value:被@Parameter注解的属性集合
    private Map<TypeElement, List<Element>> tempParameterMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 由于返回了 false 就不需要一下代码了
        /*if (set.isEmpty()) {
            messager.printMessage(Diagnostic.Kind.NOTE, "并没有发现 被@ARouter注解的地方呀");
            return false; // 没有机会处理
        }*/

        // 扫描的时候,看那些地方使用到了@Parameter注解
        if (!ProcessorUtils.isEmpty(set)) {
            // 获取所有被 @Parameter 注解的 元素(属性)集合
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Parameter.class);

            if (!ProcessorUtils.isEmpty(elements)) {
                // TODO 给仓库 存储相关信息
                for (Element element : elements) { // element == name, sex,  age

                    // 字段节点的上一个节点 类节点==Key
                    // 注解在属性的上面,属性节点父节点 是 类节点
                    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

                    // enclosingElement == Personal_MainActivity == key

                    if (tempParameterMap.containsKey(enclosingElement)) {
                        tempParameterMap.get(enclosingElement).add(element);
                    } else { // 没有key Personal_MainActivity
                        List<Element> fields = new ArrayList<>();
                        fields.add(element);
                        tempParameterMap.put(enclosingElement, fields); // 加入缓存
                    }
                } // for end  缓存 tempParameterMap有值了

                // TODO 生成类文件
                // 判断是否有需要生成的类文件
                if (ProcessorUtils.isEmpty(tempParameterMap)) return true;

                TypeElement activityType = elementUtils.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE);
                TypeElement parameterType = elementUtils.getTypeElement(ProcessorConfig.AROUTER_AIP_PARAMETER_GET);

                // 生成方法
                // Object targetParameter
                ParameterSpec parameterSpec = ParameterSpec.builder(TypeName.OBJECT, ProcessorConfig.PARAMETER_NAME).build();

                // 循环遍历 缓存tempParameterMap
                // 可能很多地方都使用了 @Parameter注解,那么就需要去遍历 仓库
                for (Map.Entry<TypeElement, List<Element>> entry : tempParameterMap.entrySet()) {
                    // key:   Personal_MainActivity
                    // value: [name,sex,age]
                    TypeElement typeElement = entry.getKey();

                    // 非Activity直接报错
                    // 如果类名的类型和Activity类型不匹配
                    if (!typeUtils.isSubtype(typeElement.asType(), activityType.asType())) {
                        throw new RuntimeException("@Parameter注解目前仅限用于Activity类之上");
                    }

                    // 是Activity
                    // 获取类名 == Personal_MainActivity
                    ClassName className = ClassName.get(typeElement);

                    // 方法生成成功
                    ParameterFactory factory = new ParameterFactory.Builder(parameterSpec)
                            .setMessager(messager)
                            .setClassName(className)
                            .build();

                    // Personal_MainActivity t = (Personal_MainActivity) targetParameter;
                    factory.addFirstStatement();

                    // 难点 多行
                    for (Element element : entry.getValue()) {
                        factory.buildStatement(element);
                    }

                    // 最终生成的类文件名(类名$$Parameter) 例如:Personal_MainActivity$$Parameter
                    String finalClassName = typeElement.getSimpleName() + ProcessorConfig.PARAMETER_FILE_NAME;
                    messager.printMessage(Diagnostic.Kind.NOTE, "APT生成获取参数类文件:" +
                            className.packageName() + "." + finalClassName);

                    // 开始生成文件,例如:PersonalMainActivity$$Parameter
                    try {
                        JavaFile.builder(className.packageName(), // 包名
                                TypeSpec.classBuilder(finalClassName) // 类名
                                        .addSuperinterface(ClassName.get(parameterType)) //  implements ParameterGet 实现ParameterLoad接口
                                        .addModifiers(Modifier.PUBLIC) // public修饰符
                                        .addMethod(factory.build()) // 方法的构建(方法参数 + 方法体)
                                        .build()) // 类构建完成
                                .build() // JavaFile构建完成
                                .writeTo(filer); // 文件生成器开始生成类文件
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        // 1 2 3节课 true  执行两次 为了防止第二有问题 加了if (set.isEmpty()) {  内部机制回来检测一遍 所以有了第二次
        //     4节课 false 执行一次
        return false; // 前几年研究 好像记得 执行一次
    }
}

生成的文件类似下面的:,可以看到所有的文件的命名以及实现的类,都具有步骤2的统一的规则

public class Personal_MainActivity$$Parameter implements ParameterGet {
  @Override
  public void getParameter(Object targetParameter) {
    Personal_MainActivity t = (Personal_MainActivity) targetParameter;
    t.name = t.getIntent().getStringExtra("name");
    t.sex = t.getIntent().getStringExtra("sex");
    t.age = t.getIntent().getIntExtra("age", t.age);
  }
}

4.)arouter_api-->ParameterManager参数管理器编写

之后调用loadParamater方法就可以完成跳转的页面的内的参数接收

/**
 * 参数的 加载管理器
 * TODO 同学们:这是用于接收参数的
 *
 *  第一步:查找 Personal_MainActivity$$Parameter
 *  第二步:使用 Personal_MainActivity$$Parameter  this 给他
 */
public class ParameterManager {

    private static ParameterManager instance;

    // private boolean isCallback;

    public static ParameterManager getInstance() {
        if (instance == null) {
            synchronized (ParameterManager.class) {
                if (instance == null) {
                    instance = new ParameterManager();
                }
            }
        }
        return instance;
    }

    // LRU缓存 key=类名      value=参数加载接口
    private LruCache<String, ParameterGet> cache;

    private ParameterManager() {
        cache = new LruCache<>(100);
    }

    // 为什么还要拼接,此次拼接 是为了寻找他
    static final String FILE_SUFFIX_NAME = "$$Parameter"; // 为了这个效果:Order_MainActivity + $$Parameter

    // 使用者 只需要调用这一个方法,就可以进行参数的接收
    public void loadParameter(Activity activity) { // 必须拿到 Personal_MainActivity
        String className = activity.getClass().getName(); // className == Personal_MainActivity

        ParameterGet parameterLoad = cache.get(className); // 先从缓存里面拿 如果有  如果没有
        if (null == parameterLoad) { // 缓存里面没东东   提高性能
            // 拼接 如:Order_MainActivity + $$Parameter
            // 类加载Order_MainActivity + $$Parameter
            try {
                // 类加载Personal_MainActivity + $$Parameter
                Class<?> aClass = Class.forName(className + FILE_SUFFIX_NAME);
                // 用接口parameterLoad = 接口的实现类Personal_MainActivity
                parameterLoad = (ParameterGet) aClass.newInstance();
                cache.put(className, parameterLoad); // 保存到缓存
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        parameterLoad.getParameter(activity); // 最终的执行  会执行我们生成的类
    }
}

使用示例:

 ParameterManager.getInstance().loadParameter(this);

至此,我们完成了传递参数的自定义注解、注解处理器、编译期运行生成的参数接收类、接收参数的使用开关类,基本可以实现参数的接收。

接下来,参数可以传递了,那么怎么跳转呢

三、Arouter路由跳转

这一步我们目的是实现组件之间的跳转,还是通过注解、反射、apt来进行解耦

1.)组件之间的注解arouter_annotation->ARouter

@Target(ElementType.TYPE) // 该注解作用在类之上
@Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作,注解会在class文件中存在
public @interface ARouter {

    // 详细路由路径(必填),如:"/app/MainActivity"
    String path();

    // 路由组名(选填,如果开发者不填写,可以从path中截取出来)
    String group() default "";
}

2.)编译器生成类的规则制定arouter_api->ARouterGroup、ARouterPath,分别作为模块获取以及模块下的类的获取

/**
 * Group分组的领头 带头大哥
 *
 *
 * TODO
 *  *    key:   app
 *  *    value:  ARouterPath
 */
public interface ARouterGroup {

    /**
     * 例如:order分组下有这些信息,personal分组下有这些信息
     * 例如:"order" --- ARouterPath的实现类 -->(APT生成出来的 ARouter$$Path$$order)
     *
     * @return  key:"order/app/personal"      value:系列的order组下面所有的(path---class)
     */
    Map<String, Class<? extends ARouterPath>> getGroupMap();

}

 

/**
 *  其实就是 路由组 Group 对应的 ---- 详细Path加载数据接口 ARouterPath
 *  例如:order分组 对应 ---- 有那些类需要加载(Order_MainActivity  Order_MainActivity2 ...)
 *
 *
 *  TODO
 *    key:   /app/MainActivity1
 *    value:  RouterBean(MainActivity1.class)
 *
 */
public interface ARouterPath {

    /**
     * 例如:order分组下有这些信息,personal分组下有这些信息
     *
     * @return key:"/order/Order_MainActivity"   或  "/personal/Personal_MainActivity"
     *         value: RouterBean==Order_MainActivity.class 或 RouterBean=Personal_MainActivity.class
     */
    Map<String, RouterBean> getPathMap();

}

3.)注解处理器arouter_compiler->ARouterProcessor

这里面有apt对注解的处理,缓存的处理。动态生成的处理

目的是为了将项目所有的添加注解的类路径进行缓存和添加,便于编译期生成的文件用于跳转呀

/**
 * 同学们注意:编码此类,记住就是一个字(细心,细心,细心),出了问题debug真的不好调试
 */

// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)

// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({ProcessorConfig.AROUTER_PACKAGE})

// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)

// 注解处理器接收的参数
@SupportedOptions({ProcessorConfig.OPTIONS, ProcessorConfig.APT_PACKAGE})

public class ARouterProcessor extends AbstractProcessor {

    // 操作Element的工具类(类,函数,属性,其实都是Element)
    private Elements elementTool;

    // type(类信息)的工具类,包含用于操作TypeMirror的工具方法
    private Types typeTool;

    // Message用来打印 日志相关信息
    private Messager messager;

    // 文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
    private Filer filer;

    private String options; // 各个模块传递过来的模块名 例如:app order personal
    private String aptPackage; // 各个模块传递过来的目录 用于统一存放 apt生成的文件

    // 仓库一 Path  缓存一
    // Map<"personal", List<RouterBean>>
    private Map<String, List<RouterBean>> mAllPathMap = new HashMap<>(); // 目前是一个

    // 仓库二 Group 缓存二
    // Map<"personal", "ARouter$$Path$$personal.class">
    private Map<String, String> mAllGroupMap = new HashMap<>();

    // 做初始化工作,就相当于 Activity中的 onCreate函数一样的作用
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        elementTool = processingEnvironment.getElementUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        typeTool = processingEnvironment.getTypeUtils();

        // 只有接受到 App壳 传递过来的书籍,才能证明我们的 APT环境搭建完成
        options = processingEnvironment.getOptions().get(ProcessorConfig.OPTIONS);
        aptPackage = processingEnvironment.getOptions().get(ProcessorConfig.APT_PACKAGE);
        messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>>>>>>>>> options:" + options);
        messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>>>>>>>>> aptPackage:" + aptPackage);
        if (options != null && aptPackage != null) {
            messager.printMessage(Diagnostic.Kind.NOTE, "APT 环境搭建完成....");
        } else {
            messager.printMessage(Diagnostic.Kind.NOTE, "APT 环境有问题,请检查 options 与 aptPackage 为null...");
        }
    }

    /**
     * 相当于main函数,开始处理注解
     * 注解处理器的核心方法,处理具体的注解,生成Java文件
     *
     * @param set              使用了支持处理注解的节点集合
     * @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找的注解。
     * @return true 表示后续处理器不会再处理(已经处理完成)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) {
            messager.printMessage(Diagnostic.Kind.NOTE, "并没有发现 被@ARouter注解的地方呀");
            return false; // 没有机会处理
        }

        // 获取所有被 @ARouter 注解的 元素集合
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);

        // 通过Element工具类,获取Activity,Callback类型
        TypeElement activityType = elementTool.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE);
        // 显示类信息(获取被注解的节点,类节点)这也叫自描述 Mirror
        TypeMirror activityMirror = activityType.asType();

        // 遍历所有的类节点
        for (Element element : elements) {
            // 获取类节点,获取包节点 (com.xiangxue.xxxxxx)
            // String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();

            // 获取简单类名,例如:MainActivity
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "被@ARetuer注解的类有:" + className); // 打印出 就证明APT没有问题

            // 拿到注解
            ARouter aRouter = element.getAnnotation(ARouter.class);

            // TODO  一系列的检查工作
            // 在循环里面,对 “路由对象” 进行封装
            RouterBean routerBean = new RouterBean.Builder()
                    .addGroup(aRouter.group())
                    .addPath(aRouter.path())
                    .addElement(element)
                    .build();

            // ARouter注解的类 必须继承 Activity
            TypeMirror elementMirror = element.asType(); // Main2Activity的具体详情 例如:继承了 Activity
            if (typeTool.isSubtype(elementMirror, activityMirror)) { // activityMirror  android.app.Activity描述信息
                routerBean.setTypeEnum(RouterBean.TypeEnum.ACTIVITY); // 最终证明是 Activity
            } else { // Derry.java 的干法 就会抛出异常
                // 不匹配抛出异常,这里谨慎使用!考虑维护问题
                throw new RuntimeException("@ARouter注解目前仅限用于Activity类之上");
            }

            if (checkRouterPath(routerBean)) {
                messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean Check Success:" + routerBean.toString());

                // 赋值 mAllPathMap 集合里面去
                List<RouterBean> routerBeans = mAllPathMap.get(routerBean.getGroup());

                // 如果从Map中找不到key为:bean.getGroup()的数据,就新建List集合再添加进Map
                if (ProcessorUtils.isEmpty(routerBeans)) { // 仓库一 没有东西
                    routerBeans = new ArrayList<>();
                    routerBeans.add(routerBean);
                    mAllPathMap.put(routerBean.getGroup(), routerBeans);// 加入仓库一
                } else {
                    routerBeans.add(routerBean);
                }
            } else { // ERROR 编译期发生异常
                messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
            }
        } // TODO end for  同学们注意:在循环外面了 (此循环结束后,仓库一 缓存一 就存好所有 Path值了)

        // mAllPathMap 里面有值了
        // 定义(生成类文件实现的接口) 有 Path Group
        TypeElement pathType = elementTool.getTypeElement(ProcessorConfig.AROUTER_API_PATH); // ARouterPath描述
        TypeElement groupType = elementTool.getTypeElement(ProcessorConfig.AROUTER_API_GROUP); // ARouterGroup描述

        // TODO 第一大步:系列PATH
        try {
            createPathFile(pathType); // 生成 Path类
        } catch (IOException e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.NOTE, "在生成PATH模板时,异常了 e:" + e.getMessage());
        }

        // TODO 第二大步:组头(带头大哥)
        try {
            createGroupFile(groupType, pathType);
        } catch (IOException e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.NOTE, "在生成GROUP模板时,异常了 e:" + e.getMessage());
        }

        return true; // 坑:必须写返回值,表示处理@ARouter注解完成
    }

    /**
     * 生成路由组Group文件,如:ARouter$$Group$$app
     * @param groupType ARouterLoadGroup接口信息
     * @param pathType ARouterLoadPath接口信息
     */
    private void createGroupFile(TypeElement groupType, TypeElement pathType) throws IOException {
        // 仓库二 缓存二 判断是否有需要生成的类文件
        if (ProcessorUtils.isEmpty(mAllGroupMap) || ProcessorUtils.isEmpty(mAllPathMap)) return;

        // 返回值 这一段 Map<String, Class<? extends ARouterPath>>
        TypeName methodReturns = ParameterizedTypeName.get(
                ClassName.get(Map.class),        // Map
                ClassName.get(String.class),    // Map<String,

                // Class<? extends ARouterPath>> 难度
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        // ? extends ARouterPath
                        WildcardTypeName.subtypeOf(ClassName.get(pathType))) // ? extends ARouterLoadPath
                        // WildcardTypeName.supertypeOf() 做实验 ? super

                // 最终的:Map<String, Class<? extends ARouterPath>>
        );

        // 1.方法 public Map<String, Class<? extends ARouterPath>> getGroupMap() {
        MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(ProcessorConfig.GROUP_METHOD_NAME) // 方法名
                .addAnnotation(Override.class) // 重写注解 @Override
                .addModifiers(Modifier.PUBLIC) // public修饰符
                .returns(methodReturns); // 方法返回值

        // Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
        methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
                ClassName.get(Map.class),
                ClassName.get(String.class),

                // Class<? extends ARouterPath> 难度
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathType))), // ? extends ARouterPath
                        ProcessorConfig.GROUP_VAR1,
                        ClassName.get(HashMap.class));

        //  groupMap.put("personal", ARouter$$Path$$personal.class);
        //	groupMap.put("order", ARouter$$Path$$order.class);
        for (Map.Entry<String, String> entry : mAllGroupMap.entrySet()) {
            methodBuidler.addStatement("$N.put($S, $T.class)",
                    ProcessorConfig.GROUP_VAR1, // groupMap.put
                    entry.getKey(), // order, personal ,app
                    ClassName.get(aptPackage, entry.getValue()));
        }

        // return groupMap;
        methodBuidler.addStatement("return $N", ProcessorConfig.GROUP_VAR1);

        // 最终生成的类文件名 ARouter$$Group$$ + personal
        String finalClassName = ProcessorConfig.GROUP_FILE_NAME + options;

        messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由组Group类文件:" +
                aptPackage + "." + finalClassName);

        // 生成类文件:ARouter$$Group$$app
        JavaFile.builder(aptPackage, // 包名
                TypeSpec.classBuilder(finalClassName) // 类名
                .addSuperinterface(ClassName.get(groupType)) // 实现ARouterLoadGroup接口 implements ARouterGroup
                .addModifiers(Modifier.PUBLIC) // public修饰符
                .addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)
                .build()) // 类构建完成
                .build() // JavaFile构建完成
                .writeTo(filer); // 文件生成器开始生成类文件
    }

    /**
     * 系列Path的类  生成工作
     * @param pathType ARouterPath 高层的标准
     * @throws IOException
     */
    private void createPathFile(TypeElement pathType) throws IOException {
        // 判断 map仓库中,是否有需要生成的文件
        if (ProcessorUtils.isEmpty(mAllPathMap)) {
            return; // 连缓存一 仓库一 里面 值都没有 不用干活了
        }

        // 倒序生成代码

        // 任何的class类型,必须包装
        // Map<String, RouterBean>
        TypeName methodReturn = ParameterizedTypeName.get(
                  ClassName.get(Map.class),         // Map
                  ClassName.get(String.class),      // Map<String,
                  ClassName.get(RouterBean.class)   // Map<String, RouterBean>
        );

        // 遍历仓库 app,order,personal
        for (Map.Entry<String, List<RouterBean>> entry : mAllPathMap.entrySet()) { // personal
            // 1.方法
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ProcessorConfig.PATH_METHOD_NAME)
                    .addAnnotation(Override.class) // 给方法上添加注解  @Override
                    .addModifiers(Modifier.PUBLIC) // public修饰符
                    .returns(methodReturn) // 把Map<String, RouterBean> 加入方法返回
                    ;

            // Map<String, RouterBean> pathMap = new HashMap<>(); // $N == 变量 为什么是这个,因为变量有引用 所以是$N
            methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
                    ClassName.get(Map.class),           // Map
                    ClassName.get(String.class),        // Map<String,
                    ClassName.get(RouterBean.class),    // Map<String, RouterBean>
                    ProcessorConfig.PATH_VAR1,          // Map<String, RouterBean> pathMap
                    ClassName.get(HashMap.class)        // Map<String, RouterBean> pathMap = new HashMap<>();
                    );

            // 必须要循环,因为有多个
            // pathMap.put("/personal/Personal_Main2Activity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY,
            // Personal_Main2Activity.class);
            // pathMap.put("/personal/Personal_MainActivity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY));
            List<RouterBean> pathList = entry.getValue();
            /**
                $N == 变量 变量有引用 所以 N
                $L == TypeEnum.ACTIVITY
             */
            // personal 的细节
            for (RouterBean bean : pathList) {
                methodBuilder.addStatement("$N.put($S, $T.create($T.$L, $T.class, $S, $S))",
                        ProcessorConfig.PATH_VAR1, // pathMap.put
                        bean.getPath(), // "/personal/Personal_Main2Activity"
                        ClassName.get(RouterBean.class), // RouterBean
                        ClassName.get(RouterBean.TypeEnum.class), // RouterBean.Type
                        bean.getTypeEnum(), // 枚举类型:ACTIVITY
                        ClassName.get((TypeElement) bean.getElement()), // MainActivity.class Main2Activity.class
                        bean.getPath(), // 路径名
                        bean.getGroup() // 组名
                        );
            } // TODO end for

            // return pathMap;
            methodBuilder.addStatement("return $N", ProcessorConfig.PATH_VAR1);

            // TODO 注意:不能像以前一样,1.方法,2.类  3.包, 因为这里面有implements ,所以 方法和类要合为一体生成才行,这是特殊情况

            // 最终生成的类文件名  ARouter$$Path$$personal
            String finalClassName = ProcessorConfig.PATH_FILE_NAME + entry.getKey();

            messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由Path类文件:" +
                    aptPackage + "." + finalClassName);

            // 生成类文件:ARouter$$Path$$personal
            JavaFile.builder(aptPackage, // 包名  APT 存放的路径
                    TypeSpec.classBuilder(finalClassName) // 类名
                            .addSuperinterface(ClassName.get(pathType)) // 实现ARouterLoadPath接口  implements ARouterPath==pathType
                            .addModifiers(Modifier.PUBLIC) // public修饰符
                            .addMethod(methodBuilder.build()) // 方法的构建(方法参数 + 方法体)
                            .build()) // 类构建完成
                    .build() // JavaFile构建完成
                    .writeTo(filer); // 文件生成器开始生成类文件

            // 仓库二 缓存二  非常重要一步,注意:PATH 路径文件生成出来了,才能赋值路由组mAllGroupMap
            mAllGroupMap.put(entry.getKey(), finalClassName);
        }
    }

    /**
     * 校验@ARouter注解的值,如果group未填写就从必填项path中截取数据
     * @param bean 路由详细信息,最终实体封装类
     */
    private final boolean checkRouterPath(RouterBean bean) {
        String group = bean.getGroup(); //  同学们,一定要记住: "app"   "order"   "personal"
        String path = bean.getPath();   //  同学们,一定要记住: "/app/MainActivity"   "/order/Order_MainActivity"   "/personal/Personal_MainActivity"

        // 校验
        // @ARouter注解中的path值,必须要以 / 开头(模仿阿里Arouter规范)
        if (ProcessorUtils.isEmpty(path) || !path.startsWith("/")) {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的path值,必须要以 / 开头");
            return false;
        }

        // 比如开发者代码为:path = "/MainActivity",最后一个 / 符号必然在字符串第1位
        if (path.lastIndexOf("/") == 0) {
            // 架构师定义规范,让开发者遵循
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
            return false;
        }

        // 从第一个 / 到第二个 / 中间截取,如:/app/MainActivity 截取出 app,order,personal 作为group
        String finalGroup = path.substring(1, path.indexOf("/", 1));

        // app,order,personal == options

        // @ARouter注解中的group有赋值情况
        if (!ProcessorUtils.isEmpty(group) && !group.equals(options)) {
            // 架构师定义规范,让开发者遵循
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的group值必须和子模块名一致!");
            return false;
        } else {
            bean.setGroup(finalGroup);
        }

        // 如果真的返回ture   RouterBean.group  xxxxx 赋值成功 没有问题
        return true;
    }
}

生成的代码示例:

app下各个类的获取方式

public class ARouter$$Path$$app implements ARouterPath {
  @Override
  public Map<String, RouterBean> getPathMap() {
    Map<String, RouterBean> pathMap = new HashMap<>();
    pathMap.put("/app/Main2Activity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY, Main2Activity.class, "/app/Main2Activity", "app"));
    pathMap.put("/app/MainActivity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY, MainActivity.class, "/app/MainActivity", "app"));
    return pathMap;
  }
}

group下的app的获取方式

public class ARouter$$Group$$app implements ARouterGroup {
  @Override
  public Map<String, Class<? extends ARouterPath>> getGroupMap() {
    Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
    groupMap.put("app", ARouter$$Path$$app.class);
    return groupMap;
  }
}

4.)组件化路由管理器RouterManager以及bundle参数传递管理BundleManager

RouterManager:目的是通过建造者模式封装参数的传递以及跳转逻辑,navation方式是跳转的具体逻辑

BundleManager:辅助RouterManager将参数封装在bundle里面

/**
 * 整个目标
 * 第一步:查找 ARouter$$Group$$personal ---> ARouter$$Path$$personal
 * 第二步:使用 ARouter$$Group$$personal ---> ARouter$$Path$$personal
 */
public class RouterManager {

    private String group; // 路由的组名 app,order,personal ...
    private String path;  // 路由的路径  例如:/order/Order_MainActivity

    /**
     * 上面定义的两个成员变量意义:
     * 1.拿到ARouter$$Group$$personal  根据组名 拿到 ARouter$$Path$$personal
     * 2.操作路径,通过路径 拿到  Personal_MainActivity.class,就可以实现跳转了
     */

    // 单例模式
    private static RouterManager instance;

    public static RouterManager getInstance() {
        if (instance == null) {
            synchronized (RouterManager.class) {
                if (instance == null) {
                    instance = new RouterManager();
                }
            }
        }
        return instance;
    }

    // 提供性能  LRU缓存
    private LruCache<String, ARouterGroup> groupLruCache;
    private LruCache<String, ARouterPath> pathLruCache;

    // 为了拼接,例如:ARouter$$Group$$personal
    private final static String FILE_GROUP_NAME = "ARouter$$Group$$";

    private RouterManager() {
        groupLruCache = new LruCache<>(100);
        pathLruCache = new LruCache<>(100);
    }

    /***
     * @param path 例如:/order/Order_MainActivity
     *      * @return
     */
    public BundleManager build(String path) {
        if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
            throw new IllegalArgumentException("不按常理出牌 path乱搞的啊,正确写法:如 /order/Order_MainActivity");
        }

        // 同学可以自己增加
        // ...

        if (path.lastIndexOf("/") == 0) { // 只写了一个 /
            throw new IllegalArgumentException("不按常理出牌 path乱搞的啊,正确写法:如 /order/Order_MainActivity");
        }

        // 截取组名  /order/Order_MainActivity  finalGroup=order
        String finalGroup = path.substring(1, path.indexOf("/", 1)); // finalGroup = order

        if (TextUtils.isEmpty(finalGroup)) {
            throw new IllegalArgumentException("不按常理出牌 path乱搞的啊,正确写法:如 /order/Order_MainActivity");
        }

        // 证明没有问题,没有抛出异常
        this.path =  path;  // 最终的效果:如 /order/Order_MainActivity
        this.group = finalGroup; // 例如:order,personal

        // TODO 走到这里后  grooup 和 path 没有任何问题   app,order,personal      /app/MainActivity

        return new BundleManager(); // Builder设计模式 之前是写里面的, 现在写外面吧
    }

    // 真正的导航
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public Object navigation(Context context, BundleManager bundleManager) {
        // 例如:寻找 ARouter$$Group$$personal  寻址   ARouter$$Group$$order   ARouter$$Group$$app
        String groupClassName = context.getPackageName() + "." + FILE_GROUP_NAME + group;
        Log.e("derry >>>", "navigation: groupClassName=" + groupClassName);


        try {
            // TODO 第一步 读取路由组Group类文件
            ARouterGroup loadGroup = groupLruCache.get(group);
            if (null == loadGroup) { // 缓存里面没有东东
                // 加载APT路由组Group类文件 例如:ARouter$$Group$$order
                Class<?> aClass = Class.forName(groupClassName);
                // 初始化类文件
                loadGroup = (ARouterGroup) aClass.newInstance();

                // 保存到缓存
                groupLruCache.put(group, loadGroup);
            }

            if (loadGroup.getGroupMap().isEmpty()) {
                throw new RuntimeException("路由表Group报废了..."); // Group这个类 加载失败
            }

            // TODO 第二步 读取路由Path类文件
            ARouterPath loadPath = pathLruCache.get(path);
            if (null == loadPath) { // 缓存里面没有东东 Path
                // 1.invoke loadGroup
                // 2.Map<String, Class<? extends ARouterLoadPath>>
                Class<? extends ARouterPath> clazz = loadGroup.getGroupMap().get(group);

                // 3.从map里面获取 ARouter$$Path$$personal.class
                loadPath = clazz.newInstance();

                // 保存到缓存
                pathLruCache.put(path, loadPath);
            }

            // TODO 第三步 跳转
            if (loadPath != null) { // 健壮
                if (loadPath.getPathMap().isEmpty()) { // pathMap.get("key") == null
                    throw new RuntimeException("路由表Path报废了...");
                }

                // 最后才执行操作
                RouterBean routerBean = loadPath.getPathMap().get(path);

                if (routerBean != null) {
                    switch (routerBean.getTypeEnum()) {
                        case ACTIVITY:
                            Intent intent = new Intent(context, routerBean.getMyClass()); // 例如:getClazz == Order_MainActivity.class
                            intent.putExtras(bundleManager.getBundle()); // 携带参数
                            context.startActivity(intent, bundleManager.getBundle());
                            break;
                        //同学们可以自己扩展 类型
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}
/**
 * 跳转时 ,用于参数的传递
 */
public class BundleManager {

    // Intent传输  携带的值,保存到这里
    private Bundle bundle = new Bundle();

    public Bundle getBundle() {
        return this.bundle;
    }

    // 对外界提供,可以携带参数的方法
    public BundleManager withString(@NonNull String key, @Nullable String value) {
        bundle.putString(key, value);
        return this; // 链式调用效果 模仿开源框架
    }

    public BundleManager withBoolean(@NonNull String key, @Nullable boolean value) {
        bundle.putBoolean(key, value);
        return this;
    }

    public BundleManager withInt(@NonNull String key, @Nullable int value) {
        bundle.putInt(key, value);
        return this;
    }

    public BundleManager withBundle(Bundle bundle) {
        this.bundle = bundle;
        return this;
    }

    // Derry只写到了这里,同学们可以自己增加 ...

    // 直接完成跳转
    public Object navigation(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            return RouterManager.getInstance().navigation(context, this);
        }
        return null;
    }
}

经过上面一番操作,在各个组件下都会有编译期生成的用于跳转的java方法,以及获取参数的方法,如图:

 

 

看一下使用

MainActivity下跳转

    // 使用我们自己写的路由 跳转交互
        RouterManager.getInstance()
                .build("/personal/Order_MainActivity ")
                .withString("name", "史甄湘")
                .withString("sex", "男")
                .withInt("age", 99)
                .navigation(this);

Order下的接收

@ARouter(path = "/order/Order_MainActivity")
public class Order_MainActivity extends AppCompatActivity {

    @Parameter
    String name;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.order_activity_main);

        // 用到才加载 == 懒加载
        ParameterManager.getInstance().loadParameter(this);

        Log.e(Cons.TAG, "order/Order_MainActivity name:" + name);
    }

    public void jumpPersonal(View view) {
        // Toast.makeText(this, "路由还没有写好呢,别猴急...", Toast.LENGTH_SHORT).show();

        RouterManager.getInstance()
                .build("/personal/Personal_MainActivity")
                .withString("name", "李元霸")
                .withString("sex", "男")
                .withInt("age", 99)
                .navigation(this);
    }
}

路由管理以及参数懒加载完事了,还有后续优化

未完待续~

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值