组件化解耦 | 浅析ARouter路由发现原理与简单实践

前言

2022新年好,回顾过去两年虽然零零散散多少也学了点东西,但是缺少了总结输出,一方面是因为工作比之前忙了很多,一方面也是因为自己懈怠了,正好前段时间公司有重构需求需要了解下路由框架,借这个机会重新捡起一些丢掉的东西吧!

ARouter在众多路由框架中也算是经典了,但是对于SDK开发来说,ARouter不免有点过重了,里面有大量对Activity和Fragment的业务,对于SDK解耦基本用不上,但是我们还是可以参考ARouter的路由发现思路,完成一个自己的小路由框架,直接进入主题吧

项目地址/资源

项目概览

对于最新版本(1.5.2)比起两年前发现多了一个gradle插件(可能是之前没看到),是可选项,可以加快首次启动加载速度,项目模块和对应功能:

模块 功能
arouter-api 核心API,包括路由发现、初始化跳转等
arouter-compiler 注解处理器,根据注解和模块生成对应的类
arouter-annotation 注解相关信息
arouter-gradle-plugin gradle 插件工程,利用ASM插桩加快加载速度

路由动态注册与生成

核心思路与技术要点

  • 利用注解生成 路径 - 类 映射表,以及拓展功能(降级、拦截器、绿色通道等);再通过核心API封装进行跳转查找
  • APT 注解处理生成各个模块的路由类
  • 【可选】ASM 插桩代码,加快找到路由注册表(原有方式是扫描APK下所有dex文件固定包名下的类)

APT处理注解

arouter-compiler编译时扫描符合的注解,ARouter共有三个Processor,以主要的RouteProcessor为例,会生成三类文件

  • ARouter$$Root$$模块名: 添加模块内 《组名 - Gronp类》的映射表
  • ARouter$$Group**$$**组名: 各个组内《路径 - 路由信息》的映射表(不同模块同个组名会导致覆盖!!)
  • ARouter$$Providers$$模块名: 一个模块一个,存放所有Provider类型的映射

图为demo工程生成的类:
在这里插入图片描述
还是以基础的路由发现为例,在demo的moudlejava模块中,会生成

  • ARouter$$Root$$modulejava.java
  • ARouter$$Group$$yourservicegroupname.java
  • ARouter$$Group$$test.java
  • ARouter$$Group$$m2.java
  • ARouter$$Group$$module.java

其中ARouter$$Root$$modulejava 内容如下

public interface IRouteRoot {
    void loadInto(Map<String, Class<? extends IRouteGroup>> routes);
}

public class ARouter$$Root$$modulejava implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("m2", ARouter$$Group$$m2.class); // 各个模块的组名和对应的类
    routes.put("module", ARouter$$Group$$module.class);
    routes.put("test", ARouter$$Group$$test.class);
    routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
  }
}

组内以ARouter$$Group$$test为例,其内容如下


public interface IRouteGroup {
    /**
     * Fill the atlas with routes in group.
     */
    void loadInto(Map<String, RouteMeta> atlas);
}

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("pac", 10); put("ch", 5); put("obj", 11); put("fl", 6); put("name", 8); put("dou", 7); put("boy", 0); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}

可以看到存放的是完整路径对应的类、优先级、类型等关系,看到这里应该就大概明白,通过这些生成的类,维护着一个映射关系表,从而实现路由发现和查找

那么接下来就是如何将这一个个文件中的路由关系收集起来,进而实现各种业务逻辑

路由初始化

