基于fat-aar.build的多版本生成方案

首先先说下我这边的需求:多个module或多个第三方库(aar、jar)自由搭配合并到一个aar中。类比可以参考微信支付的不同版本:带监测的版本和不带监测的版本。
然后是说说原来的fat-aar.build不适合我这原因:
1.代码写死只能生成基于release版本。
2.无法配合maven进行多版本的上传管理。

/**
 * This is free and unencumbered software released into the public domain.

 Anyone is free to copy, modify, publish, use, compile, sell, or
 distribute this software, either in source code form or as a compiled
 binary, for any purpose, commercial or non-commercial, and by any
 means.

 In jurisdictions that recognize copyright laws, the author or authors
 of this software dedicate any and all copyright interest in the
 software to the public domain. We make this dedication for the benefit
 of the public at large and to the detriment of our heirs and
 successors. We intend this dedication to be an overt act of
 relinquishment in perpetuity of all present and future rights to this
 software under copyright law.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.

 For more information, please refer to <http://unlicense.org/>
 */


import com.android.annotations.NonNull
import com.android.manifmerger.ManifestMerger2
import com.android.manifmerger.ManifestMerger2.Invoker
import com.android.manifmerger.ManifestMerger2.MergeType
import com.android.manifmerger.MergingReport
import com.android.manifmerger.PlaceholderEncoder
import com.android.manifmerger.XmlDocument
import com.android.utils.ILogger
import com.google.common.base.Charsets
import com.google.common.io.Files

/**
 * Fat AAR Lib generator v 0.2.1
 * Target Gradle Version :: 2.2.0
 *
 * Latest version available at https://github.com/adwiv/android-fat-aar
 * Please report issues at https://github.com/adwiv/android-fat-aar/issues
 *
 * This code is in public domain.
 *
 * Use at your own risk and only if you understand what it does. You have been warned ! :-)
 */

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:manifest-merger:25.3.2'
    }
}


// Change backslash to forward slash on windows
ext.build_dir = buildDir.path.replace(File.separator, '/');
ext.root_dir = project.rootDir.absolutePath.replace(File.separator, '/');
//ext.exploded_aar_dir = "$build_dir/intermediates/exploded-aar";
//ext.classs_release_dir = "$build_dir/intermediates/classes/baidu/release";
//ext.bundle_release_dir = "$build_dir/intermediates/bundles/baidu/release";
//ext.manifest_aaapt_dir = "$build_dir/intermediates/manifests/aapt/release";
ext.class_release_dir = new HashMap<String, String>()
ext.bundle_release_dir = new HashMap<String, String>()
ext.manifest_aaapt_dir = new HashMap<String, String>()

ext.generated_rsrc_dir = "$build_dir/generated/source/r/release";

ext.base_r2x_dir = "$build_dir/fat-aar/release/";

def gradleVersionStr = GradleVersion.current().getVersion();
ext.gradleApiVersion = gradleVersionStr.substring(0, gradleVersionStr.lastIndexOf(".")).toFloat();

println "Gradle version: " + gradleVersionStr;


afterEvaluate {

//    buildArr(project.ext.product, project.ext.buildType)


    android.productFlavors.forEach({
        def product = it;
        android.buildTypes.forEach({
            buildArr(product.name, it.name)
        })
    })
}

