Gradle系列之Android Gradle高級配置

本篇文章主要在以前學習的基礎上,從實際開發的角度學習如何對 Android Gradle 來進行自定義以知足不一樣的開發需求,下面是 Gradle 系列的幾篇文章:java

下面是主要內容:android

  1. 修改生成的Apk文件名
  2. 版本信息統一管理
  3. 隱藏簽名文件信息
  4. 動態配置AndroidManifest文件
  5. 自定義BuildConfig
  6. 動態添加自定義資源
  7. Java編譯選項
  8. adb操做選項配置
  9. DEX選項配置
  10. 自動起立未使用的資源
  11. 突破65535方法限制

修改生成的Apk文件名

修改打包輸出的 Apk 的文件名主要用到三個屬性:安全

applicationVariants //Android應用Gradle插件
libraryVariants     //Android庫Gradle插件
testVariants        //上述兩種插件都適用

下面是修改打包生成的 Apk 文件名的代碼,參考以下:服務器

android{
    //...
    
    /**
     * 修改打包生成的apk的文件名
     */
    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            if (output.outputFile != null && output.outputFile.name.endsWith('.apk') &&
                    'release' == variant.buildType.name) {
                //輸出文件名
                outputFileName = "AndroidGradleProject_v${variant.versionName}_${buildTime()}.apk"
            }
        }
    }   
}
//當前時間
def static buildTime() {
    def date = new Date()
    return date.format("yyyMMdd")
}

此時,執行 release 模式構建 Apk 的任務,生成的 Apk 的名字就修改了,固然還能夠配置在 debug 模式下生成對應的文件名等。app

版本信息統一管理

每一個應用都有一個版本,版本通常由三部分組成:major.minor.patch,第一個是主版本號,第二個是副版本號,第三個是補丁號,如 1.0.0 這種格式的版本號,在 Android 開發中最原始的版本配置方式就是在 build.gradle 中在 defaultConfig 中配置對應的版本號和版本名稱,參考以下:ide

//最原始的版本配置方式
android{
    defaultConfig {
        versionCode 1
        versionName "1.0"
        //...
    }
}

實際開發中通常將這種版本相關的信息單獨定義在一個獨立的版本管理文件中進行統一管理,定義 version.gradle 文件以下:post

ext{
    //應用版本號、版本名稱
    appversionCode = 1
    appVersionName = "1.0"
    //其餘版本號...
}

而後在 build.gradle 中使用 version.gradle 文件中定義的版本號、版本名稱便可,參考以下:性能

//引入version.gradle文件
apply from: "version.gradle"
android {
    //...
    defaultConfig {
        //使用version.gradle裏定義的版本號
        versionCode appversionCode
        //使用version.gradle裏定義的版本名稱
        versionName appVersionName
        //...
    }
}

固然不僅是應用的版本號,還有使用到的一些第三方的庫的版本也可使用這樣的方式來統一管理。學習

隱藏簽名文件信息

簽名文件信息是很是重要的信息,若是將簽名文件信息直接配置到項目中將是不安全的,那麼簽名文件如何可以安全呢,簽名文件放在本地是不安全的,那麼只能放在服務器上纔是安全的,打包的時候從服務器上讀取簽名文件信息便可,固然這個服務器也能夠是一臺專門用於打包正式 Apk 的電腦,將簽名文件和密鑰信息配置成環境變量,打包是直接從環境變量中讀取簽名文件和密鑰信息便可。測試

配置四個環境變量 STORE_FILE、STORE_PASSWORD、KEY_ALIAS、KEY_PASSWORD 分別對應簽名文件、簽名文件密碼、簽名文件密鑰別名、簽名文件密鑰密碼,環境變量的配置就不具體說了,代碼參考以下:

android {
    //簽名文件配置
    signingConfigs {
        //讀取配置的與簽名文件信息對應的環境變量
        def appStoreFile = System.getenv('STORE_FILE')
        def appStorePassword = System.getenv('STORE_PASSWORD')
        def appKeyAlias = System.getenv('KEY_ALIAS')
        def appKeyPassword = System.getenv('KEY_PASSWORD')
        //若是獲取不到相關簽名文件信息,則使用默認的簽名文件
        if(!appStoreFile || !appStorePassword || !keyAlias || !keyPassword){
            appStoreFile = "debug.keystore"
            appStorePassword = "android"
            appKeyAlias = "androiddebugkey"
            appKeyPassword = "android"
        }
        release {
            storeFile file(appStoreFile)
            storePassword appStorePassword
            keyAlias appKeyAlias
            keyPassword appKeyPassword
        }
        debug {
            //默認狀況下,debug模式下的簽名已配置爲Android SDK自動生成的debug簽名文件證書
            //.android/debug.keystore
        }
    }
}

