[0] AutoRegister框架分析

1,762 阅读4分钟

AutoRegister 是一个使用字节码插桩技术实现的通用注册的框架,这个框架对使用场景有一些约定。

AutoRegister.png

使用配置

AutoRegister项目app模块中配置代码如下:

apply plugin: 'auto-register'
project.ext.registerInfoList = [
    [
        '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 {
    registerInfo = registerInfoList
    cacheEnabled = true
}

配置说明

上面配置代码中,AutoRegister通过autoregister闭包结构,映射参数配置。registerInfoList是一个配置列表,每个配置项都有固定的结构,插件通过结构中指定的参数,下面是配置数据在插件中对应的解析代码,可以看到八个配置:

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

    if (cacheEnabled) {
        checkRegisterInfo()
    } else {
        deleteFile(AutoRegisterHelper.getRegisterInfoCacheFile(project))
        deleteFile(AutoRegisterHelper.getRegisterCacheFile(project))
    }
}
被收集的类配置:接口、基类及范围规定

scanInterface、scanSuperClasses、include、exclude,下面内容来自框架文档

> scanInterface : (必须)字符串,接口名(完整类名),所有直接实现此接口的类将会被收集
> scanSuperClasses : 字符串或字符串数组,类名(完整类名),所有直接继承此类的子类将会被收集
> include : 数组,需要扫描的类(正则表达式,包分隔符用/代替,如: com/billy/android/.*),默认为所有的类
> exclude : 数组,不需要扫描的类(正则表达式,包分隔符用/代替,如: com/billy/android/.*),列表项(正则)匹配到最终的实现类,指定基类/接口不会对其子类/实现类生效

如下方法,确定扫描类列表,代码有删减:

class ScanClassVisitor extends ClassVisitor {
    
    void visit(int version, int access, String name, String signature,
               String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces)
        //抽象类、接口、非public等类无法调用其无参构造方法
        if (is(access, Opcodes.ACC_ABSTRACT)
                || is(access, Opcodes.ACC_INTERFACE)
                || !is(access, Opcodes.ACC_PUBLIC)
        ) {
            return
        }
        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) {
                            //  println("superClassNames--------"+name)
                            ext.classList.add(name) //需要把对象注入到管理类 就是fileContainsInitClass
                            found = true
                            addToCacheMap(superName, name, filePath)
                            return
                        }
                    }
                }
                if (ext.interfaceName && interfaces != null) {
                    interfaces.each { itName ->
                        if (itName == ext.interfaceName) {
                            ext.classList.add(name)//需要把对象注入到管理类  就是fileContainsInitClass
                            addToCacheMap(itName, name, filePath)
                            found = true
                        }
                    }
                }
            }
        }
    }
}

// include和exclude的处理,exclude的优先级高于include
private static boolean shouldProcessThisClassForRegister(RegisterInfo info, String entryName) {
    if (info != null) {
        def list = info.includePatterns
        if (list) {
            def exlist = info.excludePatterns
            Pattern pattern, p
            for (int i = 0; i < list.size(); i++) {
                pattern = (Pattern)list.get(i)
                if (pattern.matcher(entryName).matches()) {
                    if (exlist) {
                        for (int j = 0; j < exlist.size(); j++) {
                            p = (Pattern)exlist.get(j)
                            if (p.matcher(entryName).matches())
                                return false
                        }
                    }
                    return true
                }
            }
        }
    }
    return false
}

管理和注册方法设置
> codeInsertToClassName : (必须)字符串,类名(完整类名),通过编译时生成代码的方式将收集到的类注册到此类的codeInsertToMethodName方法中
> codeInsertToMethodName: 字符串,方法名,注册代码将插入到此方法中。若未指定,则默认为static块,(方法名为:)
> registerClassName: 无
> registerMethodName : (必须)字符串,方法名,静态方法,方法的参数为 scanInterface

参数分为两组:codeInsertToClassName / codeInsertToMethodNameregisterMethodName / registerClassName

  1. 第一组是管理类和管理方法:对应到示例中是 CategoryManager#initCategory
  2. 第二组是注册类和注册方法:对应到示例中是 CategoryManager#register

示例中没有明确指定registerClassName,会默认赋值initClassName,也就是 codeInsertToClassName。所以,框架的设置中允许把管理类和注册类分开来写的。

// 代码有删减
void convertConfig() {
    registerInfo.each { map ->
        RegisterInfo info = new RegisterInfo()
        info.initClassName = map.get('codeInsertToClassName')    //代码注入的类
        info.initMethodName = map.get('codeInsertToMethodName')  //代码注入的方法(默认为static块)
        info.registerMethodName = map.get('registerMethodName')  //生成的代码所调用的方法
        info.registerClassName = map.get('registerClassName')    //注册方法所在的类
        info.init()
    }
}

void init() {
    if (!registerClassName) {
        registerClassName = initClassName
    }
}

注释:从示例中可以看出 codeInsertToMethodNameregisterMethodName 需要保持一致,同时为静态方法或者同时非静态方法;如果没有指定codeInsertToMethodName,默认是静态代码块,视为静态方法,此时 registerMethodName 需指定静态方法。

对于四个参数的解释,参考框架中的实现类,略有修改。从下面的代码可以看出,AutoRegister的主要作用是从不同的模块收集子类和实现类(CategoryA/CategoryB),生成注册函数(register)调用代码,插入到初始化入口函数 initCategory()static 代码块。在此基础上,APP初始化时只需要调用 initCategory() 或者自动执行 static 代码块,就可以做到添加一系列实例对象到初始化列表(CATEGORIES)中,完成注册工作。

// codeInsertToClassName | registerClassName
public class CategoryManager {
    private static final HashMap<String, ICategory> CATEGORIES = new HashMap<>();

    // 不指定 codeInsertToMethodName 时生效
    static {
        register(new CategoryA()); //scanInterface的实现类
        register(new CategoryB()); //scanSuperClass的子类
    }

    // codeInsertToMethodName
    public static void initCategory() {
        register(new CategoryA()); //scanInterface的实现类
        register(new CategoryB()); //scanSuperClass的子类
    }

    // registerMethodName
    static void register(ICategory category) {
        if (category != null) {
            CATEGORIES.put(category.getName(), category);
        }
    }

    public static Set<String> getCategoryNames() {
        return CATEGORIES.keySet();
    }
}