Android组件自动注册方案
2018-01-09 12:14 阅读(517)
作者简介


本篇来自 lucky_billy 的投稿,分享了android 组件开发中一种更高效的组件自动注册方案,一起来看看!

lucky_billy 的博客地址:

http://blog.csdn.net/cdecde111


前言


组件自动注册方案: 在编译时,扫描即将打包到 apk 中的所有类,将所有组件类收集起来,通过修改字节码的方式生成注册代码到组件管理类中,从而实现编译时自动注册的功能,不用再关心项目中有哪些组件类了。 

特点:不需要注解,不会增加新的类;性能高,不需要反射,运行时直接调用组件的构造方法;能扫描到所有类,不会出现遗漏;支持分级按需加载功能的实现。

最近在公司做 android 组件化开发框架的搭建,采用组件总线的方式进行通信:提供一个基础库,各组件( IComponent 接口的实现类)都注册到组件管理类(组件总线:ComponentManager)中,组件之间在同一个 app 内时,通过 ComponentManager 转发调用请求来实现通信(不同 app 之间的通信方式不是本文的主题,暂且略去)。但在实现过程中遇到了一个问题:

如何将不同 module 中的组件类自动注册到 ComponentManager 中?


自动注册到 ComponentManager


目前市面上比较常用的解决方案是使用 annotationProcessor:通过编译时注解动态生成组件映射表代码的方式来实现。但尝试过后发现有问题,因为编译时注解的特性只在源码编译时生效,无法扫描到 aar 包里的注解( project 依赖、maven 依赖均无效),也就是说必须每个 module 编译时生成自己的代码,然后要想办法将这些分散在各aar种的类找出来进行集中注册。

ARouter 的解决方案是:

运行时通过读取所有 dex 文件遍历每个 entry 查找指定包内的所有类名,然后反射获取类对象。这种效率看起来并不高。

ActivityRouter 的解决方案是( demo 中有2个组件名为 ’app’ 和 ’sdk’):

这种方式用一个 RouterInit 类组合了所有 module 中的路由映射表类,运行时效率比扫描所有 dex 文件的方式要高,但需要额外在主工程代码中维护一个组件名称列表注解: @Modules({“app”, “sdk”})

有没有一种方式可以更高效地管理这个列表呢?


 高效地管理映射表


联想到之前用 ASM 框架自动生成代码的方式做了个 AndAop 插件用于自动插入指定代码到任意类的任意方法中,于是写了一个自动生成注册组件的 gradle 插件。 

大致思路是:在编译时,扫描所有类,将符合条件的类收集起来,并通过修改字节码生成注册代码到指定的管理类中,从而实现编译时自动注册的功能,不用再关心项目中有哪些组件类了。不会增加新的 class,不需要反射,运行时直接调用组件的构造方法。

性能方面:由于使用效率更高的 ASM 框架来进行字节码分析和修改,并过滤掉android/support 包中的所有类(还支持设置自定义的扫描范围),经公司项目实测,未代码混淆前所有 dex 文件总计12MB 左右,扫描及代码插入的总耗时在2s-3s之间,相对于整个apk 打包所花3分钟左右的时间来说可以忽略不计(运行环境:MacBookPro 15 吋高配 Mid 2015)。

开发完成后,考虑到这个功能的通用性,于是升级组件扫描注册插件为通用的自动注册插件 AutoRegister,支持配置多种类型的扫描注册,使用方式见 github 中的 README 文档。此插件现已用到组件化开发框架 CC 中

升级后,AutoRegister 插件的完整功能描述是:

在编译期扫描即将打包到 apk 中的所有类,并将指定接口的实现类(或指定类的子类)通过字节码操作自动注册到对应的管理类中。尤其适用于命令模式或策略模式下的映射表生成。

在组件化开发框架中,可有助于实现分级按需加载的功能:

我们看下具体的实现过程。


准备工作


首先要知道如何使用 Android Studio 开发 Gradle 插件:

http://blog.csdn.net/sbsujjbcy/article/details/50782830