注意一點,配置好環境變量後,若是不能讀取到新配置的環境變量,重啓電腦後就能讀取到了,至於如何使用專用的服務器進行打包、讀取簽名文件信息實踐後再來介紹。

動態配置AndroidManifest文件

動態配置 AndroidManifest 配置就是動態的去修改 AndroidManifest 文件中的一些內容,如友盟等第三方統計平臺分析統計的時候,通常會要求要在 AndroidManifest 文件中指定渠道名稱,以下所示:

<meta-data android:value="CHANNEL_ID" android:name="CHANNEL"/>

這裏 CHANNEL_ID 要替換成不一樣渠道的名稱,如 baidu、miui 等各個渠道名稱,那麼如何動態的修改這些變化的參數呢,這裏須要用到 Manifest 佔位符和 manifestPlaceholder,manifestPlaceholder 是 ProductFlavor 的一個屬性,是一個 Map 類型,能夠配置多個佔位符,具體代碼參考以下:

android{
    //維度
    flavorDimensions "channel"
    productFlavors{
        miui{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","google")
        }
        baidu{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","baidu")
        }
    }
}

上述代碼中配置了 flavorDimensions 屬性,這個屬性能夠理解爲維度,好比 release 和 debug 是一個維度、不一樣的渠道是一個維度、免費版本仍是付費版本又是一個維度,若是這三個維度都要考慮,那麼生成 Apk 的格式就是 2 * 2 * 2 供 8 個不一樣的 Apk,從 Gradle 3.0 開始不論是一個維度仍是多個維度,都必須使用 flavorDimensions 來約束,上面代碼中定義了一個維度 channel,再加上 buildType 中的 debug 和 release ,故此時生成不一樣 Apk 的個數是 4 個,以下圖所示:

channel.jpg

固然,若是沒有配置 flavorDimensions 則會出現以下錯誤,具體以下:

Error:All flavors must now belong to a named flavor dimension.

實際開發中根據實際狀況配置對應的 flavorDimensions 便可。

而後,在 AndroidManifest 文件中使用佔位符介紹打包時傳遞過來的參數,在 AndroidManifest 文件中添加 以下:

<meta-data android:value="${CHANNEL}" android:name="channel"/>

最後,執行對應的渠道包任務,如執行 assembleBaiduRelease 將會將 AndroidManifest 中的渠道替換成 baidu,可以使用命令執行也可以使用 Android Studio 選擇對應的 task 來執行,執行命令以下:

gradle assembleBaiduRelease

若是使用 Android Studio ,打開右側 Gradle 控制面板,找到對應的 task 來執行相應的任務,以下圖所示:

assembleBaiduRelease

選擇對應的 task 執行就會生成對應的 Apk,使用 Android Killer 反編譯打開生成的 Apk ,查看 AndroidManifest 文件以下:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.manu.androidgradleproject">
    <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" roundIcon="@mipmap/ic_launcher_round">
        <!--AndroidManifest文件修改爲功-->
        <meta-data android:name="channel" android:value="baidu"/>
        <activity android:name="com.manu.androidgradleproject.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <meta-data android:name="android.support.VERSION" android:value="26.1.0"/>
        <meta-data android:name="android.arch.lifecycle.VERSION" android:value="27.0.0-SNAPSHOT"/>
    </application>
</manifest>

上述案列中,渠道的名稱是一致的,能夠經過遍歷很方便的完成渠道名稱的替換,參考以下:

productFlavors.all{ flavor ->
    manifestPlaceholders.put("CHANNEL",name)
}

這一小節重要的一點就是關於 manifestPlaceholders 佔位符的使用。

自定義BuildConfig

BuildConfig 是一個在 Android Gradle 構建腳本編譯後生成的類,默認構建生成的 BuildConfig 內容以下:

/**
 * Automatically generated file. DO NOT MODIFY
 */
package com.manu.androidgradleproject;

public final class BuildConfig {
  public static final boolean DEBUG = false;
  public static final String APPLICATION_ID = "com.manu.androidgradleproject";
  public static final String BUILD_TYPE = "release";
  public static final String FLAVOR = "baidu";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0";
}

上面 BuildConfig 中的一些常量都是關於應用的一些關鍵信息,其中 DEBUG 在 debug 模式下爲 true,release 模式下爲 false,此外還有應用包名、構建類型、構建渠道、版本號及版本名稱,因此若是開發中須要用到這些值能夠在 BuildConfig 中直接獲取,好比包名的獲取通常是 context.getPackageName(),若是直接從 BuildConfig 中獲取是否是不只方便並且有利於應用性能提高,因此,可在構建時在該文件中添加一些額外的有用的信息,可使用 buildConfigField 方法,具體以下:

/**
 * type:生成字段的類型
 * name:生成字段的常量名稱
 * value:生成字段的常量值
 */
public void buildConfigField(String type, String name, String value) {
    //...
}

下面使用 buildConfigField 方法爲每一個渠道配置一個相關地址,參考以下:

android{
    //維度
    flavorDimensions "channel"
    productFlavors{
        miui{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","miui")
            buildConfigField 'String' ,'URL','"http://www.miui.com"'
        }
        baidu{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","baidu")
            //buildConfigField方法參數value中的內容是單引號中的,若是value是String,則String的雙引號不能省略
            buildConfigField 'String' ,'URL','"http://www.baidu.com"'
        }
    }
}

再打包時就會自動生成添加的字段,構建完成後查看 BuildConfig 文件,生成了上面添加的字段,參考以下:

/**
 * Automatically generated file. DO NOT MODIFY
 */
package com.manu.androidgradleproject;

public final class BuildConfig {
  public static final boolean DEBUG = false;
  public static final String APPLICATION_ID = "com.manu.androidgradleproject";
  public static final String BUILD_TYPE = "release";
  public static final String FLAVOR = "baidu";
  public static final int VERSION_CODE = -1;
  public static final String VERSION_NAME = "";
  // Fields from product flavor: baidu
  public static final String URL = "http://www.baidu.com";
}

至此,自定義 BuildConfig 的學習就到此爲止,固然 buildConfigField 也可使用到構建類型中,關鍵就是 buildConfigField 方法的使用。

動態添加自定義資源

Android 開發中資源文件都是放置在 res 目錄下,還能夠在 Android Gradle 中定義,自定義資源須要使用到 resValue 方法,該方法在 BuildType 和 ProductFlavor 對象中可使用,使用 resValue 方法會生成相對應的資源,使用方式和在 res/values 文件中定義的同樣

android{
    //...
    productFlavors {
        miui {
            //...
           /**
            * resValue(String type,String name,String value)
            * type:生成字段的類型(id、string、bool等)
            * name:生成字段的常量名稱
            * value:生成字段的常量值
            */
            resValue 'string', 'welcome','miui'
        }

        baidu {
            //...
            resValue 'string', 'welcome','baidu'
        }
    }

}

當生成不一樣的渠道包時,經過 R.string.welcome 獲取的值是不相同的,如生成的百度的渠道包時 R.string.welcome 的值爲 baidu、生成小米渠道包時 R.string.welcome 的值爲 miui,構建時生成的資源的位置在 build/generated/res/resValues/baidu/... 下面的 generated.xml 文件中,文件內容參考以下:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- Automatically generated file. DO NOT MODIFY -->

    <!-- Values from product flavor: baidu -->
    <string name="welcome" translatable="false">baidu</string>

</resources>

Java編譯選項

在 Android Gradle 中還能夠配置 Java 源代碼的編譯版本,這裏使用到 compileOptions 方法, compileOptions 可配置三個屬性:encoding、sourceCompatibility 和 targetCompatibility,經過這些屬性來配置 Java 相關的編譯選項,具體參考以下:

