1. 程式人生 > >Android匯出aar時巢狀引用的那些坑

Android匯出aar時巢狀引用的那些坑

http://www.jianshu.com/p/7a532de0b111

最近寫了個Android SDK工程,在程式碼、測試統統完成後,居然在匯出的一步折騰了兩三天,在此總結下查詢資料的過程和結果,引以借鑑。
首先,這次趟坑解決了以下問題:

  1. 匯出aar至本地Maven庫,包含引用的Module工程
  2. 匯出eclipse適用的庫工程,幷包含所有引用的jar包(包括巢狀引用)

1. 通用匯出方式

通常來講,一個簡單的Android Library工程,匯出aar有這幾種方式:

  1. 編譯後自動會在build/outputs/aar目錄下生成.aar檔案。此aar僅打包了Library工程的class、libs和資原始檔,但Library引用的其他庫(比如compile "com.squareup.okhttp3:okhttp:3.4.1"
    )並未包含在aar中。使用Library時還需把它引用的庫再手動宣告一遍,差評!
  2. 釋出到本地Maven庫(或jCenter、MavenCentral)。釋出出來的內容除了aar還包含了Library的所有dependencies資訊。使用時直接設定好maven庫地址,宣告引用Libraray,gradle就會幫你自動引用Library中巢狀引用的所有dependencies了。

我的需求:
然而,我的SDK Library引用了自己的另一個Module工程common(用來提供基礎功能,服務於不同的專案),但匯出的aar無法包含引用的common工程,我又不希望單獨匯出common工程讓外部呼叫,這可怎麼辦?


2. 匯出包含巢狀引用的aar

一翻Google後在Github上找到一個庫android-fat-aar(https://github.com/adwiv/android-fat-aar ),它可以將專案引用的module打包進aar,並且統一進行混淆(這點也很重要)。雖然它有無法合併AIDL、無法改變build type等缺點(詳細見其文件),但作為大眾的需求來講已經足夠了。

按照文件,匯入fat-aar指令碼,編譯sdk library工程,打包sdk aar釋出到本地maven庫,一切正常,但建立個demo工程使用此maven庫發現此類報錯(涉及包名處均用'xxx'代替):

Error:
A problem occurred configuring project ':demo'. > Could not resolve all dependencies for configuration ':demo:_debugApkCopy'. > Could not find xxx:common:unspecified. Required by: xxx:demo:unspecified > com.xxx:sdk:0.0.7

報錯demo工程找不到common工程的引用,但我已經將common工程打包進aar了啊,怎麼回事?


pom指令碼修改
原來,一個maven倉庫不僅僅包含自己庫的程式碼,還具有引用資訊dependencies的宣告(這就是其方便所在),而這些dependencies都列在了與aar檔案同目錄的.pom檔案中。上述的報錯就是因為匯出sdk的aar時,pom檔案中依舊保留了對common工程的依賴,因此要在gradle中手動修改pom資訊:

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: uri('../../repo'))
            pom.project{
                groupId 'com.XXX'
                version = android.defaultConfig.versionName
            }
            //去除對common的引用
            pom.whenConfigured {pom ->
                def common = pom.dependencies.find {dep -> dep.groupId == 'XXX' && dep.artifactId == 'common' }
                pom.dependencies.remove(common)
            }
        }
    }
}

然而僅僅這樣是不夠的,還會發生諸如這樣的報錯:

Caused by: java.lang.NoClassDefFoundError: com.squareup.okhttp.xxxx

因為你如果真的開啟pom檔案,就會發現common工程引用的所有第三方庫(比如okhttp什麼的)全!都!沒!有!聲!明!依!賴!於是只好在指令碼中手動修改pom檔案(group name和包名請讀者自行替換):

task uploadArchivesNew(dependsOn: uploadArchives)  {
    doLast {
        println "uploadArchivesNew..."
        // Get existing pom file
        def pomFileLocation = "../repo/com/xxx/sdk/" + android.defaultConfig.versionName + "/sdk-" + android.defaultConfig.versionName + ".pom"
        Node xml = new XmlParser().parse(pomFileLocation)

        def dependencies = xml.dependencies.first();

        configurations.compile.resolvedConfiguration.firstLevelModuleDependencies.each {
            //將common的引用加入pom(但不包括common專案本身)
            if (it.moduleGroup.equals("xxx") && it.moduleName.equals("common")) {
                it.allModuleArtifacts.each {
                    String moduleGroup = it.getModuleVersion().getId().getGroup()
                    String moduleName = it.getModuleVersion().getId().getName()
                    String moduleVersion = it.getModuleVersion().getId().getVersion()
                    if (!moduleGroup.equals("xxx") && !moduleName.equals("common")) {
                        def newDepNode = dependencies.appendNode('dependency')
                        newDepNode.appendNode('groupId', moduleGroup)
                        newDepNode.appendNode('artifactId', moduleName)
                        newDepNode.appendNode('version', moduleVersion)
                        newDepNode.appendNode('scope', 'compile')
                    }
                }
            }
        }

        // Overwrite existing pom file
        new XmlNodePrinter(new PrintWriter(new FileWriter(pomFileLocation))).print(xml)
    }
}

大功告成!每次執行此task即可完成混淆、打包、上傳至本地maven庫、修改pom資訊,使用時直接在gradle中定義好maven庫地址,宣告引用即可:

allprojects {
    repositories {
        maven{url uri('../../repo')}
    }
}
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.xxx:sdk:0.0.7'
}

3. 匯出包含所有引用jar包的eclipse工程

雖然大部分開發者在用Android Studio了,但可能還有不少專案在eclipse裡苦苦掙扎,本著方便開發者的思想,sdk還是打包出了eclipse版本,gradle指令碼如下:

task releaseJar(type: Copy, dependsOn: 'build') {
    from('build/intermediates/bundles/release/')
    into('build/outputJar')
    String jarName = 'xxx_' + android.defaultConfig.versionName + '.jar'
    rename('classes.jar', jarName)
}

匯出後在build/outputJar目錄下即可找到所有class、res等資原始檔,將其建立成eclipse庫工程即可。

附:常見問題

1. so庫與ABI選擇

由於sdk包含了多個ABI(包括armeabi, armeabi-v7a, arm64-v8a, x86, x86_64)的so庫,而應用可能只採用了一種ABI(如armeabi-v7a),所以導致接入sdk後有些cpu架構讀取不到應用中的so檔案。
解決辦法:在專案gradle指令碼中新增:

android {
    defaultConfig {
        ndk {
            abiFilters "armeabi-v7a"
        }
    }
}

這樣既可過濾sdk中其他ABI的so庫。

2. DuplicateFileException:

Error:Execution failed for task ':Grow:transformResourcesWithMergeJavaResForDebug'.
com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/NOTICE
      File1: /Users/wlg/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.8.0/eeed20590bf2a6e367e6e5ce33f44d353881fa23/jackson-core-2.8.0.jar
      File2: /Users/wlg/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.8.0/95505afd940fedb0d674a83583ae65a9c25ec9f/jackson-databind-2.8.0.jar

這是由於jar包中的META-INF/NOTICE衝突,解決辦法:在專案gradle指令碼中新增:

packagingOptions {
    exclude 'META-INF/LICENSE'
    exclude 'META-INF/NOTICE'
}

參考文獻