public void buildArr(String productName, String typeName = "release") {
//    tasks.getByName("preBuild").dependsOn clean
// Paths to embedded aar projects
    def embeddedAarDirs = new ArrayList()

    // Paths to embedded jar files
    def embeddedJars = new ArrayList()

    // Embedded aar files dependencies
    def embeddedAarFiles = new ArrayList<ResolvedArtifact>()

    // List of embedded R classes
    def embeddedRClasses = new ArrayList()

    println(project.getStatus())
    def list = new ArrayList(configurations.compile.resolvedConfiguration.firstLevelModuleDependencies)
//    configurations.compile.resolvedConfiguration.firstLevelModuleDependencies.each {
//        println("compile>>>>$it")
//    }
//    if(project.ext.has)
//    println()
    if (project.ext.has(typeName)) {
        ArrayList array = project.ext[typeName];
        println(array.contains("logger"))
//        array.each {
//            println(it)
//        }
    }

    if (list.size() > 0) {
        def productFlavor = android.productFlavors.findByName(productName)
        def buildType = android.buildTypes.findByName(typeName)

        if (productFlavor == null) return
        String product = productFlavor.name
        String Product = product.capitalize()
        println "Product: $Product, product: $product"

        String type = buildType.name
        String Type = type.capitalize()
        println "BuildType: $Type, buildType: $type"

        ext.generated_rsrc_dir = "$build_dir/generated/source/r/$product/$type";

        ext.base_r2x_dir = "$build_dir/fat-aar/$product/$type/";

        list.reverseEach {
            // 考虑到gradle 版本都大于2.3了,去掉原来的版本判断过程
            def aarPath = "${root_dir}/${it.moduleName}/build/intermediates/bundles/$product/$type"
            it.moduleArtifacts.each {
                artifact ->
                    if (project.ext.has(typeName) && project.ext[typeName].contains(artifact.name)) {

                        println "ARTIFACT 3 : ${artifact.name}"
                        if (artifact.type == 'aar') {
                            if (!embeddedAarFiles.contains(artifact)) {
                                embeddedAarFiles.add(artifact)
                            }
                            if (!embeddedAarDirs.contains(aarPath)) {
                                if (artifact.file.isFile()) {
                                    copy {
                                        from zipTree(artifact.file)
                                        into aarPath
                                    }
                                }
                                embeddedAarDirs.add(aarPath)
                            }
                        } else if (artifact.type == 'jar') {
                            def artifactPath = artifact.file
                            if (!embeddedJars.contains(artifactPath))
                                embeddedJars.add(artifactPath)
                        } else {
                            throw new Exception("Unhandled Artifact of type ${artifact.type}")
                        }
                    }
            }
        }



        ext.class_release_dir["$product-$type"] = "$build_dir/intermediates/classes/$product/${type}"
        ext.bundle_release_dir["$product-$type"] = "$build_dir/intermediates/bundles/${product}${Type}"
        ext.manifest_aaapt_dir["$product-$type"] = "$build_dir/intermediates/manifests/aapt/$product/${type}"

        embedAssets(embeddedAarDirs)

        // Merge Assets
        tasks.getByName("generate${Product}${Type}Assets").dependsOn embedAssets
        embedAssets.dependsOn "prepare${Product}${Type}Dependencies"

        // Embed Resources by overwriting the inputResourceSets
        tasks.maybeCreate("embedLibraryResources$Product${Type}").doLast {
            println "Running FAT-AAR Task :embedLibraryResources"

            def oldInputResourceSet = tasks.getByName("package${Product}${Type}Resources").inputResourceSets
            tasks.getByName("package${Product}${Type}Resources").conventionMapping.map("inputResourceSets") {
                getMergedInputResourceSets(oldInputResourceSet,embeddedAarDirs)
            }
        }

        tasks.getByName("package${Product}${Type}Resources").dependsOn "embedLibraryResources${Product}${Type}"
        tasks.getByName("embedLibraryResources${Product}${Type}").dependsOn "prepare${Product}${Type}Dependencies"

        // Embed JNI Libraries
        tasks.maybeCreate("embedJniLibs${Product}${Type}").doLast {
            println "Running FAT-AAR Task :embedJniLibs"

            embeddedAarDirs.each { aarPath ->
                println "======= Copying JNI from $aarPath/jni"
                // Copy JNI Folders
                copy {
                    from fileTree(dir: "$aarPath/jni")
                    into file(bundle_release_dir["$product-$type"] + "/jni")
                }
            }
        }

        tasks.getByName("bundle${Product}${Type}").dependsOn "embedJniLibs$Product${Type}"
        tasks.getByName("embedJniLibs$Product${Type}").dependsOn "transformNativeLibsWithSyncJniLibsFor${Product}${Type}"

        // Merge Embedded Manifests
        tasks.maybeCreate("embedManifests$Product${Type}").doLast {
            println "Running FAT-AAR Task :embedManifests"

            ILogger mLogger = new MiLogger()
            List libraryManifests = new ArrayList<>()

            embeddedAarDirs.each { aarPath ->
                File dependencyManifest = file("$aarPath/AndroidManifest.xml")

                if (!libraryManifests.contains(aarPath) && dependencyManifest.exists()) {
                    libraryManifests.add(dependencyManifest)
                }
            }

            File reportFile = file("${build_dir}/embedManifestReport.txt")
            File origManifest = file(bundle_release_dir.getAt("$product-$type") + "/AndroidManifest.xml")
            File copyManifest = file(bundle_release_dir.getAt("$product-$type") + "/AndroidManifest.orig.xml")
            File aaptManifest = file(manifest_aaapt_dir.getAt("$product-$type") + "/AndroidManifest.xml")

            if (!origManifest.exists()) {
                origManifest = file("./src/main/AndroidManifest.xml")
            }

            if (!origManifest.exists()) {
                return
            }

            copy {
                from origManifest.parentFile
                into copyManifest.parentFile
                include origManifest.name
                rename(origManifest.name, copyManifest.name)
            }

            try {

                Invoker manifestMergerInvoker = ManifestMerger2.newMerger(copyManifest, mLogger, MergeType.APPLICATION)

                manifestMergerInvoker.addLibraryManifests(libraryManifests.toArray(new File[libraryManifests.size()]))

                // manifestMergerInvoker.setPlaceHolderValues(placeHolders)
                manifestMergerInvoker.setMergeReportFile(reportFile);

                MergingReport mergingReport = manifestMergerInvoker.merge();

                mLogger.info("Merging result:" + mergingReport.getResult());
                MergingReport.Result result = mergingReport.getResult();
                switch (result) {
                    case MergingReport.Result.WARNING:
                        mergingReport.log(mLogger);
                // fall through since these are just warnings.
                    case MergingReport.Result.SUCCESS:
                        XmlDocument xmlDocument = mergingReport.getMergedXmlDocument(MergingReport.MergedManifestKind.MERGED)
                        try {
                            String annotatedDocument = mergingReport.getActions().blame(xmlDocument)
                            mLogger.verbose(annotatedDocument);
                        } catch (Exception e) {
                            mLogger.error(e, "cannot print resulting xml")
                        }
                        save(xmlDocument, origManifest)
                        mLogger.info("Merged manifest saved to " + origManifest)
                        if (aaptManifest.exists()) {
                            new PlaceholderEncoder().visit(xmlDocument)
                            save(xmlDocument, aaptManifest)
                            mLogger.info("Merged aapt safe manifest saved to " + aaptManifest)
                        }
                        break
                    case MergingReport.Result.ERROR:
                        mergingReport.log(mLogger)
                        throw new RuntimeException(mergingReport.getReportString())
                    default:
                        throw new RuntimeException("Unhandled result type : " + mergingReport.getResult())
                }
            } catch (RuntimeException e) {
                // Unacceptable error
                e.printStackTrace()
                throw new RuntimeException(e)
            }
        }

        tasks.getByName("bundle${Product}${Type}").dependsOn "embedManifests$Product${Type}"
        tasks.getByName("embedManifests$Product${Type}").dependsOn "process${Product}${Type}Manifest"

        // Merge proguard files
        tasks.maybeCreate("embedProguard$Product${Type}").doLast {
            println "Running FAT-AAR Task :embedProguard"

            def proguardRelease = file(bundle_release_dir[it.name] + "/proguard.txt")
            embeddedAarDirs.each { aarPath ->
                try {
                    def proguardLibFile = file("$aarPath/proguard.txt")
                    if (proguardLibFile.exists()) {
                        proguardRelease.append("\n" + proguardLibFile.text)
                    }
                } catch (Exception e) {
                    e.printStackTrace()
                    throw e
                }
            }
        }

        tasks.getByName("embedLibraryResources$Product${Type}").dependsOn "embedProguard$Product${Type}"
        tasks.getByName("embedProguard$Product${Type}").dependsOn tasks.getByName("prepare${Product}${Type}Dependencies")

        generateRJava(embeddedAarDirs,embeddedRClasses)

        // Generate R.java files
        tasks.getByName("compile${Product}${Type}JavaWithJavac").dependsOn generateRJava
        generateRJava.dependsOn "process${Product}${Type}Resources"

        // Bundle the java classes
        tasks.maybeCreate("collectRClass${Product}${Type}").doLast {
            println "COLLECTRCLASS"
            delete base_r2x_dir
            mkdir base_r2x_dir

            copy {
                from class_release_dir["$product-$type"]
                include embeddedRClasses
                into base_r2x_dir
            }
        }
        tasks.maybeCreate("embedJavaJars${Product}${Type}").dependsOn("collectRClass${Product}${Type}").doLast {
            embeddedAarFiles.each { artifact ->
                println "Running FAT-AAR Task :embedJavaJars, AbsolutePath: " + artifact.file.getAbsolutePath()
                FileTree aarFileTree = zipTree(artifact.file.getAbsolutePath())

                def aarFile = aarFileTree.files.find { it.name.contains("classes.jar") }
                copy {
                    from zipTree(aarFile)
                    into class_release_dir["$product-$type"]
                }
            }

            embeddedAarDirs.each { aarPath ->
                println "Running FAT-AAR Task :embedJavaJars, aarPath: $aarPath"

                // Copy all additional jar files to bundle lib
                FileTree jars = fileTree(dir: "$aarPath", include: '*.jar', exclude: 'classes.jar')
                jars += fileTree(dir: "$aarPath/libs", include: '*.jar')

                jars.each {
                    def jarFile = it.absoluteFile
                    copy {
                        from zipTree(jarFile)
                        into class_release_dir["$product-$type"]
                    }
                }
            }
        }

        tasks.getByName("bundle${Product}${Type}").dependsOn "embedJavaJars$Product${Type}"
        tasks.getByName("embedJavaJars$Product${Type}").dependsOn "compile${Product}${Type}JavaWithJavac"

        // If proguard is enabled, run the tasks that bundleRelease should depend on before proguard
        if (tasks.findByPath("proguard$Product${Type}") != null) {
            tasks.getByName(proguardRelease).dependsOn "embedJavaJars$Product${Type}"
        } else if (tasks.findByPath("transformClassesAndResourcesWithProguardFor${Product}${Type}") != null) {
            tasks.findByPath("transformClassesAndResourcesWithProguardFor${Product}${Type}").dependsOn "embedJavaJars$Product${Type}"
        }

//            }


    }
}


