Gradle系列-運用篇

午後一小憩發表於2019-05-30

Gradle系列-運用篇

上次我們說到gradle的原理,主要是偏理論上的知識點,直通車在這Android Gradle系列-原理篇。這次我們來點實戰的,隨便鞏固下之前的知識點。

android

在app module下的gradle.build中都有一個android閉包,主要配置都在這裡設定。例如預設配置項:defaultConfig;簽名相關:signingConfig;構建變體:buildTypes;產品風格:productFlavors;源集配置:sourceSets等。

defaultConfig

對於defaultConfig其實它是也一個productFlavor,只不過這裡是用來提供預設的設定項,如果之後的productFlavor沒有特殊指定的配置都會使用defaultConfig中的預設配置。

public class DefaultConfig extends BaseFlavor {
    @Inject
    public DefaultConfig(
            @NonNull String name,
            @NonNull Project project,
            @NonNull ObjectFactory objectFactory,
            @NonNull DeprecationReporter deprecationReporter,
            @NonNull Logger logger) {
        super(name, project, objectFactory, deprecationReporter, logger);
    }
}
 
public abstract class BaseFlavor extends DefaultProductFlavor implements CoreProductFlavor {
	...
}
複製程式碼

可以看到defaultConfig的超級父類就是DefaultProductFlavor。而在DefaultProductFlavor中定義了許多我們經常見到的配置:VersionCode、VersionName、minSdkVersion、targetSdkVersion與applicationId等等。