了解 TransformAPI:Transform API 是从 Gradle 1.5.0版本之后提供的,它允许第三方在打包 Dex 文件之前的编译过程中修改 java 字节码(自定义插件注册的 transform 会在 ProguardTransform 和 DexTransform 之前执行,所以自动注册的类不需要考虑混淆的情况)。参考文章有:Android 热修复使用 Gradle Plugin1.5改造 Nuwa 插件(主要看前半部分关于TransformAPI的介绍,Nuwa 相关的内容可先忽略):

http://blog.csdn.net/sbsujjbcy/article/details/50782830

字节码修改框架(相比于 Javassist 框架 ASM 较难上手,但性能更高,但相学习难度阻挡不了我们对性能的追求): 

ASM 英文文档 :

 http://download.forge.objectweb.org/asm/asm4-guide.pdf

ASM API 文档 :

http://asm.ow2.org/asm50/javadoc/user/index.html

Android 热修复方案 Tinker (七) 插桩实现(主要看关于 ASM 使用的介绍及与 transformAPI的结合):

http://blog.csdn.net/l2show/article/details/54846682


构建插件工程


按照如何使用 Android Studio 开发 Gradle 插件文章中的方法创建好插件工程并发布到本地maven 仓库(我是放在工程根目录下的一个文件夹中),这样我们就可以在本地快速调试了。

build.gradle 文件的部分内容如下:

apply plugin: 'groovy' 
apply plugin: 'maven' 

dependencies { 
    compile gradleApi() 
    compile localGroovy() } 

repositories { 
    mavenCentral() 
} 
dependencies { 
    compile 'com.android.tools.build:gradle:2.2.0' } 


//加载本地maven私服配置(在工程根目录中的local.properties文件中进行配置) 
Properties properties = new Properties() 
properties.load(project.rootProject.file('local.properties').newDataInputStream()) 
def artifactory_user = properties.getProperty("artifactory_user") 
def artifactory_password = properties.getProperty("artifactory_password") 
def artifactory_contextUrl = properties.getProperty("artifactory_contextUrl") 
def artifactory_snapshot_repoKey = properties.getProperty("artifactory_snapshot_repoKey") 
def artifactory_release_repoKey = properties.getProperty("artifactory_release_repoKey") 
def maven_type_snapshot = true // 项目引用的版本号,比如compile 'com.yanzhenjie:andserver:1.0.1'中的1.0.1就是这里配置的。 
def artifact_version='1.0.1' // 唯一包名,比如compile 'com.yanzhenjie:andserver:1.0.1'中的com.yanzhenjie就是这里配置的。 
def artifact_group = 'com.billy.android' 
def artifact_id = 'autoregister' 
def debug_flag = true //true: 发布到本地maven仓库, false: 发布到maven私服 
task sourcesJar(type: Jar) { 
    from project.file('src/main/groovy') 
    classifier = 'sources' } 

artifacts { 
    archives sourcesJar 
} 
uploadArchives { 
    repositories { 
        mavenDeployer { 
            //deploy到maven仓库 
            if (debug_flag) { 
                repository(url: uri('../repo-local')) //deploy到本地仓库 
            } else {//deploy到maven私服中 
                def repoKey = maven_type_snapshot ? artifactory_snapshot_repoKey : artifactory_release_repoKey 
                repository(url: "${artifactory_contextUrl}/${repoKey}") { 
                    authentication(userName: artifactory_user, password: artifactory_password) 
                } 
            } 

            pom.groupId = artifact_group 
            pom.artifactId = artifact_id 
            pom.version = artifact_version + (maven_type_snapshot ? '-SNAPSHOT' : '') 

            pom.project { 
                licenses { 
                    license { 
                        name 'The Apache Software License, Version 2.0' 
                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 
                    } 
                } 
            } 
        } 
    } 
}

根目录的 build.gradle 文件中要添加本地仓库的地址及 dependencies:

buildscript { 

    repositories { 
        maven{ url rootProject.file("repo-local") } 
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } 
        google() 
        jcenter() 
    } 
    dependencies { 
        classpath 'com.android.tools.build:gradle:3.0.0-beta6' 
        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' 
        classpath 'com.billy.android:autoregister:1.0.1' 
    } 
}

在 Transform 类的 transform 方法中添加类扫描相关的代码:

// 遍历输入文件 
    inputs.each { TransformInput input -> 

    // 遍历jar 
    input.jarInputs.each { JarInput jarInput -> 
        String destName = jarInput.name 
        // 重名名输出文件,因为可能同名,会覆盖 
        def hexName = DigestUtils.md5Hex(jarInput.file.absolutePath) 
        if (destName.endsWith(".jar")) { 
            destName = destName.substring(0, destName.length() - 4) 
        } 
        // 获得输入文件 
        File src = jarInput.file 
        // 获得输出文件 
        File dest = outputProvider.getContentLocation(destName + "_" + hexName, jarInput.contentTypes, jarInput.scopes, Format.JAR) 

        //遍历jar的字节码类文件,找到需要自动注册的component 
        if (CodeScanProcessor.shouldProcessPreDexJar(src.absolutePath)) { 
            CodeScanProcessor.scanJar(src, dest) 
        } 
        FileUtils.copyFile(src, dest) 

        project.logger.info "Copying\t${src.absolutePath} \nto\t\t${dest.absolutePath}" 
    } 
    // 遍历目录 
    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 
        //遍历目录下的每个文件 
        directoryInput.file.eachFileRecurse { File file -> 
            def path = file.absolutePath.replace(root, '') 
            if(file.isFile()){ 
                CodeScanProcessor.checkInitClass(path, new File(dest.absolutePath + File.separator + path)) 
                if (CodeScanProcessor.shouldProcessClass(path)) { 
                    CodeScanProcessor.scanClass(file) 
                } 
            } 
        } 
        project.logger.info "Copying\t${directoryInput.file.absolutePath} \nto\t\t${dest.absolutePath}" 
        // 处理完后拷到目标文件 
        FileUtils.copyDirectory(directoryInput.file, dest) 
    } 
}

CodeScanProcessor 是一个工具类,其中 CodeScanProcessor.scanJar(src, dest) 和CodeScanProcessor.scanClass(file) 分别是用来扫描 jar 包和 class 文件的 
扫描的原理是利用 ASM 的 ClassVisitor 来查看每个类的父类类名及所实现的接口名称,与配置的信息进行比较,如果符合我们的过滤条件,则记录下来,在全部扫描完成后将调用这些类的无参构造方法进行注册:

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) 
        RegisterTransform.infoList.each { ext -> 
            if (shouldProcessThisClassForRegister(ext, name)) { 
                if (superName != 'java/lang/Object' && !ext.superClassNames.isEmpty()) { 
                    for (int i = 0; i < ext.superClassNames.size(); i++) { 
                        if (ext.superClassNames.get(i) == superName) { 
                            ext.classList.add(name) 
                            return 
                        } 
                    } 
                } 
                if (ext.interfaceName && interfaces != null) { 
                    interfaces.each { itName -> 
                        if (itName == ext.interfaceName) { 
                            ext.classList.add(name) 
                        } 
                    } 
                } 
            } 
        } 

    } 
}

记录目标类所在的文件,因为我们接下来要修改其字节码,将注册代码插入进去:

static void checkInitClass(String entryName, File file) { 
     if (entryName == null || !entryName.endsWith(".class")) 
         return 
     entryName = entryName.substring(0, entryName.lastIndexOf('.')) 
     RegisterTransform.infoList.each { ext -> 
         if (ext.initClassName == entryName) 
             ext.fileContainsInitClass = file 
     } 
 }

扫描完成后,开始修改目标类的字节码(使用 ASM 的 MethodVisitor 来修改目标类指定方法,若未指定则默认为 static 块,即 <clinit> 方法),生成的代码是直接调用扫描到的类的无参构造方法,并非通过反射。

class 文件: 直接修改此字节码文件(其实是重新生成一个 class 文件并替换掉原来的文件)

jar 文件:复制此 jar 文件,找到 jar 包中目标类所对应的 JarEntry,修改其字节码,然后替换原来的jar文件

class CodeInsertProcessor { 
    RegisterInfo extension 

    private CodeInsertProcessor(RegisterInfo extension) { 
        this.extension = extension 
    } 

    static void insertInitCodeTo(RegisterInfo extension) { 
        if (extension != null && !extension.classList.isEmpty()) { 
            CodeInsertProcessor processor = new CodeInsertProcessor(extension) 
            File file = extension.fileContainsInitClass 
            if (file.getName().endsWith('.jar')) 
                processor.insertInitCodeIntoJarFile(file) 
            else 
                processor.insertInitCodeIntoClassFile(file) 
        } 
    } 