上面说到基础原理是通过维护一个映射关系表,从而实现路由查找功能的,那么ARouter是如何找到这些类并且加载的呢?这里看到 arouter-api 模块中初始化是如何调用的,直接上图
在这里插入图片描述
跟踪代码可以发现最后逻辑在LogisticsCenter#init 方法,逻辑参照注释

    /**
     * LogisticsCenter init, load all metas in memory. Demand initialization
     */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            
            //load by plugin first  
            loadRouterMap(); // 注意:这个方法是个空实现,如果使用了gradle插件,会在这个方法进行插桩
            // registerByPlugin 默认为false 只有使用了gradle插件才会置为true
            if (registerByPlugin) {
                logger.info(TAG, "Load router map by arouter-auto-register plugin.");
            } else {
                
                Set<String> routerMap;

                // It will rebuild router map every times when debuggable.
                // 如果首次启动或者debug模式,就会扫描特定包名下所有的类
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    
                    // These class was generated by arouter-compiler.
                    // 工具类 扫描特定包名下类,有兴趣的可以翻源码查看,主要是根据各种不同的dex情况进行查找
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }

                    PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                } else {
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }

         ...

        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

结合逻辑图和源码,大概可以知道整个框架的路由发现这块的逻辑就是查找固定包名下所有的类,这里不纠结细节,继续往下看使用Gradle插件时的逻辑以及他是如何加快启动速度的

Gradle插件实现

gradle插件是一个可选的模块,通过初始化的源码可以看到如果没有应用此插件,在应用启动的时候会对包下所有的dex进行扫描并找到特定包名下所有的类,这样有个问题就是首次启动会慢一点,于是便有了这个插件进行ASM进行插桩代码,直接在打包时就把路由信息打入,直接看下应用插件后的效果

  • 应用插件前
 //  LogisticsCenter
   private static void loadRouterMap() {
        registerByPlugin = false;
        // auto generate register code by gradle plugin: arouter-auto-register
        // looks like below:
        // registerRouteRoot(new ARouter..Root..modulejava());
        // registerRouteRoot(new ARouter..Root..modulekotlin());
    }
  • 应用插件后
// LogisticsCenter
    private static void loadRouterMap() {
        registerByPlugin = false;
        register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulejava");
        register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulekotlin");
        register("com.alibaba.android.arouter.routes.ARouter$$Root$$arouterapi");
        register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$modulejava");
        register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulejava");
        register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulekotlin");
        register("com.alibaba.android.arouter.routes.ARouter$$Providers$$arouterapi");
    }
    // 跟踪其他方法 调用国产
    private static void register(String className) {
        String str = "ARouter::";
        if (!TextUtils.isEmpty(className)) {
            try {
                Object obj = Class.forName(className).getConstructor(new Class[0]).newInstance(new Object[0]);
                if (obj instanceof IRouteRoot) {
                    registerRouteRoot((IRouteRoot) obj);
                } else if (obj instanceof IProviderGroup) {
                    registerProvider((IProviderGroup) obj);
                } else if (obj instanceof IInterceptorGroup) {
                    registerInterceptor((IInterceptorGroup) obj);
                } else {
                    ARouter.logger.info(str, "register failed, class name: " + className + " should implements one of IRouteRoot/IProviderGroup/IInterceptorGroup.");
                }
            } catch (Exception e) {
                ARouter.logger.error(str, "register class error:" + className, e);
            }
        }
    }

    private static void registerRouteRoot(IRouteRoot routeRoot) {
        markRegisteredByPlugin();
        if (routeRoot != null) {
            routeRoot.loadInto(Warehouse.groupsIndex);
        }
    }
    
    private static void markRegisteredByPlugin() {
        if (!registerByPlugin) {
            registerByPlugin = true;
        }
    }    
    

现在大概知道ARouter的gradle插件做了些什么东西了,反推实现在RegisterTransform 这个类中扫描了jar、源码里面相关的类,并记录下来,最后写入方法中,下面直接看插件和RegisterTransform实现源码
gradle插件这里是初始化了目标接口信息,将相关的接口封装成一个ScanSetting的列表(ScanSetting主要存放接口名称和查找到的类名列表),然后注册了RegisterTransform

public class PluginLaunch implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        def isApp = project.plugins.hasPlugin(AppPlugin)
        //only application module needs this plugin to generate register code
        if (isApp) {
            Logger.make(project)

            Logger.i('Project enable arouter-register plugin')

            def android = project.extensions.getByType(AppExtension)
            def transformImpl = new RegisterTransform(project)

            //init arouter-auto-register settings
            ArrayList<ScanSetting> list = new ArrayList<>(3)
            list.add(new ScanSetting('IRouteRoot'))
            list.add(new ScanSetting('IInterceptorGroup'))
            list.add(new ScanSetting('IProviderGroup'))
            RegisterTransform.registerList = list
            //register this plugin
            android.registerTransform(transformImpl)
        }
    }

}

