博客主页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...
若是个人文章对您有帮助,不妨点个赞鼓励一下(^_^)