    //处理jar包中的class代码注入 
    private File insertInitCodeIntoJarFile(File jarFile) { 
        if (jarFile) { 
            def optJar = new File(jarFile.getParent(), jarFile.name + ".opt") 
            if (optJar.exists()) 
                optJar.delete() 
            def file = new JarFile(jarFile) 
            Enumeration enumeration = file.entries() 
            JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(optJar)) 

            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) 
                if (isInitClass(entryName)) { 
                    println('codeInsertToClassName:' + entryName) 
                    def bytes = referHackWhenInit(inputStream) 
                    jarOutputStream.write(bytes) 
                } else { 
                    jarOutputStream.write(IOUtils.toByteArray(inputStream)) 
                } 
                inputStream.close() 
                jarOutputStream.closeEntry() 
            } 
            jarOutputStream.close() 
            file.close() 

            if (jarFile.exists()) { 
                jarFile.delete() 
            } 
            optJar.renameTo(jarFile) 
        } 
        return jarFile 
    } 

    boolean isInitClass(String entryName) { 
        if (entryName == null || !entryName.endsWith(".class")) 
            return false 
        if (extension.initClassName) { 
            entryName = entryName.substring(0, entryName.lastIndexOf('.')) 
            return extension.initClassName == entryName 
        } 
        return false 
    } 
    /** 
     * 处理class的注入 
     * @param file class文件 
     * @return 修改后的字节码文件内容 
     */ 
    private byte[] insertInitCodeIntoClassFile(File file) { 
        def optClass = new File(file.getParent(), file.name + ".opt") 

        FileInputStream inputStream = new FileInputStream(file) 
        FileOutputStream outputStream = new FileOutputStream(optClass) 

        def bytes = referHackWhenInit(inputStream) 
        outputStream.write(bytes) 
        inputStream.close() 
        outputStream.close() 
        if (file.exists()) { 
            file.delete() 
        } 
        optClass.renameTo(file) 
        return bytes 
    } 


    //refer hack class when object init 
    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() 
    } 

    class 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) 
            if (name == extension.initMethodName) { //注入代码到指定的方法之中 
                boolean _static = (access & Opcodes.ACC_STATIC) > 0 
                mv = new MyMethodVisitor(Opcodes.ASM5, mv, _static) 
            } 
            return mv 
        } 
    } 

    class MyMethodVisitor extends MethodVisitor { 
        boolean _static; 

        MyMethodVisitor(int api, MethodVisitor mv, boolean _static) { 
            super(api, mv) 
            this._static = _static; 
        } 

        @Override 
        void visitInsn(int opcode) { 
            if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) { 
                extension.classList.each { name -> 
                    if (!_static) { 
                        //加载this 
                        mv.visitVarInsn(Opcodes.ALOAD, 0) 
                    } 
                    //用无参构造方法创建一个组件实例 
                    mv.visitTypeInsn(Opcodes.NEW, name) 
                    mv.visitInsn(Opcodes.DUP) 
                    mv.visitMethodInsn(Opcodes.INVOKESPECIAL, name, "<init>", "()V", false) 
                    //调用注册方法将组件实例注册到组件库中 
                    if (_static) { 
                        mv.visitMethodInsn(Opcodes.INVOKESTATIC 
                                , extension.registerClassName 
                                , extension.registerMethodName 
                                , "(L${extension.interfaceName};)V" 
                                , false) 
                    } else { 
                        mv.visitMethodInsn(Opcodes.INVOKESPECIAL 
                                , extension.registerClassName 
                                , extension.registerMethodName 
                                , "(L${extension.interfaceName};)V" 
                                , false) 
                    } 
                } 
            } 
            super.visitInsn(opcode) 
        } 
        @Override 
        void visitMaxs(int maxStack, int maxLocals) { 
            super.visitMaxs(maxStack + 4, maxLocals) 
        } 
    } 
}

接收扩展参数,获取需要扫描类的特征及需要插入的代码

找了很久没找到 gradle 插件接收自定义对象数组扩展参数的方法,于是退一步改用List<Map> 接收后再进行转换的方式来实现,以此来接收多个扫描任务的扩展参数

