博客主頁html
Android Gradle中有不少相同的任務,這些任務的名字都是經過Build Types 和 Product Flavors 動態建立和生成的。java
若是修改生成的apk文件名,就要修改Android Gradle打包的輸出。Android對象提供了3個屬性:applicationVariants 僅僅用於Android應用Gradle插件,libraryVariants 僅僅適用於Android庫Gradle插件,testVariants 以上兩種Gradle插件都適用。android
這3個屬性返回的都是DomainObjectSet對象集合,裏面元素分別是ApplicationVariant,LibraryVariant,TestVariant ,這3個元素都是變體(就是Android構建產物)。如:ApplicationVariant表示Baidu渠道的release包,是基於Build Types 和 Product Flavors 生成的產物。segmentfault
android { buildTypes { release { minifyEnabled true signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt') } debug { minifyEnabled false } } productFlavors { //發佈使用 arm { } //開發使用 dev { } } // variant就是生成的產物,共有armRelease,armDebug,devRelease,devDebug四個產物 applicationVariants.all { variant -> variant.outputs.all { output -> println "applicationVariants>>>>> outputFile: ${output.outputFile}, name: ${output.outputFile.name}" println "applicationVariants>>>>> flavorName: ${variant.flavorName}, baseName: ${variant.baseName}, name: ${variant.name}" if (output.outputFile != null && output.outputFile.name.endsWith('.apk') && 'debug'.equals(variant.buildType.name)) { def apkFile = new File(output.outputFile.getParent(), "${project.name}-${variant.baseName}-${new Date().format('yyyyMMdd')}.apk") outputFileName = apkFile.name println "output apk file: >>>>>${output.outputFile}" } } } } 其中一個輸出 applicationVariants>>>>> outputFile: D:\work\yqb.com\newCode\merchantApp\app\build\outputs\apk\dev\debug\app-dev-debug.apk, name: app-dev-debug.apk applicationVariants>>>>> flavorName: dev, baseName: dev-debug, name: devDebug output apk file: >>>>>D:\work\yqb.com\newCode\merchantApp\app\build\outputs\apk\dev\debug\app-dev-debug-20190903.apk
版本通常由3個部分構成:major.minor.patch,版本號.副版本號.補丁號api
原始配置方式,比較直觀。最大問題就是修改不方便安全
android { defaultConfig { applicationId "com.kerwin" minSdkVersion 19 targetSdkVersion 26 versionCode 100 versionName "1.0.0" } }
能夠分模塊方式配置,將版本號的配置單獨抽取出來,放在單獨的文件裏,供其餘build引用。Android是能夠經過apply from方式引用app
// 新建config.gradle文件 ext { versionCode = 100 versionName = '1.0.0' }
ext { }塊爲當前project建立擴展屬性。其餘build.gradle中引用後就可使用less
apply from: 'config.gradle'
咱們也能夠從屬性文件中動態獲取,例如建立一個config.properties屬性文件ide
// config.properties versionCode=100 versionName='1.0.0'
而後在build.gradle文件中動態獲取函數
Properties properties = new Properties() if (project.hasProperty("config.properties") && file(project.property("config.properties")).exists()) { properties .load(new FileInputStream(file(project.property("config.properties")))) } if (properties != null && properties .size() > 0) { String versionCode= properties ['versionCode'] String versionName= properties ['versionName'] }
在構建的過程當中,動態修改AndroidManifest文件中內容。在使用友盟第三方分析統計時,要求在AndroidManifest文件中指定渠道名
<meta-data android:name="UMENG_CHANNEL" // ${UMENG_CHANNEL}佔位符,UMENG_CHANNEL是變量名 android:value="${UMENG_CHANNEL}"/>
其中Channel ID要替換成不一樣渠道名,如google,baidu,miui。在構建時,根據生成的不一樣渠道包來指定不一樣的渠道名,Android Gradle提供manifestPlaceholders、Manifest佔位符替換AndroidManifest文件中的內容
android { productFlavors { google { // 是一個屬性,Map類型。key就是在AndroidManifest文件中佔位符變量 manifestPlaceholders.put("UMENG_CHANNEL", "google") } } // 也能夠迭代productFlavors批量修改 productFlavors.all { flavor -> println "productFlavors>>> name: ${flavor.name}" manifestPlaceholders.put("UMENG_CHANNEL", flavor.name) } }
下面是Android Gradle自動生成的
/** * Automatically generated file. DO NOT MODIFY */ package ${packageName}; public final class BuildConfig { public static final boolean DEBUG = Boolean.parseBoolean("true"); public static final String APPLICATION_ID = "${packageName}"; public static final String BUILD_TYPE = "debug"; public static final String FLAVOR = "arm"; public static final int VERSION_CODE = 215; public static final String VERSION_NAME = "2.1.5"; }
還能夠自定義一些常量,動態配置。Android Gradle提供了buildConfigField(@NonNull String type, @NonNull String name, @NonNull String value)能夠自定義常量到BuildConfig中。
android { buildTypes { debug { buildConfigField "String", "testBuildConfig", "\"測試\"" } release { buildConfigField "String", "testBuildConfig", "\"測試\"" } } }
除了能夠res/values文件夾中使用xml的方式定義資源外,還能夠在Android Gradle中定義。
經過**resValue(@NonNull String type,
@NonNull String name, @NonNull String value)**方法,在 BuildType 和 ProjectFlavor 中都存在,能夠針對不一樣的渠道,或者不一樣的構建類型自定義特有資源。
android { buildTypes { debug { // 第一個參數能夠是 string、id、bool、dimen、integer、color resValue "string", "baidu_map_api_key", "\"1234567\"" } release { resValue "string", "baidu_map_api_key", "\"76544321\"" } } }
在下圖目錄中能夠找到生成的自定義資源
能夠經過compileOptions對java源文件編碼、源文件使用的JDK版本配置
android { compileOptions { encoding Charsets.UTF_8.name() // Java源代碼編譯級別,格式能夠是 "1.8" 、1.8 、JavaVersion.VERSION_1_8 、VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8 // 配置生成的Java字節碼的版本 targetCompatibility JavaVersion.VERSION_1_8 } }
android { dexOptions { // 配置最大堆內存 javaMaxHeapSize "4g" // 函數超過65535個時,有時須要開啓jumbo模式才能夠構建成功。默認是false jumboMode = true // 配置是否預執行dex Libraries庫工程,開啓後能夠提升增量構建速度,默認是開啓的 // 當使用dx的--multi-dex選項生成多個dex,會致使和庫工程衝突,應該關閉 preDexLibraries true // Integer類型,配置運行dx命令時使用的線程數 threadCount 4 } }
APK中包含 Dalvik Executable (DEX) 文件形式的可執行字節碼文件,這些DEX文件包含應用運行已編譯代碼。 65,535等於 64 X 1024 - 1
由於Dalvik虛擬機在執行DEX文件時,使用short類型索引DEX文件中方法,單個DEX文件中方法能夠被定義最可能是65535個,當超過就會報錯。
trouble writing output: Too many field references: 131000; max is 65536. You may try using --multi-dex option.
較低版本的編譯系統會報告一個不一樣的錯誤,但指示的是同一問題:
Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
可採用生成多個DEX文件來解決這個問題。
在Android 5.0以後,Android使用ART運行時方式,支持從APK文件加載多個DEX文件,ART在APK安裝時執行預編譯,掃描classesN.dex文件,將多個DEX文件合併成一個.oat文件執行;在 minSdkVersion 爲 21 或者更高,不須要多dex文件支持庫。
https://source.android.google...
在Android 5.0以前,Android使用Dalvik運行,而Dalvik虛擬機限制每一個APK只能使用一個classes.dex字節碼文件,要使用必須使用Multidex庫。
minSdkVersion爲 21 或者 以上,只需將 multiDexEnabled 設置爲 true 就能夠。
當配置multidex後,當超過65535時生成多個dex文件,文件名爲classes.dex,classes2.dex,classesN.dex
android { defaultConfig { // 啓用multidex multiDexEnabled true } }
若是minSdkVersion爲 21 如下(不包括21)
dependencies { // 配置multidex依賴 implementation 'com.android.support:multidex:1.0.1' }
https://developer.android.goo...
// 1. 若是沒有自定義Application,只需在AndroidManifest文件中直接配置MultiDexApplication <application android:name="android.support.multidex.MultiDexApplication" /> // 2. 若是有自定義的Application,而且是直接繼承Application的。能夠將修改成MultiDexApplication public class MMApplication extends MultiDexApplication{ } // 3. 若是自定義的Application已經繼承的第三方提供的Application,就不能繼承了。能夠在從新attachBaseContext方法實現 @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); }
在 MultiDex.install(this)完成以前,不要經過反射或者其餘任何代碼,不然致使ClassNotFoundException
Android 編譯工具會根據須要構建主 DEX 文件 (classes.dex) 和輔助 DEX 文件(classes2.dex 和 classes3.dex 等)。而後,編譯系統會將全部 DEX 文件打包到您的 APK 中。
後續會講解下MultiDex實現原理
可使用dex預處理縮短增量編譯時間。dex 預處理依賴於Android 5.0或以上版本中提供的 ART 格式。Android Studio2.3或以上版本會自動使用此功能。若是命令行運行Gradle編譯。須要設置minSdkVersion 21或以上啓用dex預處理。
一個開發類型dev 和一個發佈類型prod,它們具備不一樣的 minSdkVersion 值,來建立兩個應用版本
android { defaultConfig { ... multiDexEnabled true // The default minimum API level you want to support. minSdkVersion 15 } productFlavors { // Includes settings you want to keep only while developing your app. dev { // Enables pre-dexing for command line builds. When using // Android Studio 2.3 or higher, the IDE enables pre-dexing // when deploying your app to a device running Android 5.0 // (API level 21) or higher—regardless of what you set for // minSdkVersion. minSdkVersion 21 } prod { // If you've configured the defaultConfig block for the production version of // your app, you can leave this block empty and Gradle uses configurations in // the defaultConfig block instead. You still need to include this flavor. // Otherwise, all variants use the "dev" flavor configurations. } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile 'com.android.support:multidex:1.0.3' }
在構建多DEX時, 編譯工具會執行復雜的決策來肯定主DEX文件中須要的類,以便可以成功啓動。若是主DEX文件中沒有提供啓動時須要的任何類,就會奔潰出現java.lang.NoClassDefFoundError錯誤。
對於代碼依賴複雜或者自檢機制,就可能不會將這些類識別爲主DEX文件中必需類。須要使用multiDexKeepFile 或者multiDexKeepProguard 聲明主DEX文件中必需的類,在構建時若是匹配到就添加到主DEX文件中。
建立一個名爲multidex-config.txt文件,在文件中添加須要放在主DEX的類,每行包含一個類,格式以下:
com/example/MyClass.class com/example/MyOtherClass.class
Gradle會讀取相對於build.gradle文件路徑,multidex-config.txt 與 build.gradle 文件在同一目錄中。
android { buildTypes { release { multiDexKeepFile file('multidex-config.txt') } } }
multiDexKeepProguard中文件添加內容格式與支持 Proguard 語法相同,包含-keep選項
-keep class com.example.MyClass -keep class com.example.MyClassToo 或者指定包中全部的類 -keep class com.example.** { *; } // All classes in the com.example package
https://www.guardsquare.com/e...
android { buildTypes { release { multiDexKeepFile file('multidex-config.pro') } } }
爲了減少APK的大小,應該啓動壓縮來移除發佈構建中未使用的代碼和資源。
代碼壓縮經過 ProGuard 提供,ProGuard 會檢測和移除應用中未使用的類、字段、方法和屬性,包括自帶代碼庫中的未使用項。ProGuard 還可優化字節碼,移除未使用的代碼指令,以及用短名稱混淆其他的類、字段和方法。
資源壓縮經過 Gradle 的 Android 插件提供,該插件會移除應用中未使用的資源,包括代碼庫中未使用的資源。
要經過 ProGuard 啓用代碼壓縮,在 build.gradle 文件內相應的構建類型中添加 minifyEnabled true
代碼壓縮會影響構建速度,避免在調試中使用。
android { buildTypes { release { minifyEnabled true // 用於定義 ProGuard 規則,getDefaultProguardFile 從 ${Android SDK}\tools\proguard\文件夾獲取默認的ProGuard 配置 // proguard-rules.pro文件用於添加自定義ProGuard 配置,默認文件位於模塊根目錄 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard/proguard-rules.pro' } } }
每次構建時,ProGuard 都會輸出下列文件:
這些文件保存在 ${module-name}/build/outputs/mapping/release/ 中
默認 ProGuard 配置文件 (proguard-android.txt) 足以知足須要,ProGuard 會移除全部(而且只會移除)未使用的代碼。可是,ProGuard 很難以對多狀況進行正確分析,可能會移除應用須要的代碼。舉例來講,它可能錯誤移除代碼的狀況包括:
能夠強制 ProGuard 保留指定代碼,在 ProGuard 配置文件中添加一行 -keep 代碼。或者在想保留的代碼添加 @keep 註解,在類上添加 @keep 可原樣保留整個類,在方法或者字段上添加可完整保留方法/字段以及類名稱。
-keep public class * extends android.app.Activity
在 ProGuard 壓縮代碼後,代碼追蹤變得困難,由於方法名稱都混淆處理了。可是ProGuard 每次運行時都會建立一個 mapping.txt 文件,其中顯示了與混淆過的名稱對應的原始類名稱、方法名稱和字段名稱。ProGuard 將該文件保存在應用的 <module-name>/build/outputs/mapping/release/ 目錄中。
可使用Android SDK 提供的工具解碼混淆過的代碼,retrace腳本(Window上爲retrace.bat,Mac/Linux上爲retrace.sh),位於<sdk-root>/tools/proguard/目錄中
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>] 例如: retrace.bat -verbose mapping.txt obfuscated_trace.txt
也能夠直接使用 proguardgui.bat 圖形化工具,位於<sdk-root>/tools/proguard/bin/目錄中
資源壓縮只與代碼壓縮協同工做。代碼壓縮器移除全部未使用的代碼後,資源壓縮器即可肯定應用仍然使用的資源。
啓用資源壓縮,在 build.gradle 文件中將 shrinkResources 屬性設置爲 true,默認是false
android { buildTypes { release { shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard/proguard-rules.pro' } } }
資源壓縮器目前不會移除 values/ 文件夾中定義的資源(例如字符串、尺寸、樣式和顏色)。這是由於 Android 資源打包工具 (AAPT) 不容許 Gradle 插件爲資源指定預約義版本
在開始 shrinkResources 後,打包構建時,Android Gradle自動處理未使用的資源,生成的apk就不會包含。能夠在構建輸出日誌中查看,gradlew assembleArmRelease --info | grep "unused resource"
Removed unused resources: Binary resource data reduced from 2977KB to 2879KB: Removed 3%
可是可能會誤刪有用的資源,如使用反射去引用資源文件,Android Gradle區分不出來,認爲這些資源沒有被使用。咱們可使用keep配置哪些資源不被清理。
若是有想要保留或捨棄的特定資源,在項目中建立一個包含 resources 標記的 XML 文件,並在 tools:keep 屬性中指定每一個要保留的資源,在 tools:discard 屬性中指定每一個要捨棄的資源。這兩個屬性都接受逗號分隔的資源名稱列表。還可使用星號字符做爲通配符。
<?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/unused2" />
將該文件保存在項目資源中,例如,保存在 res/raw/keep.xml。構建不會將該文件打包到 APK 之中。
正常狀況下,資源壓縮器可準確斷定系統是否使用了資源。可是,若是在代碼調用 Resources.getIdentifier(),這就表示代碼會根據動態生成的字符串查詢資源名稱。當執行這一調用時,默認狀況下資源壓縮器會採起防護性行爲,將全部具備匹配名稱格式的資源標記爲可能已使用,沒法移除。
// 會使全部帶 img_ 前綴的資源標記爲已使用 String name = String.format("img_%1d", angle + 1); res = getResources().getIdentifier(name, "drawable", getPackageName());
默認狀況下啓用的是安全壓縮模式,tools:shrinkMode="safe"。若是將 keep.xml 文件中 shrinkMode 設置爲 strict,也就是啓用嚴格壓縮模式,而且代碼也引用了包含動態生成字符串的資源,則必須利用 tools:keep 屬性手動保留這些資源。若是不保留,也會被清理掉。
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="strict" />
shrinkResources只會移除代碼未被引用的資源,不會移除不一樣設備的備用資源。好比引用的第三方庫,特別是Support Library,爲了國際化支持幾十種語言,可是有的App不用支持這麼多的語言,只需中文和英文就能夠了;好比圖片只支持xhdpi格式就能夠。
可使用 Android Gradle 插件的 resConfigs 屬性來移除您的應用不須要的備用資源文件。
android { defaultConfig { // 只會保留默認的default 和 en 資源 ,其餘的不會打包到APK中 resConfigs "en" } }
resConfigs的參數是資源限定符,包括屏幕方向(port,land),屏幕尺寸(small,normal,large,xlarge),屏幕像素密度(hdpi,xhdpi),API Level(V3,V4)等
參考文檔
https://developer.android.goo...
https://developer.android.goo...
若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)