RegisterTransform实现

    // RegisterTransform#transform
    
    void transform(Context context, Collection<TransformInput> inputs
                   , Collection<TransformInput> referencedInputs
                   , TransformOutputProvider outputProvider
                   , boolean isIncremental) throws IOException, TransformException, InterruptedException {

        ...

        // 省略遍历所有code、jar的代码,最后都会走到ScanUtil的几个方法中,见下文解析
  

         // registerList 类型是 ArrayList<ScanSetting> 存放目标接口信息
        if (fileContainsInitClass) {
            registerList.each { ext ->
                Logger.i('Insert register code to file ' + fileContainsInitClass.absolutePath)

                if (ext.classList.isEmpty()) {
                    Logger.e("No class implements found for interface:" + ext.interfaceName)
                } else {
                    ext.classList.each {
                        Logger.i(it)
                    }
                    // 这里插入代码
                    RegisterCodeGenerator.insertInitCodeTo(ext)
                }
            }
        }

        Logger.i("Generate code finish, current cost time: " + (System.currentTimeMillis() - startTime) + "ms")
    }
目标类查找

上面省略的代码在拿到特定包名所有的类后(jar、源码)走到了
ScanUtil.scanJar(src, dest)ScanUtil.scanClass(file) 这两个方法,跟踪调用后都走到ScanUtil#scanClass(InputStream inputStream),然后通过ASM遍历是否实现了相关接口,如果存在就存放在对应的ScanSetting里面的类名列表等待下一步插入代码

 static void scanClass(InputStream inputStream) {
        ClassReader cr = new ClassReader(inputStream)
        ClassWriter cw = new ClassWriter(cr, 0)
        ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM5, cw)
        cr.accept(cv, ClassReader.EXPAND_FRAMES)
        inputStream.close()
    }

    static class ScanClassVisitor extends ClassVisitor {

        ScanClassVisitor(int api, ClassVisitor cv) {
            super(api, cv)
        }

        void visit(int version, int access, String name, String signature,
                   String superName, String[] interfaces) {
            super.visit(version, access, name, signature, superName, interfaces)
            // 这里的ext是一个ScanSetting类,里面有扫描的信息包括IRouteRoot、IProviderGroup、IInterceptorGroup
            RegisterTransform.registerList.each { ext ->
                // 这里判断是不是符合的类
                if (ext.interfaceName && interfaces != null) {
                    interfaces.each { itName ->
                        if (itName == ext.interfaceName) {
                            //fix repeated inject init code when Multi-channel packaging
                            if (!ext.classList.contains(name)) {
                               // 存放在ScanSetting.classList 中
                                ext.classList.add(name)
                            }
                        }
                    }
                }
            }
        }
    }
插入代码

插入代码这里也是遍历ScanSetting列表了,然后通过RegisterCodeGenerator.(ScanSetting registerSetting)方法插入代码

// RegisterCodeGenerator#insertInitCodeTo
    static void insertInitCodeTo(ScanSetting registerSetting) {
        if (registerSetting != null && !registerSetting.classList.isEmpty()) {
            RegisterCodeGenerator processor = new RegisterCodeGenerator(registerSetting)
            // 这里的file是指LogisticsCenter.class 所在的jar,扫描时标注赋值的,毕竟要在一个类插入代码,首先要知道这个类在哪里
            File file = RegisterTransform.fileContainsInitClass
            if (file.getName().endsWith('.jar'))
                processor.insertInitCodeIntoJarFile(file)
        }
// RegisterCodeGenerator#insertInitCodeIntoJarFile
private File insertInitCodeIntoJarFile(File jarFile) {
        if (jarFile) {
            ...

            while (enumeration.hasMoreElements()) {
                ...
                if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {
                    // 找到目标文件开始插入
                    def bytes = referHackWhenInit(inputStream)
                    jarOutputStream.write(bytes)
                } else {
                    jarOutputStream.write(IOUtils.toByteArray(inputStream))
                }
                inputStream.close()
                jarOutputStream.closeEntry()
            }
            ...
        }
        return jarFile
    }   

// RegisterCodeGenerator#referHackWhenInit
    private byte[] referHackWhenInit(InputStream inputStream) {
        ClassReader cr = new ClassReader(inputStream)
        ClassWriter cw = new ClassWriter(cr, 0)
        ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw)
        cr.accept(cv, ClassReader.EXPAND_FRAMES)
        return cw.toByteArray()
    }     

