Android Studio 使用 Gradle 打包 Jar

chaosleong發表於2015-08-04

Android Studio 打 Jar 包一直是一個麻煩的事,按照網上現有的教程,打包一個混淆的 jar 需要完成下列步驟:

  1. 將 plugin 修改為library後 build 出 aar,再提取 aar 裡面的 classes.jar
  2. 使用 jarjar 等工具剔除多餘的 class
  3. 對第二步得到的 jar 進行混淆

無論哪一步,所做的工作量都不少。於我個人而言,相當麻煩,於是花了些時間研究了下 Gradle 打 Jar 包。

程式碼

廢話不多說,先上程式碼( 注 :只在 Gradle Android Plugin 1.2.3 測試過)

build.gradle
 import com.android.build.gradle.AppPlugin
import proguard.gradle.ProGuardTask

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "org.chaos.demo.jar"
        minSdkVersion 22
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

//dependsOn 可根據實際需要增加或更改
task buildJar(dependsOn: ['compileReleaseJava'], type: Jar) {

    appendix = "demo"
    baseName = "androidJar"
    version = "1.0.0"
    classifier = "release"

    //字尾名
    extension = "jar"
    //最終的 Jar 包名,如果沒設定,預設為 [baseName]-[appendix]-[version]-[classifier].[extension]
    archiveName = "AndroidJarDemo.jar"

    //需打包的資源所在的路徑集
    def srcClassDir = [project.buildDir.absolutePath + "/intermediates/classes/release"];
    //初始化資源路徑集
    from srcClassDir

    //去除路徑集下部分的資源
//    exclude "org/chaos/demo/jar/MainActivity.class"
//    exclude "org/chaos/demo/jar/MainActivity/$*.class"
    exclude "org/chaos/demo/jar/BuildConfig.class"
    exclude "org/chaos/demo/jar/BuildConfig/$*.class"
    exclude "**/R.class"
    exclude "**/R/$*.class"

    //只匯入資源路徑集下的部分資源
    include "org/chaos/demo/jar/**/*.class"

    //注: exclude include 支援可變長引數
}

task proguardJar(dependsOn: ['buildJar'], type: ProGuardTask) {
    //Android 預設的 proguard 檔案
    configuration android.getDefaultProguardFile('proguard-android.txt')
    //會根據該檔案對 Jar 進行混淆,注意:需要在 manifest 註冊的元件也要加入該檔案中
    configuration 'proguard-rules.pro'

    String inJar = buildJar.archivePath.getAbsolutePath()
    //輸入 jar
    injars inJar
    //輸出 jar
    outjars inJar.substring(0, inJar.lastIndexOf('/')) + "/proguard-${buildJar.archiveName}"

    //設定不刪除未引用的資源(類,方法等)
    dontshrink

    AppPlugin appPlugin = getPlugins().findPlugin(AppPlugin)
    if (appPlugin != null) {
        List<String> runtimeJarList
        if (appPlugin.getMetaClass().getMetaMethod("getRuntimeJarList")) {
            runtimeJarList = appPlugin.getRuntimeJarList()
        } else if (android.getMetaClass().getMetaMethod("getBootClasspath")) {
            runtimeJarList = android.getBootClasspath()
        } else {
            runtimeJarList = appPlugin.getBootClasspath()
        }

        for (String runtimeJar : runtimeJarList) {
            //給 proguard 新增 runtime
            libraryjars(runtimeJar)
        }
    }
}

為什麼已在 manifest 註冊的元件需要在 .pro 檔案宣告對應的混淆規則?

可能各位注意到 proguardJar task 的第二行註釋,在 apk 的打包過程中,aapt 會在解析 manifest 後生成一個用於不混淆 manifest 中已註冊的元件的規則檔案。然而我們的 task 只是依賴於compileReleaseJava(該 task 在執行 aapt 前), Gradle Android Plugin 中配置上述 aapt 生成的規則檔案的程式碼如下:

BasePlugin.groovy
 protected File createProguardTasks(@NonNull BaseVariantData variantData,
                                       @Nullable BaseVariantData testedVariantData) {
    ......
  // also the config file output by aapt
  proguardTask.configuration(variantData.processResourcesTask.proguardOutputFile)
  ......
}

礙於技術原因,獲取不到processResourcesTask的例項,所以目前只能先新增對應的元件到規則檔案中,還望知道怎麼獲取的朋友能夠分享下,謝謝。

使用方法

不需要混淆則執行命令

gradle buildJar
或
./gradlew buildjar

需要混淆則執行

gradle proguardJar
或
./gradlew proguardJar

總結

buildJar這部分相對比較簡單,很多內容網上都有教程。關鍵在於混淆,由於需要匯入 runtime 相關的 jar,雖說可以寫死 runtime 的路徑,但是團隊每個人都有自己的安裝習慣,路徑不一定一致,於是乎看原始碼翻了一段時間才找到相應的程式碼。至於想更多個性化的朋友,建議從原始碼入手。

相關文章