有了上面的基礎,那麼在defaultConfig中我們要配置的變數就顯而易見了。

    defaultConfig {
        applicationId "com.idisfkj.androidapianalysis"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
複製程式碼

signingConfigs

signingConfig是用來配置keyStore,我們可以針對不同的版本配置不同的keyStore,例如

    signingConfigs {
        config { //預設配置
            storeFile file('key.store')
            storePassword 'android123'
            keyAlias 'android'
            keyPassword 'android123'
        }
        dev { //dev測試版配置
            storeFile file('xxxx')
            storePassword 'xxx'
            keyAlias 'xxx'
            keyPassword 'xxx'
        }
    }
複製程式碼

有人可能會說這不安全的,密碼都是明文,都暴露出去了。是的,如果這專案釋出到遠端,那麼這些祕鑰就洩露出去了。所以為了安全起見,我們可以對其進一些特殊處理。

  1. 通過環境變數獲取祕鑰
storePassword System.getenv("KSTOREPWD")
keyPassword System.getenv("KEYPWD")
複製程式碼
  1. 從命令列中獲取祕鑰
storePassword System.console().readLine("\nKeystore password: ")
keyPassword System.console().readLine("\nKey password: ")
複製程式碼

上面兩種是Android Develop官網提供的,但經過測試都會報null異常,查了下資料都說是gradle不支援(如果有成功的可以告知我),所以還是推薦下面的這種方法

在專案的根目錄下(settings.gradle平級)建立keystore.properties檔案,我們在這個檔案中進行儲存祕鑰,它是支援key-value模式的鍵值對資料

storePassword = android123
keyPassword = android123
複製程式碼

之後就是讀取其中的password,在build.gradle通過afterEvaluate回撥進行讀取與設定

afterEvaluate {
    def propsFile = rootProject.file('keystore.properties')
    def configName = 'config'
    if (propsFile.exists() && android.signingConfigs.hasProperty(configName)) {
        def props = new Properties()
        props.load(new FileInputStream(propsFile))
        android.signingConfigs[configName].keyPassword = props['keyPassword']
        android.signingConfigs[configName].storePassword = props['storePassword']
    }
}
複製程式碼

我們已經通過動態讀取了password,所以在之前的signingConfigs中就無需再配置password

    signingConfigs {
        config {
            storeFile file('key.store')
            keyAlias 'android'
        }
    }
複製程式碼

最後一步,為了保證祕鑰的安全性,在.gitignore中新增keystore.properties的忽略配置,防止上傳到遠端倉儲暴露祕鑰。

buildTypes

構建變體主要用來配置shrinkResources:資源是否需要壓縮、zipAlignEnabled:壓縮是否對齊、minifyEnabled:是否程式碼混淆與signingConfig:簽名配置等等。新建專案時,預設有一個release配置,但我們實際開發中可能需要多個不同的配置,例如debug模式,為了方法除錯,一般都不需要對其進行程式碼混淆、壓縮等處理。或者outer模式,需要的簽名配置不同,所以最終的配置可以是這樣:

    buildTypes {
        debug {
            minifyEnabled false
            zipAlignEnabled false
            shrinkResources false
            signingConfig signingConfigs.config
        }
        outer {
            minifyEnabled false
            zipAlignEnabled false
            shrinkResources false
            signingConfig signingConfigs.outConfig
        }
        release {
            minifyEnabled true
            zipAlignEnabled true
            shrinkResources true
            signingConfig signingConfigs.config
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
複製程式碼

Sync Now之後,開啟Android Studio 右邊的Gradle,找到app->Tasks->build,發現已經新增了assembleDebug與assembleOuter構建task。

productFlavors

一個專案可能有不同的版本環境,例如開發功能中的開發版、專案上線的正式版。開發版與正式版請求的資料api可能不同,對於這種情況我們就可以使用productFlavor來構建不同的產品風格,可以看下面的dev與prod配置

    flavorDimensions "mode"
    productFlavors {
        dev {
            applicationIdSuffix ".dev"
            dimension "mode"
            manifestPlaceholders = [PROJECT_NAME: "@string/app_name_dev",
                                    APP_ID      : "21321843"]
            buildConfigField 'String', 'API_URL', '"https://dev.idisfkj.android.com"'
            buildConfigField 'String', 'APP_KEY', '"3824yk32"'
        }
        prod {
            applicationIdSuffix ".prod"
            dimension "mode"
            manifestPlaceholders = [PROJECT_NAME: "@string/app_name",
                                    APP_ID      : "12932843"]
            buildConfigField 'String', 'API_URL', '"https://prod.idisfkj.android.com"'
            buildConfigField 'String', 'APP_KEY', '"32143dsk2"'
        }
    }
複製程式碼

對於判斷是否為同一個app,手機系統是根據app的applicationId來識別的,預設applicationId是packageName。所以為了讓dev與prod的版本都能共存在一個手機上,可以通過applicationIdSuffix來為applicationId增加字尾,改變安裝包的唯一標識。

還有可以通過manifestPlaceholders來配置可用於AndroidManifest中的變數,例如根據不同的產品風格顯示不同的app名稱

dev與prod網路請求時使用不同的api host,可以設定buildConfigField,這樣我們就可以在程式碼中通過BuildConfig獲取

    fun getApiUlr(): String {
        return BuildConfig.API_URL
    }
複製程式碼

這裡的BuildConfig會根據你構建的產品風格返回不同的值,它位於build->generated->source->buildConfig->變體,大致內容如下:

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.idisfkj.androidapianalysis.dev";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "devMinApi21";
  public static final int VERSION_CODE = 20001;
  public static final String VERSION_NAME = "1.0-minApi21";
  public static final String FLAVOR_mode = "dev";
  public static final String FLAVOR_api = "minApi21";
  // Fields from product flavor: dev
  public static final String API_URL = "https://dev.idisfkj.android.com";
  public static final String APP_KEY = "3824yk32";
}
複製程式碼

Sync Now之後,開啟Android Studio 右邊的Gradle,找到app->Tasks->build,發現新新增了assembleDev與assembleProd構建task。

flavorDimensions是用來設定多維度的,上面的例子只展示了一個維度,所以dimension為mode的形式。我們新增一個api維度,構建不同的minSkdVerison版本的apk

    flavorDimensions "mode", "api"
    productFlavors {
        dev {
            applicationIdSuffix ".dev"
            dimension "mode"
            manifestPlaceholders = [PROJECT_NAME: "@string/app_name_dev",
                                    APP_ID      : "21321843"]
            buildConfigField 'String', 'API_URL', '"https://dev.idisfkj.android.com"'
            buildConfigField 'String', 'APP_KEY', '"3824yk32"'
        }
        prod {
            applicationIdSuffix ".prod"
            dimension "mode"
            manifestPlaceholders = [PROJECT_NAME: "@string/app_name",
                                    APP_ID      : "12932843"]
            buildConfigField 'String', 'API_URL', '"https://prod.idisfkj.android.com"'
            buildConfigField 'String', 'APP_KEY', '"32143dsk2"'
        }
        minApi16 {
            dimension "api"
            minSdkVersion 16
            versionCode 10000 + android.defaultConfig.versionCode
            versionNameSuffix "-minApi16"
        }
        minApi21 {
            dimension "api"
            minSdkVersion 21
            versionCode 20000 + android.defaultConfig.versionCode
            versionNameSuffix "-minApi21"
        }
    }
複製程式碼

gradle建立的構建變體數量等於每個風格維度中的風格數量與你配置的構建型別數量的乘積,所以上面例子的構建變體數量為12個。在gradle為每個構建變體或對應apk命名時,屬於較高優先順序風格維度的產品風格首先顯示,之後是較低優先順序維度的產品風格,再之後是構建型別。而優先順序的判斷則以flavorDimensions的值順序為依據,以上面的構建配置為例:

構建變體:[dev, prod][minApi16, minApi21][debug, outer, release] 對應apk:app-[dev, prod]-[minApi16, minApi21]-[debug, outer, release].apk

構建變體有這麼多,但有時我們並不全部需要,例如你不需要mode為dev,api為minApi16的變體,這時你就可以使用variantFilter方法來過濾

    variantFilter { variant ->
        def names = variant.flavors*.name
        if (names.contains("minApi16") && names.contains("dev")) {
            setIgnore(true)
        }
    }
複製程式碼

你再回到app->Tasks中檢視變體,會發現已經將devMinApi16相關的變體過濾了。

你不僅可以過濾構建變體,還可以改變預設的apk輸出名稱。例如你想修改buildType為release的apk名稱,這時你可以使用android.applicationVariants.all

    android.applicationVariants.all { variant ->
        if (variant.buildType.name == buildTypes.release.name) {
            variant.outputs.all {
                outputFileName = "analysis-release-${defaultConfig.versionName}.apk"
            }
        }
    }
複製程式碼

這樣在release下的包名都是以analysis打頭

sourceSets

Android Studio會幫助我們建立預設的main源集與目錄(位於app/src/main),用來儲存所有構建變體間的共享資源。所以你可以通過設定main源集來更改預設的配置。例如現在你想將res的路徑修改成src/custom/res

    sourceSets {
        main {
            res.srcDirs = ['src/custom/res']
        }
    }
複製程式碼

這樣res資源路徑就定位到了src/custom/res下,當然你也可以修改其它的配置,例如java、assets、jni等。

如果你配置了多個路徑,即路徑集合:

    sourceSets {
        main {
            res.srcDirs = ['src/custom/res', 'scr/main/res']
        }
    }

複製程式碼

這時你要保證不能有相同的名稱,即每個檔案只能唯一存在其中一個目錄下。

你也可以檢視所以的構建變體的預設配置路徑: 點選右邊gradle->app->android->sourceSets,你將會看到如下類似資訊

------------------------------------------------------------
Project :app
------------------------------------------------------------
 
androidTest
-----------
Compile configuration: androidTestCompile
build.gradle name: android.sourceSets.androidTest
Java sources: [app/src/androidTest/java]
Manifest file: app/src/androidTest/AndroidManifest.xml
Android resources: [app/src/androidTest/res]
Assets: [app/src/androidTest/assets]
AIDL sources: [app/src/androidTest/aidl]
RenderScript sources: [app/src/androidTest/rs]
JNI sources: [app/src/androidTest/jni]
JNI libraries: [app/src/androidTest/jniLibs]
Java-style resources: [app/src/androidTest/resources]
...
複製程式碼

上面是androidTest變體的預設路徑,首先它會去查詢相應的構建變體的預設位置,如果沒有找到,就會使用main源集下的預設配置。也就是我們所熟悉的app/src/main路徑下的資源。

因為它是跟構建變體來搜尋的,所以它有個優先順序:

  1. src/modeApiDebug: 構建變體
  2. src/debug:構建型別
  3. src/modeApi:產品風格
  4. src/main:預設main源

對於源集的建立,如下所示在app/src下右鍵新建,但它只會幫你建立源集下的java資料夾,其它的都要你自己逐個建立

Gradle系列-運用篇

我們自定義一個debug源集,所以進去之後Target Source Set選擇debug,再點選finish結束。這時你將會在src下看到debug資料夾

現在你已經有了debug的源集目錄,假設你現在要使debug下的app名稱展示成Android精華錄debug(預設是Android精華錄)。這時你可以右鍵debug新建values

Gradle系列-運用篇

在values目錄下新建strings.xml,然後在其中配置app_name

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Android精華錄debug</string>
</resources>
複製程式碼

最後你再去構建debug相關的變體時,你安裝的app展示的名稱將是Android精華錄debug。

所以通過修改mian源集或者配置其它的變體源集,可以實現根據變體載入不同的資料來源。這樣系統化的配置載入資源將更加方便專案測試與版本需要的配置。

dependencies

dependencies閉包上用來配置專案的第三方依賴,如果你根據上面的配置有設定變體,那麼你將可以根據變體來選擇性的依賴第三方庫

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    //根據變體選擇性依賴
    outerImplementation '...'
    prodMinApi21Implementation '...'
}
複製程式碼

關於dependencies,這只是簡單的配置方式,之後我還會單獨抽出一篇文章來寫系統化的配置dependencies,感興趣的可以關注下。

gradle相關的配置還有很多,這裡只是冰山一角,但我的建議是根據你的實際需求去學習與研究,相信你也會有意想不到的成長。

最後附上原始碼地址:github.com/idisfkj/and…

部落格地址:www.rousetime.com/

系列

Android Gradle系列-入門篇

Android Gradle系列-原理篇

Gradle系列-運用篇

相關文章