前面扫描标注了LogisticsCenter.class所在的jar,这里也是找到这个jar,然后遍历这个jar找到LogisticsCenter.class,然后调用了MyClassVisitor,MyClassVisitor里面再调用一个RouteMethodVisitor找到目标方法然后插入代码,这一块应该很好理解,直接看RouteMethodVisitor的实现

class RouteMethodVisitor extends MethodVisitor {

        RouteMethodVisitor(int api, MethodVisitor mv) {
            super(api, mv)
        }

        @Override
        void visitInsn(int opcode) {
            //generate code before return
            if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
                // 遍历前一步存放的类名列表
                extension.classList.each { name ->
                    name = name.replaceAll("/", ".")
                    mv.visitLdcInsn(name)//类名
                    // generate invoke register method into LogisticsCenter.loadRouterMap()
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC
                            , ScanSetting.GENERATE_TO_CLASS_NAME
                            , ScanSetting.REGISTER_METHOD_NAME
                            , "(Ljava/lang/String;)V"
                            , false)
                }
            }
            super.visitInsn(opcode)
        }
        @Override
        void visitMaxs(int maxStack, int maxLocals) {
            super.visitMaxs(maxStack + 4, maxLocals)
        }
    }

至此,Gradle插件就完成了所有的工作

实现一个简单的路由发现框架

前面说到这次重新看ARouter的原因主要是公司项目重构,而且主要是想用一下这个路由发现业务,在分析完ARouter路由注册和发现这块的逻辑,可以看到还是比较好实现的,下面示例一个简单版本的实现

模块组成

  • zrouter-annotation 定义了注解还有包含注解的信息
  • zrouter-api 核心API,包括初始化等
  • zrouter-compiler 注解处理器,用来根据注解动态生成模块代码

ZRouter-annotation

这个模块主要定义了用到的注解和处理注解信息生成的bean类工具类等,示例也非常简单直接参考ARouter的


// Route.java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

    String path();

    int priority() default -1;

    int extras() default Integer.MIN_VALUE;

}

// RouteMeta.java

public class RouteMeta {

    private Class<?> destination;   
    private String path;           
    private String group;          
    private int priority = -1;     
    private int extra;
    
    ...
    // 省略 get set方法和构造函数
}

ZRouter-compiler

直接上代码,核心部分是process,其他一些api工具也贴上方便以后参考,三方库主要使用了javapoet生成代码以及auto-service动态注册


// ZRouterCompiler.java
@AutoService(Processor.class)
public class ZRouterCompiler extends AbstractProcessor {

    /**
     * 节点工具类(类、函数、属性都是节点)
     */
    private Elements mElementUtils;

    /**
     * 类信息工具类
     */
    private Types mTypeUtils;

    /**
     * 文件生成器
     */
    private Filer mFiler;

    /**
     * 日志信息打印器
     */
    private Messager mMessager;

    // Module name, maybe its 'app' or others
    String moduleName = null;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mElementUtils = processingEnv.getElementUtils();
        mTypeUtils = processingEnv.getTypeUtils();
        mFiler = processingEnv.getFiler();
        mMessager = processingEnv.getMessager();


        //通过 key 获取 build.gradle 中对应的 value
//        processingEnv.getOptions().get("KeyValue");

        // Attempt to get user configuration [moduleName]
        Map<String, String> options = processingEnv.getOptions();
        if (MapUtils.isNotEmpty(options)) {
            moduleName = options.get(KEY_MODULE_NAME);
        }

