创建一个包含所有颤振库和依赖项的 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

制作包含依赖库的AAR包

使用 yum 下载软件包的所有依赖项的 RPM

如何解决本机 Java 或 Kotlin 中的错误“在依赖项的 AAR 元数据中指定的 minCompileSdk (31)”? [复制]

使用依赖 pom/iml 文件手动添加 aar

如何将 JAR 依赖项包含到 AAR 库中