private List getMergedInputResourceSets(List inputResourceSet,embeddedAarDirs) {
    //We need to do this trickery here since the class declared here and that used by the runtime
    //are different and results in class cast error
    def ResourceSetClass = inputResourceSet.get(0).class

    List newInputResourceSet = new ArrayList(inputResourceSet)

    println "getMergedInputResourceSets"

    embeddedAarDirs.each { aarPath ->
        try {
            def resname
            if (gradleApiVersion >= 2.3f) {
                def parentProject = project.rootProject.name.toString()

                def startIndex = aarPath.indexOf('/' + parentProject)
                def endIndex = aarPath.indexOf('/build/')

                if (startIndex < 1 || endIndex < 1)
                    return;
                resname = aarPath.substring(startIndex, endIndex).replace('/', ':')
            }
//            else
//                resname = (aarPath.split(exploded_aar_dir)[1]).replace('/', ':');
            def rs = ResourceSetClass.newInstance([resname, true] as Object[])
            rs.addSource(file("$aarPath/res"))
            newInputResourceSet += rs
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    return newInputResourceSet
}

 /* Assets are simple files, so just adding them to source set seems to work.
 */
def embedAssets(embeddedAarDirs){
    tasks.maybeCreate("embedAssets").doLast {
        println "Running FAT-AAR Task :embedAssets"
        embeddedAarDirs.each { aarPath ->
            // Merge Assets
            android.sourceSets.main.assets.srcDirs += file("$aarPath/assets")
        }
    }
}


def generateRJava(embeddedAarDirs,embeddedRClasses){
    tasks.maybeCreate("generateRJava").doLast {
        println "Running FAT-AAR Task :generateRJava"

        // Now generate the R.java file for each embedded dependency
        def mainManifestFile = android.sourceSets.main.manifest.srcFile;
        def libPackageName = "";

        if (mainManifestFile.exists()) {

            libPackageName = new XmlParser().parse(mainManifestFile).@package
        }

        embeddedAarDirs.each { aarPath ->

            def manifestFile = file("$aarPath/AndroidManifest.xml");
            if (!manifestFile.exists()) {
                manifestFile = file("./src/main/AndroidManifest.xml");
            }

            if (manifestFile.exists()) {
                def aarManifest = new XmlParser().parse(manifestFile);
                def aarPackageName = aarManifest.@package

                String packagePath = aarPackageName.replace('.', '/')

                // Generate the R.java file and map to current project's R.java
                // This will recreate the class file
                def rTxt = file("$aarPath/R.txt")
                def rMap = new ConfigObject()

                if (rTxt.exists()) {
                    rTxt.eachLine {
                        line ->
                            //noinspection GroovyUnusedAssignment
                            def (type, subclass, name, value) = line.tokenize(' ')
                            rMap[subclass].putAt(name, type)
                    }
                }

                def sb = "package $aarPackageName;" << '\n' << '\n'
                sb << 'public final class R {' << '\n'

                rMap.each {
                    subclass, values ->
                        sb << "  public static final class $subclass {" << '\n'
                        values.each {
                            name, type ->
                                sb << "    public static $type $name = ${libPackageName}.R.${subclass}.${name};" << '\n'
                        }
                        sb << "    }" << '\n'
                }

                sb << '}' << '\n'

                mkdir("$generated_rsrc_dir/$packagePath")
                file("$generated_rsrc_dir/$packagePath/R.java").write(sb.toString())

                embeddedRClasses += "$packagePath/R.class"
                embeddedRClasses += "$packagePath/R\$*.class"
            }

        }
    }
}



private void save(XmlDocument xmlDocument, File out) {
    try {
        Files.write(xmlDocument.prettyPrint(), out, Charsets.UTF_8);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

class MiLogger implements ILogger {

    @Override
    void error(
            @com.android.annotations.Nullable Throwable t,
            @com.android.annotations.Nullable String msgFormat, Object... args) {
        System.err.println(String.format("========== ERROR : " + msgFormat, args))
        if (t) t.printStackTrace(System.err)
    }

    @Override
    void warning(@NonNull String msgFormat, Object... args) {
        System.err.println(String.format("========== WARNING : " + msgFormat, args))
    }

    @Override
    void info(@NonNull String msgFormat, Object... args) {
        System.out.println(String.format("========== INFO : " + msgFormat, args))
    }

    @Override
    void verbose(@NonNull String msgFormat, Object... args) {
        // System.out.println(String.format("========== DEBUG : " + msgFormat, args))
    }
}

以上为新的fat-aar.build代码,复制粘贴即可,下面贴一段使用的代码:

  withoutWechat {
           ...
            project.ext.withoutWechat = ["logger", "baseHttp", "hardybus", "gson"]
           ...
        }
        release {
         ...
            project.ext.release = ["logger", "baseHttp", "hardybus", "gson", "wechat"]
         ...

        }
        debug {
            buildConfigField "boolean", "LOG_DEBUG", "true"
        }
    compile(name: 'logger', ext: 'aar')
    compile(name: 'baseHttp', ext: 'aar')
    compile(name: 'hardybus', ext: 'aar')
    compile(name: 'gson', ext: 'aar')
    compile project(':wechat')

project.ext.withoutWechat = ["logger", "baseHttp", "hardybus", "gson"],数组是要合并的库的名称,因为是基于fat-arr.build是基于buildType来区分要合并的的库,所以数组名称必须和buildType的名称一样。
注意事项及问题:
1.数据的定义必须是全局定义,即project.ext.release
2.必须至少要有一个自定义的productFlavor,不然会无法触发合并的构建(如有这方面的需要自行改下就好,不难~)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值