本篇文章主要在以前学习的基础上,从实际开发的角度学习如何对 Android Gradle 来进行自定义以知足不一样的开发需求,下面是 Gradle 系列的几篇文章:java
下面是主要内容:android
修改打包输出的 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 文件中指定渠道名称,以下所示:
<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 个,以下图所示:
固然,若是没有配置 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 来执行相应的任务,以下图所示:
选择对应的 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 是一个在 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>
在 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 的全称是 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]
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 中,在实际的项目中并无被清除,可经过日志查看哪些资源被清理了,而后决定要不要在项目中清除。
在 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),一块儿交流学习。