版权声明:本文为LooperJing原创文章,转载请注明出处!
·
本来这篇要写Android性能优化的,个人时间比较少,每天加班到很晚,写博客的时间就很少了,但是Gradle系列的文章还没有写完,所以补一篇,在Gradle系列第(二)篇---Gradle编程主要对象主要写了Gradle中的几个对象(Project,Settings,Gradle,Task、Action),现在聊一聊Android Studio中的gradle常见的功能需求。如果你還沒有阅读过我的前两篇博客Gradle系列第(一)篇---Groovy语法初探和Gradle系列2---Gradle编程主要对象,可以先看一下,有助于本文的理解,好啦,各位看官准备好瓜子花生,接下来一大篇文章哗啦啦的来了。不过不用担心,这篇博客仍然是面向基础。
读完这篇博客,你会了解到这些内容
- 1、Android的构建文件
- 2、全局参数配置
- 3、用脚本更改项目结构
- 4、多种apk的生成
- 5、签名的配置与使用
- 6、项目混淆(Proguard)
- 7、gradle多渠道打包
- 8、APK需求定制的案例
- 9、动态参数配置
- 10、gradle依赖管理
- 11、gradle.properties文件配置
- 12、jar文件输出
一、AS项目构建文件的简单解释
一个AS项目结构大概像下面这样子
如蓝色条所示,项目中总共包含了6个构建文件(不算Library中的gradle),我们先从宏观的方面了解一下,每个构建文件的作用是啥?
- 1、这个文件是app文件夹下这个Module的gradle配置文件,也可以算是整个项目最主要的gradle配置文件,比如自动打包debug,release,beta等环境,签名,多渠道打包,混淆等操作都可以在这里面写。每一个Module都需要有一个gradle配置文件。
- 2、我们主要看下gradle-wrapper.properties这个文件的内容
#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
可以看到里面声明了gradle的目录与下载路径以及当前项目使用的gradle版本,这些默认的路径我们一般不会更改的,有時候导入一个新项目,gradle版本不对,可以在这里修改。
- 3、这个文件是整个项目的gradle基础(全局)配置文件,内容主要包含了两个方面:一个是声明仓库的源,这里可以看到是指明的jcenter(), 之前版本则是mavenCentral(), jcenter可以理解成是一个新的中央远程仓库,兼容maven中心仓库,而且性能更优。另一个是声明了android gradle plugin的版本。allprojects:中定义的属性会被应用到所有 moudle 中,但是为了保证每个项目的独立性,我们一般不会在这里面操作太多共有的东西。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
所有通过gradle导入的jar包都是从http://bintray.com/bintray/jcenter这个中央仓库上扒下来的。如果你需要的jar包在这个网站上没有,那就无法通过gradle的方式来导入哦。
- 4、这个里面可以配置参数,然后在其他build.gradle中引用,后面会讲例子,如何动态配置参数。
- 5、这里主要指定了ndk和SDK的路径
ndk.dir=G\:\\Users\\wangjing\\AppData\\Local\\Android\\sdk\\ndk-bundle
sdk.dir=G\:\\Users\\wangjing\\AppData\\Local\\Android\\sdk
- 6、setting.gradle最关键的内容就是告诉Gradle这个multiprojects包含哪些子projects,当你的app只有一个模块的时候,你的setting.gradle将会是这样子的:
include ':app'
当你的app有多个模块的时候,你的setting.gradle将会是这样子的
include ':app', ':library',。。。。
setting.gradle文件将会在gradle初始化时期执行,关于初始化时期,可以查看上一篇博客,并且定义了哪一个模块将会被构建。举个例子,上述setting.gradle包含了app模块,setting.gradle是针对多模块操作的,所以单独的模块工程完全可以删除掉该文件。在这之后,Gradle会为我们创建一个Setting对象,每一个settings.gradle都会转换成一个Settings对象,并为其包含必要的方法,你不必知道Settings类的详细细节,但是你最好能够知道这个概念。另外可以在settings做一些初始化的工作,后面介绍。
读到这里做个总结
- build.gradle:控制每个Module的构建过程
- gradle.properties:设置gradle脚本中的参数
- local.properties:gradle的SDK和NDK环境变量配置
- gradle.properties:用于配置参数信息
- setting.gradle :配置gradle的多项目管理
二、实用技能精讲
1、全局参数配置
通常我们的项目都有很多的Module,像我现在公司的项目就有十几个,那么每个Module里面的gradle文件通常都有类似这样的配置。
android {
compileSdkVersion 24
buildToolsVersion "24.0.0"
defaultConfig {
applicationId "com.zhangwan.www.gradle"
minSdkVersion 15
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
这些配置对于每个Module来说,最好统一,我把它定义在项目根目录的gradle文件中,如下。
//全局配置
ext {
minSdkVersion =15
targetSdkVersion =24
compileSdkVersion =24
buildToolsVersion ="24.0.0"
versionCode =1
versionName="1.0"
}
定义好了,我们可以在各个Module的gradle文件文件中引用,如下:
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
利用Gradle全局变量,对于多Module有很大的好处,方便统一,除了上面的列子,在举个例子。上面全部按照单个的属性配置的,对于相关的属性,可以将他们写到一个列表中,下面定义了一个dependencies_config的列表。
ext{
dependencies_config=[supportv7:"com.android.support:appcompat-v7:25.0.0"]
}
在Module中,这样引用
dependencies {
compile rootProject.ext.dependencies_config.supportv7
.....
}
2、项目结构更改
sourceSets 的作用是重新定义资源文件位置,比如
android {
compileSdkVersion 24
buildToolsVersion "24.0.0"
sourceSets{
main{
res.srcDirs=['src/main/res','src/main/res/layout/activity','src/main/res/layout/fragment']
}
}
}
在你Sync Now之后,会出现activity和fragment两个文件夹
最常见的是下面这块代码,当Eclipse项目转到Studio的时候,需要重新指定一些文件的位置。
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
sourceSets的用法就是这样,可以重新指定文件目录,但是读到这,可能有些人心中有个问题,为什么sourceSets ,defaultConfig这样的东东要写在android的大括号中。换言之,android这个大括号里面还能写什么东西,我来列举一下。
android {
defaultConfig {
//默认配置项,defaultConfig就是程序的默认配置,注意,如果在 AndroidMainfest.xml里面定义了与这里相同的属性,会以这里的为主。
}
buildTypes {
// 编译配置,release或debug版本的内容
}
compileOptions {
// Java 的版本配置
}
sourceSets {
//源码设置(项目目录结构的设置)
}
packagingOptions {
//打包时的相关配置
}
lintOptions {
//编译的 lint 开关,程序在buid的时候,会执行lint检查,有任何的错误或者警告提示,都会终止构建,我们可以将其关掉。
//abortOnError false
}
productFlavors {
//产品发布的一些东西,比如渠道、包名等
flavor1 {
}
flavor2 {
}
}
signingConfigs {
//签名的配置
release {
}
}
testOptions{
//测试配置,TestOptions类型
}
aaptOptions{
//aapt配置,AaptOptions类型
}
lintOptions{
//lint配置,LintOptions类型
}
dexOptions{
//dex配置,DexOptions类型
}
compileOptions{
// 编译配置,CompileOptions类型
}
packagingOptions{
// PackagingOptions类型
}
jacoco{
//JacocoExtension类型。 用于设定 jacoco版本
}
splits{
//Splits类型。
}
}
在DSL文档中,以上每个类型都有它的详细配置选项,一般常见的设置就是上面啦,如果你觉得有的不太了解,看下面之后就了解了。
3、多种apk的生成
默认studio生成的buildTypes是像下面这样的,但是呢,我还想要其他的变种类型。
buildTypes {
release {
minifyEnabled false// 不混淆
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
我们可以这样添加
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
r1{
applicationIdSuffix ".r1"
}
r2{
applicationIdSuffix ".r2"
}
r3{
applicationIdSuffix ".r3"
}
}
通过这样就可以得到多种变种app,执行assemble这个task,打出所有apk。
总共得到系统默认有的release和debug两个apk,额外还有r1,r2,r3三个不同的apk。
那么applicationIdSuffix是什么呢,逆向r1包看看。
系统通过包名来区分应用,这种方式无非就是在包名后面加上了一个后缀r1。
4、签名的配置与使用
上面打出的包都是没有指定签名的,我们要配置一个签名,首先需要生成签名文件。我生成的签名文件是1.jks
signingConfigs{
signR1{
storeFile file("build/1.jks");
storePassword "123456"
keyAlias "xxx"
keyPassword "123456"
}
signR2{
storeFile file("build/2.jks");
storePassword "123456"
keyAlias "xxx"
keyPassword "123456"
}
}
签名在signingConfigs中配置,signR1,signR2是签名的名字,在buildTypes中使用。
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
r1{
signingConfig signingConfigs.signR1
applicationIdSuffix ".r1"
}
r2{
signingConfig signingConfigs.signR2
applicationIdSuffix ".r2"
}
}
加上签名后打的包是这样,跟未加签名相比较,多了app-r1.apk,app-r2.apk。
5、项目混淆(Proguard)
面对众多的渠道,打包也有很多不同的需求。 比如 debug版,release版,dev版等等。 有时候不同的版本中使用到的不同的服务端api域名也不相同。 比如 debug_api.com,release_api.com,dev_api.com等等。不同的版本对应了不同的 api 域名,还可能对应不同的 icon 等。渠道首发包通常需要要求在欢迎页添加渠道的logo等。下面我们开始进行打包。首先进行混淆设置,混淆需要buildTypes中配置,在上面说过,默认生成的buildTypes是这样子的
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
其中,proguard-android.txt是在你的sdk\tools\proguard目录下。minifyEnabled:表示是否开启混淆,默认为false;proguardFiles:混淆配置文件,一般就采用项目中默认的proguard-rules.pro文件。在这个文件中写我们的混淆规则,比如:
-keepclasseswithmembernames class * { # 保持 native 方法不被混淆
native <methods>;
}
-keepclassmembers enum * { # 保持枚举 enum 类不被混淆
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable { # 保持 Parcelable 不被混淆
public static final android.os.Parcelable$Creator *;
}
有这些还不够,还需要在gradle中开启混淆
buildTypes {
release {
// 不显示 Log
buildConfigField "boolean", "LOG_DEBUG", "false"
shrinkResources true
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg'
}
debug {
// 显示 Log
buildConfigField "boolean", "LOG_DEBUG", "true"
signingConfig signingConfigs.debug
}
}
我们设置minifyEnabled true,就会在打包的时候进行代码混淆处理. 其中proguard-android.txt不用管,在sdk目录里面,我们主要是配置了proguard.cfg文件。可能大家直接在android studio创建项目不会有这个文件,而是proguard-rules.pro文件,其实一样的,我这里是因为项目是从eclipse迁移过来的,之前在eclipse上混淆是proguard.cfg文件.
6、gradle多渠道打包
- 1、第一步 在AndroidManifest.xml里配置PlaceHolder
<meta-data
android:name="MY_CHANNEL"
android:value="${MY_CHANNEL}" />
- 2、第二步 在build.gradle设置productFlavors
productFlavors{
xiaomi {
//用gradle修改AndroidManifest.xml中的meta-data元素值
manifestPlaceholders = [MY_CHANNEL: "xiaomi"]
}
_360 {
manifestPlaceholders = [MY_CHANNEL: "_360"]
}
baidu {
manifestPlaceholders = [MY_CHANNEL: "baidu"]
}
huawei{
manifestPlaceholders = [MY_CHANNEL: "huawei"]
}
}
或者批量修改
productFlavors{
xiaomi {}
_360 {}
baidu {}
huawei{}
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [MY_CHANNEL: name]
}
最后,最好在defaultConfig中定义一个默认的渠道
defaultConfig{
manifestPlaceholders = [ MY_CHANNEL:"xiaomi" ]
}
到此配置完成,可以执行命令了。
- 3、去工程的根目录,也就是有gradlew文件的目录,打开命令行,输入命令:
./gradlew assemble
这时候你去app/build/outputs/apk中就能看到自动打好的渠道包了。
./gradlew assembleRelease
只打Release包
./gradlew assembleDebug
只打Debug包
./gradlew assemblebaidu
只打360的渠道包
./gradlew assemblebaiduRelease
不想敲命令行的,调起下面这个面板打包
7、APK需求定制
上面说了一下打包,在打包的時候,一些特殊化的操作,比如修改指定apk Logo,apk重命名,这些怎么搞?
- 渠道包重命名
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
File outputDirectory = new File(outputFile.parent);
def fileName
if (variant.buildType.name == "release") {
fileName = "wangjing_${variant.productFlavors[0].name}.apk"
} else {
fileName = "wangjing_${variant.productFlavors[0].name}_beta.apk"
}
output.outputFile = new File(outputDirectory, fileName)
}
}
}
- 根据渠道修改APP名称
buildTypes {
debug {
// 显示Log
buildConfigField "boolean", "LOG_DEBUG", "true"
//重命名
resValue("string","app_name","DEBUG")
versionNameSuffix "-debug"
minifyEnabled false
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.debug
}
release {
// 不显示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
//重命名
resValue("string","app_name","DEBUG")
//混淆
minifyEnabled true
//加载默认混淆配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//签名
signingConfig signingConfigs.release
}
}
其中 resValue("string","app_name","DEBUG") 表示一个string 类型的变量app_name的值是DEBUG,做了上面的配置之后,需要将string.xml的app_name删掉,因为gradle编译的时候,会将脚本中的配置跟string.xml的合并。
8、动态参数配置
signingConfigs{
release{
storeFile file("build/mykey.jks")
storePassword "123456"
keyAlias "123456"
keyPassword "123456"
}
}
上面的这段配置,有个缺点,就是值直接写死了,我们可以动态配置参数。在哪里配置呢,一开始就说了,在gradle.properties中配置参数。如下:
systemPro.keyAliasPassword=123456
systemPro.keyAlias=123456
systemPro.keyStorePassword=123456
systemPro.keyStore=mykey.jks
配置好了,就可以“到处”使用了
signingConfigs{
release{
storeFile System.properties["keyStore"]
storePassword System.properties["keyStorePassword"]
keyAlias System.properties["keyAlias"]
keyPassword System.properties["keyAliasPassword"]
}
debug{
storeFile file("mykey.jks")
storePassword "123456"
keyAlias"123456"
keyPassword "123456"
}
}
9、gradle依赖管理
比如我们想依赖个support-v4包,直接一句话:
compile 'com.android.support:support-v4:23.1.1'
一个依赖需要定义三个元素:group,name和version。group意味着创建该library的组织名,通常这会是包名,name是该library的唯一标示。
上述的代码是基于groovy语法的,所以其完整的表述应该是这样的:
compile group: 'com.android.support:', name: 'support-v4', version:'23.1.1'
有些时候,你可能需要和sdk协调工作。为了能顺利编译你的代码,你需要添加SDK到你的编译环境。你不需要将sdk包含在你的APK中,因为它早已经存在于设备中,不需要在compile,我们总共有5个不同的配置:
compile是默认的那个,其含义是包含所有的依赖包,即在APK里,compile的依赖会存在。
apk的意思是apk中存在,但是不会加入编译中,这个貌似用的比较少。
provided的意思是提供编译支持,但是不会写入apk。
testCompile和androidTestCompile会添加额外的library支持针对测试。
通常项目的Module很多,依赖也非常多,为了方便管理,我们应该将这些依赖写到一个全局的地方,可以供其他module使用。这种思想也是第一小节所提的全局参数的配置。依赖管理可以参考:http://stormzhang.com/android/2016/03/13/gradle-config/
10、gradle.properties文件配置
gradle.properties常见配置比如有:
开启并行编译:加快gradle 的编译
org.gradle.parallel=true开启编译守护进程:该进程在第一次启动后回一直存在,当你进行二次编译的时候,可以重用该进程。
org.gradle.daemon=true加大可用编译内存:
org.gradle.jvmargs=-Xms256m -Xmx1024m
11、jar文件输出
android Studio常常有输出jar包的需求,只要下面这段代码即可:
task makeJar(type: Copy) {
delete 'build/libs/my.jar'
from('build/intermediates/bundles/release/')
into('build/libs/')
include('classes.jar')
rename ('classes.jar', 'my.jar')
}
OK,Gradle研究了一个多星期,这篇博客耗时两个晚上,终于结束,另外如果时间来的急,在写一篇Gradle系列4或5,因为感觉自己还没有讲清楚,侧重多个Module中gradle的使用与Gradle常用命令的使用,下篇博客继续性能优化系列的更新,每一次写博客都花费很多的时间和精力也是一次锻炼,跟他人分享自己的学习成果,最后附上参考资料,比我写的好。