android 组件化之ARouter《三》

前言

我前一篇介绍偶尔ARouter的初始化过程已经导航过程。在初始化话的时候我们知道需要遍历所有的dex 文件找到编译期间生成的类,这个过程比较耗费时间,因此ARouter 提供了一个gradle 插件在编译期间遍历所以的class 文件,找到动态生成的类,然后动态修改字节码插入部分代码,避免了程序运行期间查找,提高了效率。本篇就来研究器实现方式。

一、动态生成的代码

我们通过Route 注解了一个名为TestActivity的页面,在编译期间会动态生成一些类,关于动态生成的过程这里不做介绍,感兴趣的可以查阅android 编译时注解处理器的相关知识。

首先TestActivity 是loginmodule 下面的一个类。

生成的类一

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$loginmodule implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("test", ARouter$$Group$$test.class);
  }
}
ARouter$$Group$$moduleNmme  代表的一个module。 一个module 里面可以存在多个分组。这里就只有一个test 组。

生成的类二

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/testActivity", RouteMeta.build(RouteType.ACTIVITY, TestActivity.class, "/test/testactivity", "test", null, -1, -2147483648));
  }
}
ARouter$$Group$$groupName  表示一个组,一组内可能存在多个路由。组的名称就是注解path里面第一个/与第二个/之间的之间的部分。

加入还存在一个@Route(path = "/debug/debugActivity") 那么就会存在一个debug 组。

一、动态插入代码

下图是android 的打包过程(图片来源与网上)

这整个过程由android 插件完成。

apply plugin: 'com.android.application'

在打包的流程中,android 插件给用户提供了对应的API去访问编译之后的class文件,这个API 就是

Transform ,这一应用现在主要集中在字节码查找、代码注入等。

每个 Transform 都是一个 gradle task, 将 class 文件、本地依赖的 jar, aar 和 resource 资源统一处理。每个 Transform 在处理完之后交给下一个 Transform。如果是用户自定义的 Transform 会插在队列的最前面。

使用方式

 首先在项目的gradle 文件下面添加依赖

   classpath "com.alibaba:arouter-register:1.0.2"

在App模块下面使用插件

apply plugin: 'com.alibaba.arouter'
apply plugin: 'com.alibaba.arouter'  实际就是找到对应的插件对象然后调用这个对象的apply方法。 

具体的讲就是调用PluginLaunch 的apply 方法

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)
 //注册我们的transform任务,
            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)
        }
    }

}

这里主要是查找注册的android 插件,然后调用registerTransform 注册用户自定义的Transform。这里注册了一个RegisterTransform对象。

RegisterTransform.groovy

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

        Logger.i('Start scan register info in jar file.')

        long startTime = System.currentTimeMillis()
        boolean leftSlash = File.separator == '/'

        inputs.each { TransformInput input ->

            // scan all jars,遍历所有的依赖。
       
            input.jarInputs.each { JarInput jarInput ->
                String destName = jarInput.name
                // rename jar files
                def hexName = DigestUtils.md5Hex(jarInput.file.absolutePath)
                if (destName.endsWith(".jar")) {
                    destName = destName.substring(0, destName.length() - 4)
                }
                // input file
                File src = jarInput.file
                // output file 获取输出文件地址,在build文件夹下面
                File dest = outputProvider.getContentLocation(destName + "_" 
+ hexName, jarInput.contentTypes, jarInput.scopes, Format.JAR)

                //scan jar file to find classes
                if (ScanUtil.shouldProcessPreDexJar(src.absolutePath)) {
                    ScanUtil.scanJar(src, dest)
                }
                FileUtils.copyFile(src, dest)

            }
            // scan class files
 //这里是我们自己编写的源文件。
//DirectoryInput  包含全部的编译后的class文件,
            input.directoryInputs.each { DirectoryInput directoryInput ->
                File dest = 
outputProvider.getContentLocation(directoryInput.name, 
directoryInput.contentTypes, directoryInput.scopes, 
Format.DIRECTORY)
                String root = directoryInput.file.absolutePath
                if (!root.endsWith(File.separator))
                    root += File.separator
 //遍历全部的class文件
                directoryInput.file.eachFileRecurse { File file ->
                    def path = file.absolutePath.replace(root, '')
                    if (!leftSlash) {
                        path = path.replaceAll("\\\\", "/")
                    }

                //判断是不是com/alibaba/android/arouter/routes/包下面的类文件
                    if(file.isFile() && ScanUtil.shouldProcessClass(path)){
//扫描文件
                        ScanUtil.scanClass(file)
                    }
                }

                // copy to dest
                FileUtils.copyDirectory(directoryInput.file, dest)
            }
        }