        if (StringUtils.isNotEmpty(moduleName)) {
            moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");

            mMessager.printMessage(Diagnostic.Kind.NOTE,"The user has configuration the module name, it was [" + moduleName + "]");
        } else {
            mMessager.printMessage(Diagnostic.Kind.ERROR,"NO_MODULE_NAME");
            throw new RuntimeException("ZRouter::Compiler >>> No module name, for more information, look at gradle log.");
        }

    }


    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        if (CollectionUtils.isEmpty(annotations)) {
            return false;
        }

        List<Element> elementList = new ArrayList<>(roundEnv.getElementsAnnotatedWith(Route.class));
        Map<Element,RouteMeta> metaMap = new HashMap<>();


       try {
           // 获取注解信息,构建bean类
           for (Element element : elementList){
               System.out.println("ele:" + element.getSimpleName());
               RouteMeta routeMeta = new RouteMeta();
               Route annotation = element.getAnnotation(Route.class);
               routeMeta.setPath(annotation.path());
               routeMeta.setExtra(annotation.extras());
               routeMeta.setPriority(annotation.priority());
               metaMap.put(element,routeMeta);
           }

           // javapoet API,构建一个方法       
           MethodSpec.Builder loadPluginMethodBuilder = Utils.getLoadPluginMethodBuilder();

           // 遍历注解信息,生成代码
           for (Map.Entry<Element,RouteMeta> entry : metaMap.entrySet()){
               RouteMeta routeMeta = entry.getValue();
               Element element = entry.getKey();

               loadPluginMethodBuilder.addStatement("data.add(new $T($T.class,$S,$S," +
                       routeMeta.getPriority()+ "," + routeMeta.getExtra()+ "))",
                       CLS_ROUTE_META,
                       ClassName.get((TypeElement) element),
                       routeMeta.getPath(),
                       routeMeta.getGroup()
               );

           }

           // 生成文件
           String fileName = PREFIX_CLASS_NAME + moduleName;
           System.out.println("ready to generate file:" + fileName);
           JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                   TypeSpec.classBuilder(fileName)
                           .addJavadoc(FILE_TIP)
                           .addSuperinterface(CLS_ROUTER_ACQUIRER)
                           .addModifiers(PUBLIC)
                           .addMethod(loadPluginMethodBuilder.build())
                           .build()
           ).addFileComment(FILE_TIP).build().writeTo(mFiler);




       }catch (Exception e){
           e.printStackTrace();
           System.out.println("=====build route error =====");
       }

        return true;
    }

    /**
     * 接收外来传入的参数,最常用的形式就是在 build.gradle 脚本文件里的 javaCompileOptions 的配置
     *
     * @return 属性的 Key 集合
     */
    @Override
    public Set<String> getSupportedOptions() {
//        Set<String> hashSet = new LinkedHashSet<>();
//        hashSet.add("MODULE_NAME");
        return super.getSupportedOptions();
    }

    /**
     * 当前注解处理器支持的注解集合,如果支持,就会调用 process 方法
     *
     * @return 支持的注解集合
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> hashSet = new LinkedHashSet<>();
        hashSet.add(Route.class.getCanonicalName());
//        hashSet.add(BindClick.class.getCanonicalName());
        return hashSet;
    }

    /**
     * 编译当前注解处理器的 JDK 版本
     *
     * @return JDK 版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_8;
    }

工具类和常量

// Utils.java
public class Utils {

    static final ClassName ROUTER_ACQUIRER = ClassName.get("com.hjl.zrouter_api","IRouteAcquirer");

    static ParameterizedTypeName inputMapType = ParameterizedTypeName.get(
            ClassName.get(List.class),
            ClassName.get(RouteMeta.class)
    );

    public static MethodSpec.Builder getLoadPluginMethodBuilder(){
        return MethodSpec.methodBuilder(METHOD_LOAD_PLUGIN)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(inputMapType,"data");
    }

}

// Constant.java
interface Constant {
    String KEY_MODULE_NAME = "ZROUTER_MODULE_NAME";
    String PREFIX_CLASS_NAME = "ZRouter$$";
    String METHOD_LOAD_PLUGIN = "loadPlugin";
    String PACKAGE_OF_GENERATE_FILE = "com.hjl.zrouter.routes";
    String FILE_TIP = "This file generate by ZRouter, Do not modify";

    ClassName CLS_ROUTER_ACQUIRER = ClassName.get("com.hjl.zrouter_api","IRouteAcquirer");
    ClassName CLS_ROUTE_META = ClassName.get(RouteMeta.class);
}

build.gradle

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])


    implementation project(':zrouter:zrouter-annotation')

    implementation 'com.squareup:javapoet:1.13.0'
    implementation 'org.apache.commons:commons-lang3:3.5'
    implementation 'org.apache.commons:commons-collections4:4.1'

    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
    compileOnly 'com.google.auto.service:auto-service-annotations:1.0-rc7'
}

核心代码逻辑还是很简单的,主要是思路,生成的代码也很简单,接下来的就是遍历包名找到这些类了

// TestJavaChannelAdapter.java
@Route(path = "/test/java")
public class TestJavaChannelAdapter implements IChannelAdapter {
   ... 
   // 省略业务逻辑
}

// 下面是对应生成的类

/**
 * This file generate by ZRouter, Do not modify
 */
