AutoRegister:一種更高效的組件自動註冊方案(android組件化開發)

*本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈html

摘要:java

在編譯時,掃描即將打包到apk中的全部類,將全部組件類收集起來,經過修改字節碼的方式生成註冊代碼到組件管理類中,從而實現編譯時自動註冊的功能,不用再關心項目中有哪些組件類了。 特色:不須要註解,不會增長新的類;性能高,不須要反射,運行時直接調用組件的構造方法;能掃描到全部類,不會出現遺漏;支持分級按需加載功能的實現。android

前言


最近在公司作android組件化開發框架的搭建,採用組件總線的方式進行通訊:提供一個基礎庫,各組件(IComponent接口的實現類)都註冊到組件管理類(組件總線:ComponentManager)中,組件之間在同一個app內時,經過ComponentManager轉發調用請求來實現通訊(不一樣app之間的通訊方式不是本文的主題,暫且略去)。但在實現過程當中遇到了一個問題:git

如何將不一樣module中的組件類自動註冊到ComponentManager中github

目前市面上比較經常使用的解決方案是使用annotationProcessor:經過編譯時註解動態生成組件映射表代碼的方式來實現。但嘗試事後發現有問題,由於編譯時註解的特性只在源碼編譯時生效,沒法掃描到aar包裏的註解(project依賴、maven依賴均無效),也就是說必須每一個module編譯時生成本身的代碼,而後要想辦法將這些分散在各aar種的類找出來進行集中註冊。web

ARouter的解決方案是:正則表達式

  • 每一個module都生成本身的java類,這些類的包名都是'com.alibaba.android.arouter.routes'
  • 而後在運行時經過讀取每一個dex文件中的這個包下的全部類經過反射來完成映射表的註冊,詳見ClassUtils.java源碼

運行時經過讀取全部dex文件遍歷每一個entry查找指定包內的全部類名,而後反射獲取類對象。這種效率看起來並不高。apache

ActivityRouter的解決方案是(demo中有2個組件名爲'app'和'sdk'):api

  • 在主app module中有一個@Modules({"app", "sdk"})註解用來標記當前app內有多少組件,根據這個註解生成一個RouterInit類
  • 在RouterInit類的init方法中生成調用同一個包內的RouterMapping_app.map
  • 每一個module生成的類(RouterMapping_app.java 和 RouterMapping_sdk.java)都放在com.github.mzule.activityrouter.router包內(在不一樣的aar中,但包名相同)
  • 在RouterMapping_sdk類的map()方法中根據掃描到的當前module內全部路由註解,生成了調用Routers.map(...)方法來註冊路由的代碼
  • 在Routers的全部api接口中最終都會觸發RouterInit.init()方法,從而實現全部路由的映射表註冊

這種方式用一個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中的全部類,並將指定接口的實現類(或指定類的子類)經過字節碼操做自動註冊到對應的管理類中。尤爲適用於命令模式或策略模式下的映射表生成。

在組件化開發框架中,可有助於實現分級按需加載的功能:

  • 在組件管理類中生成組件自動註冊的代碼
  • 在組件框架第一次被調用時加載此註冊表
  • 若組件中有不少功能提供給外部調用,能夠將這些功能包裝成多個Processor,並將它們自動註冊到組件中進行管理
  • 組件被初次調用時再加載這些Processor

#實現過程

第一步:準備工做

  1. 首先要知道如何使用Android Studio開發Gradle插件
  2. 瞭解TransformAPI:Transform API是從Gradle 1.5.0版本以後提供的,它容許第三方在打包Dex文件以前的編譯過程當中修改java字節碼(自定義插件註冊的transform會在ProguardTransform和DexTransform以前執行,因此自動註冊的類不須要考慮混淆的狀況).參考文章有:
  3. 字節碼修改框架(相比於Javassist框架ASM較難上手,但性能更高,但相學習難度阻擋不了咱們對性能的追求):

第二步:構建插件工程

  1. 按照如何使用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'
    }
}
複製代碼

2.在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)
                        }
                    }
                }
            }
        }

    }
}
複製代碼

3.記錄目標類所在的文件,由於咱們接下來要修改其字節碼,將註冊代碼插入進去

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
     }
 }
複製代碼

4.掃描完成後,開始修改目標類的字節碼(使用ASM的MethodVisitor來修改目標類指定方法,若未指定則默認爲static塊,即<clinit>方法),生成的代碼是直接調用掃描到的類的無參構造方法,並不是經過反射

  • class文件: 直接修改此字節碼文件(實際上是從新生成一個class文件並替換掉原來的文件)
  • jar文件:複製此jar文件,找到jar包中目標類所對應的JarEntry,修改其字節碼,而後替換原來的jar文件
import org.apache.commons.io.IOUtils
import org.objectweb.asm.*

import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
/**
 *
 * @author billy.qi
 * @since 17/3/20 11:48
 */
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)
        }
    }
}
複製代碼

5.接收擴展參數,獲取須要掃描類的特徵及須要插入的代碼

找了好久沒找到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中配置自動註冊插件所需的相關擴展參數

在主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

接下來就用這個插件來爲咱們自動管理註冊表吧!

相關文章
相關標籤/搜索