首先先说下我这边的需求:多个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,不然会无法触发合并的构建(如有这方面的需要自行改下就好,不难~)
2281

被折叠的 条评论
为什么被折叠?



