你该知道的Gradle配置知识总结
前言
本文主要介绍了关于gradle配置的相关知识,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。
参考链接:
本片文章的内容全部参考自上面的链接,其中有些内容是直接翻译的,有些内容是结合自己的经验总结的,可能有理解错误的地方,非常希望大家能指正出来,在交流中进步。
gradle 编译过程
编译流程图
上图展示了一个典型的 app 编译过程,主要分为以几步:
- 编译器将源代码(包括依赖库)转化为 dex 文件,编译资源文件(res 以及 assets 文件下的资源)。
- apk packager 整合所有的 dex 文件和编译过的资源文件,并且对 apk 进行签名。
- 签名文件必须使用 debug 版或者 release 版,使用 debug keystore 生成的 app 被用来测试和分析,使用 release keystore 生成的 app 可以进行发布供其他用户使用。
- 在生成最终的 apk 之前,apk packager 会使用 zipalign 工具优化整个 app ,以便 app 在使用的过程中更加节省内存。
自定义编译配置
android studio 的 gradle 插件方便我们在以下几个方面配置我们的编译选项:
build types - 编译类型
编译类型,包括我们最熟悉的 release 和 debug 两种类型,我们可以根据这两种类型定义出更多的类型。配置对应的 build.gradle 文件在 moudle 下,需要添加新的或者修改 build type ,只需要在 android{ ... }里面操作。
一个示例如下:
android { ... defaultconfig {...} buildtypes { release { //开启混淆 minifyenabled true //混淆规则文件 proguardfiles getdefaultproguardfile('proguard-android.txt'), 'proguard-rules.pro' } debug { //apk的后缀 applicationidsuffix ".debug" } //debug的一个扩展 jnidebug { // 复制debug的属性和签名配置 initwith debug applicationidsuffix ".jnidebug" //开启jni调试 jnidebuggable true } } }
其中 initwith 可以方便我们继承其他的配置,只需要添加需要的部分。
product flavors - 构建不同版本
配置 apk 的版本信息,可以为每一个版本指定不同的 applicationid 和版本名称。关于 applicationid ,可以把它也理解为包名,不过和 manifest 文件中的包名作用不同,它是用来给应用商店和设备区分不同的 app ,而 manifest 中的 pakage 属性用来在源代码中引用 r 类和其他类。即同一份代码 applicationid 可以让它变成不同的 app 。
示例配置如下:
android { ... defaultconfig {...} buildtypes {...} productflavors { demo { applicationid "com.example.myapp.demo" versionname "1.0-demo" } full { applicationid "com.example.myapp.full" versionname "1.0-full" } } }
通过上面的配置之后,如果 buildtypes 里面配置了两个编译类型,假如是 debug 和 release ,将会产生四个 apk 文件,每一种 buildtype 都会和每种 flavor 进行组合拼接,进而产生不同的变种版本(build variant),上面对应的四个不同的变种版本分别是:demodebug、demorelease、fulldebug、fullrelease。
mutiple manifest files - 合并多个清单文件
配置多个 manifest 文件。经常会在项目中依赖其他项目,这个时候就会有多个 manifest 文件,那在编译的时候该如何处理呢?这个时候需要进行合并,而且还必须有一套相应的合并规则解决和避免合并冲突。对于不同的 manifest 文件中同一个属性的不同值,在合并的时候还需要优先级来进行判断,用高优先级的去覆盖低优先级的。
关于优先级定义如下:
- 最高优先级:buildtype 的设置
- 次高优先级:productflavor 的设置
- 中等优先级:在 src/main 目录下的 manifest 文件
- 最低优先级:各种依赖和第三方库的设置
合并规则:概括来说是这样:
- 合并之前,先将每个 module 里面的 buildtype 内容写到 manifest 里面去,比如你在 buildtype 里面的 minsdkversion 和targetsdkversion 以及 versioncode 和 versionname 等等(此时合并后的 manifest 文件可以在 app/intermediates/manifests/* 目录下查看)。
- 对于同一个属性,当高优先级和低优先级都为非默认值时,如果可以匹配,那直接合并,不能匹配,就会产生冲突(这种是针对两个不同的 module 来说),下面会专门给出例子。
- 不管高优先级还是低优先级,如果其中一个没有设置该属性或者设置为默认的属性值,而另外一个设置了非默认的属性值,则合并的结果就是非默认的属性值,在项目编译后,可以查看 manifest 的合并记录,该文件目录为:app/intermediates/outputs/logs/manifest*.txt。
示例:现在给出一些例子说明上述规则,我的主 module 名为 app ,新建一个依赖的 module 叫 uisdk ,现在分别给出两个 module 的 build.gradle 文件:
app/build.gradle
apply plugin: 'com.android.application' android { compilesdkversion 24 buildtoolsversion "24.0.0" defaultconfig { applicationid "com.example.rth.study" minsdkversion 15 targetsdkversion 24 versioncode 1 versionname "1.0" } buildtypes { release { minifyenabled false proguardfiles getdefaultproguardfile('proguard-android.txt'), 'proguard-rules.pro' } } productflavors { demo { minsdkversion 7 applicationid "com.rth.app" } } } dependencies { compile filetree(include: ['*.jar'], dir: 'libs') testcompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:24.0.0' compile project(':uisdk') }
uisdk/build.gradle
apply plugin: 'com.android.library' android { compilesdkversion 24 buildtoolsversion "24.0.0" defaultconfig { minsdkversion 8 targetsdkversion 24 versioncode 1 versionname "1.0" } buildtypes { release { minifyenabled false proguardfiles getdefaultproguardfile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile filetree(dir: 'libs', include: ['*.jar']) testcompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:24.0.0' }
在 app/build.gradle 里面,defaultconfig 的 minsdkversion 为15,但我在变种版本(productflavors 里的 demo)里设置的 minsdkversion 为7,最终 app 的 manifest 的 minsdkversion 就为7,再看 uisdk 里面的 build.gradle ,minsdkversion 为8,就是说 app 这个 module 和 uisdk 这个 module library 在同一个属性上使用了不同的非默认值,而且 library 的 manifest 属于最低优先级,它设置的值又比优先级比它高的值还要高,就会出错,出错信息的描述也很清晰:
error:execution failed for task ':app:processdemodebugmanifest'. manifest merger failed : uses-sdk:minsdkversion 7 cannot be smaller than version 8 declared in library ... suggestion: use tools:overridelibrary="com.example.uisdk" to force usage
根据错误信息,我们有两种方式解决这个问题:
- 把 app 里面的值调高,或者把 uisdk 里面的值调低。
- 就像上面建议的那样,使用 overridelibrary 这个标签。该标签的作用在名字上已经体现出来了,就是直接覆盖 library 里面的设置,现在我们在 app/src/main/manifest 里面加上这么一句:
<uses-sdk tools:overridelibrary="com.example.uisdk"/>
就能编译通过了,这适用于比较特殊的情况,就是在依赖库里可能要适用一些新特性,这些特性在 app 的 minsdkversion 下不能使用,而且 app 的 minsdkversion 已经不能更改了。
标记选择器(marker selectors) :选择器的功能可以让一些属性在某些 libary 里面无效,比如就拿上面的例子来说,我想让 uisdk 只处理 ui 上的东西,不想让他具有网络访问的功能,那么我可以这么设置:
<uses-permission android:name="android.permission.internet" tools:node="remove" tools:selector="com.example.uisdk" />
其中 tools:node 标签表示删除该权限,tools:selector 标签选择在哪个依赖库里执行 tools:node 表示的动作。
可以看出这些配置还是挺灵活的。
configure dependencies - 配置依赖
这个应该是最熟悉的了,项目中经常要依赖第三方库,一个典型了例子如下:
android {...} ... dependencies { //将本地 module library 编译到项目中 compile project(":mylibrary") //编译远程依赖 compile 'com.android.support:appcompat-v7:23.4.0' //编译本地 jar 包 compile filetree(dir: 'libs', include: ['*.jar']) }
上面主要用到的方式是 compile ,gradle 支持6种编译方式:
- compile:对所有 buildtype 以及 flavors 进行编译并打包到 apk 。
- provided:和 compile 相似,但只在编译时使用,几只参与编译,不打包到最终 apk 。
- apk:只会打包到 apk 中,不参与编译,所以不能在项目代码中使用相应库中的方法。
- test compile:相比于 compile ,仅仅针对单元测试的代码编译打包。
- debug compile:仅针对 debug 模式编译打包。
- release compile:仅针对 release 模式编译打包。
另外在进行 sdk 开发时,一般为了减小 sdk 体积,一些依赖库会用 provided 的方式,同时需要注意的是,对于远程依赖,compile 和 provided 的效果一样,都不会打包到 jar 包或者 arr 包中,但对于本地的 jar 包或者 arr 包的依赖,compile 和 provided 就有区别了。
configure sigining - 配置签名
在用 gradle 配置 release 版本的签名信息时,需要下面三个步骤:
- 生成一个 keystore ,一个二进制文件保存一些私钥,这个必须好好保存。
- 生成一个私钥,用于开发者或者公司与这个 app 建立对应关系。
- 将生成的信息配置到 moudle 层的 build.gradle 里。
示例如下:
android { ... defaultconfig {...} signingconfigs { release { storefile file("myreleasekey.keystore") storepassword "password" keyalias "myreleasekey" keypassword "password" } } buildtypes { release { ... signingconfig signingconfigs.release } } }
上面的配置中直接显示了一些敏感信息,比如各种密码,一种更加安全的方式是通过环境变量的方式获取:
storepassword system.getenv("kstorewd"); keypassword system.getenv("keypwd");
或者如果使用命令行的方式编译,还可以让用户在命令行输入密码:
storepassword system.console().readline("\nkeystore password: ") keypassword system.console().readline("\nkey password: ")
暂时就总结到这么多了,再次说明,如果发现理解错的地方欢迎指正!!!
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。