import org.gradle.api.Project 
/** 
 * aop的配置信息 
 * @author billy.qi 
 * @since 17/3/28 11:48 
 */ 
class AutoRegisterConfig { 

    public ArrayList<Map<String, Object>> registerInfo = [] 

    ArrayList<RegisterInfo> list = new ArrayList<>() 

    Project project 

    AutoRegisterConfig(){} 

    void convertConfig() { 
        registerInfo.each { map -> 
            RegisterInfo info = new RegisterInfo() 
            info.interfaceName = map.get('scanInterface') 
            def superClasses = map.get('scanSuperClasses') 
            if (!superClasses) { 
                superClasses = new ArrayList<String>() 
            } else if (superClasses instanceof String) { 
                ArrayList<String> superList = new ArrayList<>() 
                superList.add(superClasses) 
                superClasses = superList 
            } 
            info.superClassNames = superClasses 
            info.initClassName = map.get('codeInsertToClassName') //代码注入的类 
            info.initMethodName = map.get('codeInsertToMethodName') //代码注入的方法(默认为static块) 
            info.registerMethodName = map.get('registerMethodName') //生成的代码所调用的方法 
            info.registerClassName = map.get('registerClassName') //注册方法所在的类 
            info.include = map.get('include') 
            info.exclude = map.get('exclude') 
            info.init() 
            if (info.validate()) 
                list.add(info) 
            else { 
                project.logger.error('auto register config error: scanInterface, codeInsertToClassName and registerMethodName should not be null\n' + info.toString()) 
            } 

        } 
    } 
}
import java.util.regex.Pattern 
/** 
 * aop的配置信息 
 * @author billy.qi 
 * @since 17/3/28 11:48 
 */ 
class RegisterInfo { 
    static final DEFAULT_EXCLUDE = [ 
            '.*/R(\\$[^/]*)?' 
            , '.*/BuildConfig$' 
    ] 
    //以下是可配置参数 
    String interfaceName = '' 
    ArrayList<String> superClassNames = [] 
    String initClassName = '' 
    String initMethodName = '' 
    String registerClassName = '' 
    String registerMethodName = '' 
    ArrayList<String> include = [] 
    ArrayList<String> exclude = [] 

    //以下不是可配置参数 
    ArrayList<Pattern> includePatterns = [] 
    ArrayList<Pattern> excludePatterns = [] 
    File fileContainsInitClass //initClassName的class文件或含有initClassName类的jar文件 
    ArrayList<String> classList = new ArrayList<>() 

    RegisterInfo(){} 

    boolean validate() { 
        return interfaceName && registerClassName && registerMethodName 
    } 

    //用于在console中输出日志 
    @Override 
    String toString() { 
        StringBuilder sb = new StringBuilder('{') 
        sb.append('\n\t').append('scanInterface').append('\t\t\t=\t').append(interfaceName) 
        sb.append('\n\t').append('scanSuperClasses').append('\t\t=\t[') 
        for (int i = 0; i < superClassNames.size(); i++) { 
            if (i > 0) sb.append(',') 
            sb.append(' \'').append(superClassNames.get(i)).append('\'') 
        } 
        sb.append(' ]') 
        sb.append('\n\t').append('codeInsertToClassName').append('\t=\t').append(initClassName) 
        sb.append('\n\t').append('codeInsertToMethodName').append('\t=\t').append(initMethodName) 
        sb.append('\n\t').append('registerMethodName').append('\t\t=\tpublic static void ') 
                .append(registerClassName).append('.').append(registerMethodName) 
        sb.append('\n\t').append('include').append(' = [') 
        include.each { i -> 
            sb.append('\n\t\t\'').append(i).append('\'') 
        } 
        sb.append('\n\t]') 
        sb.append('\n\t').append('exclude').append(' = [') 
        exclude.each { i -> 
            sb.append('\n\t\t\'').append(i).append('\'') 
        } 
        sb.append('\n\t]\n}') 
        return sb.toString() 
    } 