//fileContainsInitClass 是com.alibaba:arouter-api 里面的LogisticsCenter类

        if (fileContainsInitClass) {
            registerList.each { ext ->
                if (ext.classList.isEmpty()) {
                } 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)扫描这个jar 文件里面的class文件。scanJar 主要是找到处于com/alibaba/android/arouter/routes/

包下面的类并查看该类时候实现了IRouteRoot,IInterceptorGroup或者IProviderGroup接口,如果实现了某个接口就将该文件保存在对应的ScanSetting内

除此之外 ,scanJar还会找到com/alibaba/android/arouter/core/ 包下面的LogisticsCenter类。LogisticsCenter 就是需要插入代码的类。

第二部分是扫描模块下面用户自己的类,查找处于com/alibaba/android/arouter/routes/包下面的类,同时扫描类,将类添加到对应的ScanSetting对象内。

最终调用RegisterCodeGenerator.insertInitCodeTo(ext) 去处理这些类。

其参数ext 在PluginLaunch 里面创建的ScanSetting,每一个ScanSetting内部的classList列表保存的就是实现对应接口的类。如ScanSetting('IRouteRoot')  里面保存着实现了IRouteRoot 接口的类。

RegisterCodeGenerator.groovy
、、入口函数
    static void insertInitCodeTo(ScanSetting registerSetting) {
        if (registerSetting != null && !registerSetting.classList.isEmpty()) {
            RegisterCodeGenerator processor = new 
  RegisterCodeGenerator(registerSetting)
            File file = RegisterTransform.fileContainsInitClass
            if (file.getName().endsWith('.jar'))
                processor.insertInitCodeIntoJarFile(file)
        }
    }
 RegisterTransform.fileContainsInitClass  依赖'com.alibaba:arouter-api:1.4.0'对应的jar 文件。
    private File insertInitCodeIntoJarFile(File jarFile) {
        if (jarFile) {
            def optJar = new File(jarFile.getParent(), jarFile.name + ".opt")
            if (optJar.exists())
                optJar.delete()
            //用来拷贝jarFile,
            def file = new JarFile(jarFile)
            Enumeration enumeration = file.entries()
            JarOutputStream jarOutputStream = new JarOutputStream(new
                    FileOutputStream(optJar))

            //遍历jar 包下的文件
            while (enumeration.hasMoreElements()) {
                JarEntry jarEntry = (JarEntry) enumeration.nextElement()
                String entryName = jarEntry.getName()
                ZipEntry zipEntry = new ZipEntry(entryName)
                InputStream inputStream = file.getInputStream(jarEntry)
                jarOutputStream.putNextEntry(zipEntry)
                 //找到LogisticsCenter 类
                if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {
                    Logger.i('Insert init code to class >> ' + entryName)
                    //织入字节吗
                    def bytes = referHackWhenInit(inputStream)
                    jarOutputStream.write(bytes)
                } else {
                    //不需要插入字节码的文件直接复制一份到输出文件
                    jarOutputStream.write(IOUtils.toByteArray(inputStream))
                }
                inputStream.close()
                jarOutputStream.closeEntry()
            }
            jarOutputStream.close()
            file.close()
         //删除老的jar文件
            if (jarFile.exists()) {
                jarFile.delete()
            }
        //使用我们修改之后的jar文件替换原先的jar文件,
        //这样最终打包的Jar文件就是这个被修改了的Jar文件。
            optJar.renameTo(jarFile)
        }
        return jarFile
    }

这里主要是找到LogisticsCenter 类,然后往这类类里面插入部分代码,插入代码之后将就的Jar 包删除,使用包含插入代码的新的Jar 包。

    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()
    }

这里使用了ASM 来插入代码。

ASM 是一个字节码操作库,它可以直接修改已经存在的 class 文件或者生成 class 文件。 ASM 提供了一系列便捷的功能来操作字节码内容,与其它字节码的操作框架相比(例如 AspectJ),ASM 更加偏向于底层,直接操作字节码,在设计上更小、更快,性能上更好,而且几乎可以修改任意字节码。

lass MyClassVisitor extends ClassVisitor {

        MyClassVisitor(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)
        }
    //类里面定义的每一个方法的地方都会被调用这个代码
        @Override
        MethodVisitor visitMethod(int access, String name, String desc,
                                  String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, desc, 
signature, exceptions)
            //generate code into this method
            //是不是loadRouterMap方法 
            if (name == ScanSetting.GENERATE_TO_METHOD_NAME) {
                mv = new RouteMethodVisitor(Opcodes.ASM5, mv)
            }
            return mv
        }
    }
ClassVisitor 用于访问类,对于类里面定义的方法 都会被传给 visitMethod 来处理。

对于其他的方法都忽略,直到找到需要loadRouterMap 方法。

    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)
        }
    }
extension 是ScanSetting,extension.classList保存扫描时候找到的类。

mv.visitLdcInsn(name) 相当于设置方法的参数,name 实际是一个类的全名。
mv.visitMethodInsn 就是调用LogisticsCenter的静态方法register。

LogisticsCenter.java

    private static void register(String className) {
        if (!TextUtils.isEmpty(className)) {
            try {
                Class<?> clazz = Class.forName(className);
                Object obj = clazz.getConstructor().newInstance();
                if (obj instanceof IRouteRoot) {
                    registerRouteRoot((IRouteRoot) obj);
                } else if (obj instanceof IProviderGroup) {
                    registerProvider((IProviderGroup) obj);
                } else if (obj instanceof IInterceptorGroup) {
                    registerInterceptor((IInterceptorGroup) obj);
                } else {
                    logger.info(TAG, "register failed, class name: " + className
                            + " should implements one of IRouteRoot/IProviderGroup/IInterceptorGroup.");
                }
            } catch (Exception e) {
                logger.error(TAG,"register class error:" + className);
            }
        }
    }

register  主要是通过反射创建对象,然后根据类的类型分别处理。

总结

至此,关于ARouter的动态插入代码的实现这里就介绍完了,这里是将查找相关类的过程放到了编译中,避免在运行期间查找进而浪费时间。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值