public class ZRouter$$testmodulejava implements IRouteAcquirer {
  @Override
  public void loadPlugin(List<RouteMeta> data) {
    data.add(new RouteMeta(TestJavaChannelAdapter.class,"/test/java",null,-1,-2147483648));
  }
}

ZRouter-api

这里主要是获取到所有的注解类以及一些跳转逻辑的处理了,个人认为跳转逻辑根据业务需要可以直接实现,扫描包内或者Gradle插件可以直接参考ARouter,这里只是非常简单的示范了下


public interface IRouteAcquirer {
    void loadPlugin(List<RouteMeta> data);
}

public class ZRouter {
    private List<RouteMeta> routeMetaList = new ArrayList<>();
    
    public void init(Context context){

        mContext = context;
        Log.i(TAG, "start init");
        routeMetaList.clear();
        loadRouterMap();
        if (isLoadByPlugin){
            Log.i(TAG, "load route meta from plugin");
        }else {
            try {
                // 耗时操作 可用Gradle插件解决 ,可以直接拿ARouter的改一改,这里的工具类直接使用ARouter的
                Set<String> clsNameSet = ClassUtils.getFileNameByPackageName(context, "com.hjl.zrouter.routes");
                parseCls(clsNameSet);
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG,"load route meta error :",e);
            }
        }
    }
    
    private static void loadRouterMap(){
        // TODO : ASM 代码插入
    }
    
    // 解析类并添加到列表
    private void parseCls(Set<String> data) throws Exception {
        for (String cls : data){
            ((IRouteAcquirer)Class.forName(cls).getConstructor().newInstance()).loadPlugin(routeMetaList);
        }
    }

     // 获取解析好的注解类信息
    public List<RouteMeta> getRouteMetaList() {
        return routeMetaList;
    }
}            

获取到解析好的注解类信息后,就可以根据具体业务去实现相关逻辑了,demo示范一下获取单例集合渠道列表

        val supportChannelList = mutableListOf<IChannelAdapter>()
        ZRouter.getInstance().init(context)

        ZRouter.getInstance().routeMetaList.forEach {

            Log.i("ward", "get route meta:$it ")
            val newInstance = it.destination.getConstructor().newInstance() as IChannelAdapter
            supportChannelList.add(newInstance)
        }

至此,我们完成了可以用于SDK解耦的一个小型的路由注解发现框架

结尾

其实可以发现大部分好用的开源框架都是运用了注解AOP和Gradle插件ASM插入代码分别在不同的时机插入代码,减少了大量的工作,因此对于阅读第三方框架的源码和了解的他们思想来说,熟悉这两项技术的流程和运用还是很有必要的,同时也可以在自己的项目运用来解决一些需求场景

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>