//配置Java編譯選項
android {
    compileSdkVersion 26
    buildToolsVersion '26.0.2'
    compileOptions{
        //設置源文件的編碼
        encoding = 'utf-8'
        //設置Java源代碼的編譯級別()
        sourceCompatibility = JavaVersion.VERSION_1_8
//        sourceCompatibility  "1.8"
//        sourceCompatibility  1.8
//        sourceCompatibility  "Version_1_8"
        //設置Java字節碼的版本
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

adb操做選項設置

adb 的全稱是 Android Debug Bridge,adb 主要用來鏈接手機來進行一些操做,好比調試 Apk、安裝 Apk、複製文件等操做,在 Android Gradle 中可藉助 adpOptions 來配置,可配置的有兩個屬性:installOptions 和 timeOutInMs,也能夠經過相應的 setter 方法來設置,具體參考以下:

android{
    //adb配置選項
    adbOptions{
        //設置執行adb命令的超時時間
        timeOutInMs = 5 * 1000
        /**
         * 設置adb install安裝這個操做的設置項
         * -l:鎖定應用程序
         * -r:替換已存在的應用程序
         * -t:容許測試包
         * -s:把應用程序安裝到SD卡上
         * -d:容許應用程序降級安裝
         * -g:爲該應用授予全部運行時的權限
         */
        installOptions '-r', '-s'
    }    
}

installOptions 的配置對應 adb install [-lrtsdg] 命令,若是安裝、運行或調試 Apk 的時候,若是出現 CommandRejectException 能夠嘗試設置 timeOutInMs 來解決,單位是毫秒。

DEX選項配置

Android 中的源代碼被編譯成 class 字節碼,在打包成 Apk 的時候又被 dx 命令優化成 Android 虛擬機可執行的 DEX 文件,DEX 格式的文件是專爲 Android 虛擬機設計的,在必定程度上會提升其運行速度,默認狀況下給 dx 分配的內存是 1024M,在 Android Gradle 中能夠經過 dexOptions 的五個屬性:incremental、javaMaxHeapSize、jumboMode、threadCount 和 preDexLibraries 來對 DEX 進行相關配置,具體參考以下:

android{
    //DEX選項配置
    dexOptions{
        //設置是否啓用dx增量模式
        incremental true
        //設置執行dx命令爲其分配的最大堆內存
        javaMaxHeapSize '4g'
        //設置是否開啓jumbo模式,若是項目方法數超過65535,須要開啓jumbo模式才能構建成功
        jumboMode true
        //設置Android Gradle運行dx命令時使用的線程數量,可提升dx執行的效率
        threadCount 2
        /**
         * 設置是否執行dex Libraries庫工程,開啓後會提升增量構建的速度,會影響clean的速度,默認爲true
         * 使用dx的--multi-dex選項生成多個dex,爲避免和庫工程衝突,可設置爲false
         */
        preDexLibraries true
    }
}

自動清理未使用資源

Android 開發中打包 Apk 老是但願在相同功能的狀況下 Apk 體積儘可能小,那就要在打包以前刪除沒有使用的資源文件或打包時不將無用的資源打包到 Apk 中,可使用 Android Lint 檢查未使用的資源,可是沒法清除一些第三方庫中的無用資源,還可使用 Resource Shrinking,可在打包以前檢查資源,若是沒有使用則不會被打包到 Apk 中,具體參考以下:

//自動清理未使用資源
android{
    buildTypes {
        release {
            //開啓混淆,保證某些資源在代碼中未被使用,以便於自動清理無用資源,二者配合使用
            minifyEnabled true
            /**
             * 打包時會檢查全部資源,若是沒有被引用,則不會被打包到Apk中,會處理第三方庫不使用的資源
             * 默認不啓用
             */
            shrinkResources true
            //開啓zipalign優化
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug{
        }
    }
    //...
}

爲防止有用資源未被打包到 Apk 中,Android Gradle 提供了 keep 方法來配置那些資源不被清理,在 res/raw/ 下新建一個 xml 文件來使用 keep 方法,參考以下:

<!--keep.xml文件-->
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/l_used"
    tools:shrinkMode="safe"/>

可配置的三個屬性:keep 表示要保留的資源文件,可以使用以(,)分割的資源列表,可以使用(*)做爲通配符,discard 表示要移除的資源,和 keep 相似,shrinkMode 用於設置自動清理資源的模式,通常設置爲 safe 便可,若是設置爲 strict 則有可能清除可能會使用的資源。

此外,還可使用 ProductFlavor 提供的方法 resConfigs 和 resConfig,可配置那些資源打包到 Apk 中,使用方式以下:

android{
    defaultConfig{
       //參數能夠是Android開發時的資源限定符
        resConfigs 'zh'
        //...
    }
}

上述自動清理資源的方式只是不打包到 Apk 中,在實際的項目中並無被清除,可經過日誌查看哪些資源被清理了,而後決定要不要在項目中清除。

突破65535方法限制

在 Android 開發中總會遇到方法數大於 65535 時出現異常,那爲何會有這個限制呢,由於 Java 源文件被打包成一個 DEX 文件,這個文件是優化過的、可在 Dalvik 虛擬機上可執行的文件,因爲 Dalvik 在執行 DEX 文件的時候,使用了 short 來索引 DEX 文件中的方法,這就意味着單個 DEX 文件可被定義的方法最多隻有 65535 個。解決辦法天然是當方法數超過 65535 個的時候建立多個 DEX 文件。

從 Android 5.0 開始的 Android 系統使用 ART 的運行方式,原生支持多個 DEX 文件,ART 在安裝 App 的時候執行預編譯,把多個 DEX 文件合併成一個 oat 文件執行,在 Android 5.0 以前,Dalvik 虛擬機只支持單個 DEX 文件,要想突破單個 DEX 方法數超過 65535 的限制,需使用 Multidex 庫,這裏就不在贅述了。

總結

​本篇文章的不少內容均可以用到實際開發中,這篇文章也是在邊學習邊驗證的狀況下完成的,斷斷續續花了一週時間,距離上次更文已有一週時間,但願閱讀此文可以對你有所幫助。

能夠關注公衆號:零點小築(jzman-blog),一塊兒交流學習。

相關文章
相關標籤/搜索