    void init() { 
        if (include == null) include = new ArrayList<>() 
        if (include.empty) include.add(".*") //如果没有设置则默认为include所有 
        if (exclude == null) exclude = new ArrayList<>() 
        if (!registerClassName) 
            registerClassName = initClassName 

        //将interfaceName中的'.'转换为'/' 
        if (interfaceName) 
            interfaceName = convertDotToSlash(interfaceName) 
        //将superClassName中的'.'转换为'/' 
        if (superClassNames == null) superClassNames = new ArrayList<>() 
        for (int i = 0; i < superClassNames.size(); i++) { 
            def superClass = convertDotToSlash(superClassNames.get(i)) 
            superClassNames.set(i, superClass) 
            if (!exclude.contains(superClass)) 
                exclude.add(superClass) 
        } 
        //interfaceName添加到排除项 
        if (!exclude.contains(interfaceName)) 
            exclude.add(interfaceName) 
        //注册和初始化的方法所在的类默认为同一个类 
        initClassName = convertDotToSlash(initClassName) 
        //默认插入到static块中 
        if (!initMethodName) 
            initMethodName = "<clinit>" 
        registerClassName = convertDotToSlash(registerClassName) 
        //添加默认的排除项 
        DEFAULT_EXCLUDE.each { e -> 
            if (!exclude.contains(e)) 
                exclude.add(e) 
        } 
        initPattern(include, includePatterns) 
        initPattern(exclude, excludePatterns) 
    } 

    private static String convertDotToSlash(String str) { 
        return str ? str.replaceAll('\\.', '/').intern() : str 
    } 

    private static void initPattern(ArrayList<String> list, ArrayList<Pattern> patterns) { 
        list.each { s -> 
            patterns.add(Pattern.compile(s)) 
        } 
    } 
}
在 Application 中配置扩展参数


application 中配置自动注册插件所需的相关扩展参数,在主 app module 的 build.gradle 文件中添加扩展参数,示例如下:

//auto register extension 
// 功能介绍: 
//  在编译期扫描将打到apk包中的所有类 
//  将 scanInterface的实现类 或 scanSuperClasses的子类 
//  并在 codeInsertToClassName 类的 codeInsertToMethodName 方法中生成如下代码: 
//  codeInsertToClassName.registerMethodName(scanInterface) 
// 要点: 
//  1. codeInsertToMethodName 若未指定,则默认为static块 
//  2. codeInsertToMethodName 与 registerMethodName 需要同为static或非static 
// 自动生成的代码示例: /* 
  在com.billy.app_lib_interface.CategoryManager.class文件中 
  static 
  { 
    register(new CategoryA()); //scanInterface的实现类 
    register(new CategoryB()); //scanSuperClass的子类 
  } 
 */ apply plugin: 'auto-register' autoregister { 
    registerInfo = [ 
        [ 
            'scanInterface'             : 'com.billy.app_lib_interface.ICategory' 
            // scanSuperClasses 会自动被加入到exclude中,下面的exclude只作为演示,其实可以不用手动添加 
            , 'scanSuperClasses'        : ['com.billy.android.autoregister.demo.BaseCategory'] 
            , 'codeInsertToClassName'   : 'com.billy.app_lib_interface.CategoryManager' 
            //未指定codeInsertToMethodName,默认插入到static块中,故此处register必须为static方法 
            , 'registerMethodName'      : 'register' // 
            , 'exclude'                 : [ 
                //排除的类,支持正则表达式(包分隔符需要用/表示,不能用.) 
                'com.billy.android.autoregister.demo.BaseCategory'.replaceAll('\\.', '/') //排除这个基类 
            ] 
        ], 
        [ 
            'scanInterface'             : 'com.billy.app_lib.IOther' 
            , 'codeInsertToClassName'   : 'com.billy.app_lib.OtherManager' 
            , 'codeInsertToMethodName'  : 'init' //非static方法 
            , 'registerMethodName'      : 'registerOther' //非static方法 
        ] 
    ] 
}
总结


本文介绍了 AutoRegister 插件的功能及其在组件化开发框架中的应用。重点对其原理做了说明,主要介绍了此插件的实现过程,其中涉及到的技术点有 TransformAPI、ASM、groovy 相关语法、gradle 机制。

本插件的所有代码及其用法 demo 已开源到 github上,欢迎 fork、start

接下来就用这个插件来为我们自动管理注册表吧!

我的GitHub:

https://github.com/luckybilly/AutoRegister