创建一个包含所有颤振库和依赖项的 aar
Posted
技术标签:
【中文标题】创建一个包含所有颤振库和依赖项的 aar【英文标题】:Create an aar with all flutter libraries and dependencies inside 【发布时间】:2019-11-02 02:55:13 【问题描述】:我需要创建一个包含我的 Flutter 项目的所有库的 aar,我创建了一个 Flutter 模块,现在我必须在 android 中创建一个 sdk 以嵌入到客户端应用程序中单个 aar 文件。我尝试了 Mobbeel fat AAR Gradle 插件,但无济于事。我知道我可以创建一个 Maven 存储库,但这不是我现在正在寻找的解决方案。
我的项目 build.gradle
buildscript
repositories
maven url "https://plugins.gradle.org/m2/"
google()
jcenter()
dependencies
classpath 'com.android.tools.build:gradle:3.2.1'
classpath "com.mobbeel.plugin:fat-aar:2.0.1"
allprojects
repositories
google()
jcenter()
和应用程序 build.graddle
def flutterPluginVersion = 'managed'
apply plugin: 'com.android.library'
apply plugin: "com.mobbeel.plugin"
android
compileSdkVersion 28
compileOptions
sourceCompatibility 1.8
targetCompatibility 1.8
defaultConfig
minSdkVersion 21
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
buildTypes
release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildDir = new File(rootProject.projectDir, "../build/host")
dependencies
implementation fileTree(dir: 'libs', include: ['*.jar'])
api (project(':flutter'))
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.lifecycle:lifecycle-common:2.0.0'
aarPlugin
includeAllInnerDependencies false
packagesToInclude = ["mobbeel"]
编辑:我找到了解决方案,但我不是 Android 开发人员,因此我对 mobbeel 插件进行了一些更改并将其添加到 build.gradle。之后,我可以通过执行 api project(":vibrate") 将所有库添加到我的 aar 中
String archiveAarName
project.afterEvaluate
project.android.libraryVariants.all variant ->
variant.outputs.all
archiveAarName = outputFileName
print "afterEvaluate\n"
def copyTask = createBundleDependenciesTask(variant)
String rsDirPath = "$copyTask.temporaryDir.path/rs/"
String rsCompiledDirPath = "$copyTask.temporaryDir.path/rs-compiled/"
String sourceAarPath = "$copyTask.temporaryDir.path/$variant.name/"
String taskNameCompileRs = "SDKcompileRs$variant.name.capitalize()"
String taskNameRsJa = "CreateRsJar$variant.name.capitalize()"
String taskNameCreateZip = "createZip$variant.name.capitalize()"
def compileRsTask = R2ClassTask(variant, rsDirPath, rsCompiledDirPath, taskNameCompileRs)
def rsJarTask = bundleRJarTask(variant, rsCompiledDirPath, sourceAarPath, taskNameRsJa)
def aarTask = bundleFinalAAR(variant, sourceAarPath, "finalname", taskNameCreateZip)
def assembleTask = project.tasks.findByPath("assemble$variant.name.capitalize()")
assembleBundleDependenciesTask(variant).finalizedBy(assembleTask)
assembleTask.finalizedBy(copyTask)
copyTask.finalizedBy(compileRsTask)
compileRsTask.finalizedBy(rsJarTask)
rsJarTask.finalizedBy(aarTask)
Task assembleBundleDependenciesTask(def variant)
println "assembleBundleDependenciesTask -> $variant.name"
return project.getTasks().create("hello_$variant",
project.configurations.api.getDependencies().each dependency ->
if (dependency instanceof ProjectDependency)
Project dependencyProject = project.parent.findProject(dependency.name)
String dependencyPath = "$dependencyProject.buildDir"
println "dependencyPath -> $dependencyPath"
String variantName = "$variant.name"
def assembleTask = project.tasks.findByPath(":$dependency.name:assemble$variant.name.capitalize()")
assembleTask.finalizedBy(copyTo( "$dependencyPath/outputs/aar", variantName, dependency.name))
println ''
)
Task copyTo(String fromz, String variant, String dependency)
println "copyTo fromz -> $fromz "
return project.task(type: Copy, "copyFile$dependency$variant")
from fromz
into project.projectDir.path + "/build/outputs/aar/"
include('*.aar')
rename String fileName ->
fileName = "$dependency-$variant.aar"
Task createBundleDependenciesTask(def variant)
println "createBundleDependenciesTask -> $variant.name"
String taskName = "copy$variant.name.capitalize()Dependencies"
return project.getTasks().create(taskName, CopyDependenciesTask.class,
it.includeInnerDependencies = true
it.dependencies = project.configurations.api.getDependencies()
it.variantName = variant.name
it.gradleVersion = "3.2.1"
it.buildAARDir = project.projectDir.path + "/build/outputs/aar/"
)
Task R2ClassTask(def variant, String sourceDir, String destinationDir, String taskName)
print "R2ClassTask sourceDir -> $sourceDir to destDir -> $destinationDir"
project.mkdir(destinationDir)
def classpath
classpath = project.files(project.projectDir.path +
"/build/intermediates/javac/$variant.name/compile$variant.name.capitalize()JavaWithJavac/classes")
return project.getTasks().create(taskName, JavaCompile.class,
it.source = sourceDir
it.sourceCompatibility = project.android.compileOptions.sourceCompatibility
it.targetCompatibility = project.android.compileOptions.targetCompatibility
it.classpath = classpath
it.destinationDir project.file(destinationDir)
)
Task bundleRJarTask(def variant, String fromDir, String aarPath, String taskName)
print "bundleRJarTask\n"
return project.getTasks().create(taskName, Jar.class,
it.from fromDir
it.archiveName = "r-classes.jar"
it.destinationDir project.file("$aarPath/libs")
)
Task bundleFinalAAR(def variant, String fromPath, name, String taskName)
print "bundleFinalAAR -> from $fromPath to > " + project.file(project.projectDir.path + "/build/outputs/aar/") + "\n"
return project.getTasks().create(taskName, Zip.class,
it.from fromPath
it.include "**"
it.archiveName = "$name-$variant.name.aar"
it.destinationDir(project.file(project.projectDir.path + "/build/outputs/aar/"))
)
import groovy.xml.XmlUtil
class CopyDependenciesTask extends DefaultTask
Boolean includeInnerDependencies
DependencySet dependencies
String variantName
String gradleVersion
String[] packagesToInclude = [""]
String buildAARDir
@TaskAction
def executeTask()
if (temporaryDir.exists())
temporaryDir.deleteDir()
temporaryDir.mkdir()
copyProjectBundles()
analyzeDependencies()
def copyProjectBundles()
println "copyProjectBundles"
if (gradleVersion.contains("3.2")) // Version 3.4.x
println "packaged-classes -> $project.projectDir.parentFile.parent/finalProjname/app/build/intermediates/packaged-classes/"
project.copy
from "$project.projectDir.parentFile.parent/finalProjname/app/build/intermediates/packaged-classes/"
include "$variantName/**"
into temporaryDir.path
project.copy
from("$project.projectDir.parentFile.parent/finalProjname/app/build/intermediates/res/symbol-table-with-package/$variantName")
include "package-aware-r.txt"
rename '(.*)', 'R.txt'
from("$project.projectDir.parentFile.parent/finalProjname/app/build/intermediates/aapt_friendly_merged_manifests/" +
"$variantName/process$variantName.capitalize()Manifest/aapt/")
include "AndroidManifest.xml"
into "$temporaryDir.path/$variantName"
println " check this -> $temporaryDir.path/$variantName/R.txt"
processRsAwareFile(new File("$temporaryDir.path/$variantName/R.txt"))
project.copy
from "$project.projectDir.parentFile.parent/finalProjname/app/build/intermediates/packaged_res/$variantName"
include "**"
into "$temporaryDir.path/$variantName/res"
project.copy
from "$project.projectDir.parentFile.parent/finalProjname/app/build/intermediates/library_assets/$variantName/packageDebugAssets/out/"
include "**"
into "$temporaryDir.path/$variantName/assets"
else // Version 3.0.x
project.copy
from "$project.projectDir.parentFile.parent/finalProjname/app/build/intermediates/bundles/"
from "$project.projectDir.parentFile.parent/finalProjname/app/build/intermediates/manifests/full/"
include "$variantName/**"
exclude "**/output.json"
into temporaryDir.path
def analyzeDependencies()
print "analyzeDependencies\n"
dependencies.each dependency ->
def dependencyPath
def archiveName
print "dependency -> " + dependency
if (dependency instanceof ProjectDependency)
print " instanceof -> ProjectDependency\n"
String group = dependency.group
Project dependencyProject
dependencyProject = project.parent.findProject(dependency.name)
println "dependencyProject -> $dependencyProject"
if (dependencyProject.plugins.hasPlugin('java-library'))
println "Internal java dependency detected -> " + dependency.name
archiveName = dependencyProject.jar.archiveName
dependencyPath = "$dependencyProject.buildDir/libs/"
else
println "Internal android dependency detected -> " + dependency.name
dependencyProject.android.libraryVariants.all
if (it.name == variantName)
it.outputs.all archiveName = outputFileName
dependencyPath = buildAARDir
processDependency(dependency, archiveName, dependencyPath)
else if (dependency instanceof ExternalModuleDependency)
println "External dependency detected -> " + dependency.group + ":" + dependency.name + ":" + dependency.version
dependencyPath = project.gradle.getGradleUserHomeDir().path + "/caches/modules-2/files-2.1/"
dependencyPath += dependency.group + "/" + dependency.name + "/" + dependency.version + "/"
processDependency(dependency, archiveName, dependencyPath)
else
println "Not recognize type of dependency for " + dependency
println()
/**
* In this case dependency is outside from workspace, download from maven repository if file is
* a jar directly move to lib/ folder and analyze pom file for detect another transitive dependency
* @param dependency
* @return
*/
def processDependency(Dependency dependency, String archiveName, String dependencyPath)
println "processDependency -> $archiveName in $dependencyPath"
project.fileTree(dependencyPath).getFiles().each file ->
println "processDependency file.name -> $file.name "
if (file.name.endsWith(".pom"))
println "POM: " + file.name
processPomFile(file.path)
else
if (archiveName == null || file.name == archiveName)
println "Artifact: " + file.name
if (file.name.endsWith(".aar"))
processZipFile(file, dependency)
else if (file.name.endsWith(".jar"))
if (!file.name.contains("sources"))
copyArtifactFrom(file.path)
else
println " |--> Exclude for source jar"
println()
def processZipFile(File aarFile, Dependency dependency)
println "processZipFile"
String tempDirPath = "$temporaryDir.path/$dependency.name_zip"
println "tempDirPath -> $tempDirPath"
project.copy
from project.zipTree(aarFile.path)
include "**/*"
into tempDirPath
File tempFolder = new File(tempDirPath)
println "temporaryDir -> $temporaryDir.path/$variantName/"
project.copy
from "$tempFolder.path"
include "classes.jar"
into "$temporaryDir.path/$variantName/libs"
def jarName = getJarNameFromDependency(dependency)
rename "classes.jar", jarName
project.copy
from "$tempFolder.path/libs"
include "**/*.jar"
into "$temporaryDir.path/$variantName/libs"
project.copy
from "$tempFolder.path/jni"
include "**/*.so"
into "$temporaryDir.path/$variantName/jni"
project.copy
from "$tempFolder.path/assets"
include "**/*"
into "$temporaryDir.path/$variantName/assets"
project.copy
from "$tempFolder.path/res"
include "**/*"
exclude "values/**"
into "$temporaryDir.path/$variantName/res"
processValuesResource(tempFolder.path)
processRsFile(tempFolder)
println "tempFolder.deleteDir()"
tempFolder.deleteDir()
def getJarNameFromDependency(Dependency dependency)
def jarName = ""
if (null != dependency.group)
jarName += dependency.group.toLowerCase() + "-"
jarName += dependency.name.toLowerCase()
if(null != dependency.version && !dependency.version.equalsIgnoreCase('unspecified'))
jarName += "-" + dependency.version
jarName += ".jar"
return jarName
def processRsAwareFile(File resAwareFile)
println "processRsAwareFile"
RandomAccessFile raf = new RandomAccessFile(resAwareFile, "rw")
long writePosition = raf.getFilePointer()
raf.readLine() // Move pointer to second line of file
long readPosition = raf.getFilePointer()
byte[] buffer = new byte[1024]
int bytesInBuffer
while (-1 != (bytesInBuffer = raf.read(buffer)))
raf.seek(writePosition)
raf.write(buffer, 0, bytesInBuffer)
readPosition += bytesInBuffer
writePosition += bytesInBuffer
raf.seek(readPosition)
raf.setLength(writePosition)
raf.seek(0)
if (gradleVersion.contains("3.2"))
String filePath = "$project.projectDir.parentFile.parent/finalProjname/app/build/intermediates/symbols/$variantName/R.txt"
Scanner resourcesOriginal = new Scanner(new File(filePath))
raf.seek(0) // Move pointer to first line
String line
int offset = 0
while (resourcesOriginal.hasNextLine())
boolean match = false
line = resourcesOriginal.nextLine()
println line
line += "\n"
byte[] data = line.getBytes()
raf.seek(offset)
raf.write(data, 0, data.length)
offset += data.length
raf.seek(offset + 1)
raf.close()
def processRsFile(File tempFolder)
println "processRsFile"
def mainManifestFile = project.android.sourceSets.main.manifest.srcFile
def libPackageName = ""
if (mainManifestFile.exists())
println "processRsFile -> mainManifestFile.exists()"
libPackageName = new XmlParser().parse(mainManifestFile).@package
def manifestFile = new File("$tempFolder/AndroidManifest.xml")
if (manifestFile.exists())
println "processRsFile -> 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 = new File("$tempFolder/R.txt")
def rMap = new ConfigObject()
if (rTxt.exists())
println "processRsFile -> 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 = com.company.native_sdk.R.$subclass.$name;" << '\n'
sb << " " << '\n'
sb << '' << '\n'
new File("$temporaryDir.path/rs/$packagePath").mkdirs()
FileOutputStream outputStream = new FileOutputStream("$temporaryDir.path/rs/$packagePath/R.java")
println "R file path -> $temporaryDir.path/rs/$packagePath/R.java"
outputStream.write(sb.toString().getBytes())
outputStream.close()
def processValuesResource(String tempFolder)
println "processValuesResource"
File valuesSourceFile = new File("$tempFolder/res/values/values.xml")
File valuesDestFile = new File("$temporaryDir.path/$variantName/res/values/values.xml")
if (valuesSourceFile.exists())
println "processValuesResource -> valuesSourceFile.exists"
if (!valuesDestFile.exists())
println "processValuesResource -> !valuesDestFile.exists"
project.copy
from "$tempFolder/res"
include "values/*"
into "$temporaryDir.path/$variantName/res"
else
println "processValuesResource -> valuesDestFile.exists"
def valuesSource = new XmlSlurper().parse(valuesSourceFile)
def valuesDest = new XmlSlurper().parse(valuesDestFile)
valuesSource.children().each
valuesDest.appendNode(it)
FileOutputStream fileOutputStream = new FileOutputStream(valuesDestFile, false)
byte[] myBytes = XmlUtil.serialize(valuesDest).getBytes("UTF-8")
fileOutputStream.write(myBytes)
fileOutputStream.close()
else
println "processValuesResource -> !valuesSourceFile.exists"
def copyArtifactFrom(String path)
project.copy
includeEmptyDirs false
from path
include "**/*.jar"
into "$temporaryDir.path/$variantName/libs"
rename '(.*)', '$1'.toLowerCase()
def processPomFile(String pomPath)
def pom = new XmlSlurper().parse(new File(pomPath))
pom.dependencies.children().each
def subJarLocation = project.gradle.getGradleUserHomeDir().path + "/caches/modules-2/files-2.1/"
if (!it.scope.text().equals("test") && !it.scope.text().equals("provided"))
String version = it.version.text()
if (version.startsWith("\$") && version.endsWith(""))
pom.properties.children().each
if (version.contains(it.name()))
version = it.text()
println " |--> Inner dependency: " + it.groupId.text() + ":" + it.artifactId.text() + ":" + version
if (includeInnerDependencies || it.groupId.text() in packagesToInclude)
subJarLocation += it.groupId.text() + "/" + it.artifactId.text() + "/" + version + "/"
project.fileTree(subJarLocation).getFiles().each file ->
if (file.name.endsWith(".pom"))
println " /--> " + file.name
processPomFile(file.path)
else
if (!file.name.contains("sources") && !file.name.contains("javadoc"))
copyArtifactFrom(file.path)
else
println " (Exclude inner dependency)"
【问题讨论】:
嗨,曼努埃尔。我还想将颤振模块导出为 aar 文件。你有解决办法吗? @NiravTukadiya 看看我的新编辑。希望对你有帮助。 【参考方案1】:aar 文件不包含传递依赖项,并且没有描述库使用的依赖项的 pom 文件。
这意味着,如果您使用 flatDir
存储库导入 aar 文件,您还必须在项目中指定依赖项。
我知道这不是您正在寻找的解决方案,但您应该使用 maven 存储库来解决此问题。 在这种情况下,gradle 使用 pom 文件下载依赖项,该文件将包含依赖项列表。
【讨论】:
那么,如果我有一个 maven 解决方案,我是否也必须在我的 maven 存储库中拥有所有颤振插件?【参考方案2】:将 Android 原生库添加到您在 Android Studio 中的项目中:文件 -> 新建 -> 新模块 -> Android 库。
然后将这个插件https://github.com/kezong/fat-aar-android 添加到项目中,并用'embed'关键字替换'implementation'。 然后您的项目结构将如下所示:
在 flutter_library 目录中运行命令flutter build aar -v
。注意:flutter_library 包含 Flutter 相关文件,例如 lib/、.android、.ios、pubspec.yaml 等
在根项目目录运行./gradlew assemble
aar 将位于 library/build/outputs/aar
看我的例子:https://github.com/askarsyzdykov/native_flutter_lib
【讨论】:
以上是关于创建一个包含所有颤振库和依赖项的 aar的主要内容,如果未能解决你的问题,请参考以下文章
如何使用maven shaded插件而不是两个创建一个包含所有依赖项的可执行jar
如何解决本机 Java 或 Kotlin 中的错误“在依赖项的 AAR 元数据中指定的 minCompileSdk (31)”? [复制]