本文概述
- 前言
- 项目顶层build.gradle
- 子项目/模块中的build.gradle
- 依赖配置
- Android Gradle完整配置
- Android JNI开发和Gradle配置
- 总结:Android开发的起点
前言
之前我们讨论了Gradle和Groovy的相关内容,本文具体介绍在Android Studio中使用Gradle的详细配置,希望得到一个Android项目的普遍配置,旨在在每个Android项目中重用这个配置。若无意外,每次新建项目,我们只需要复制这份配置即可,毕竟这个gradle配置也不见得需要经常改动。
项目顶层build.gradle
该项目用于全局配置,其中添加的配置对于当前项目及其子项目或模块都有效,默认由Android Studio自动生成,其中的配置包括:
- 所有项目的脚本依赖配置,脚本的依赖使用buildscript配置,包括使用的仓库和依赖的插件。
- 所有项目的仓库设置,用于项目本身的依赖来源,使用allprojects配置。
- 一个简单的clean清理任务,用于删除构建生成的文件。
该build.gradle配置和解释如下:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
// 全局脚本依赖配置, 包括仓库和依赖项
buildscript {
// 仓库配置
repositories {
// google仓库
google()
// jcenter仓库
jcenter()
}
// 脚本依赖项
dependencies {
// android gradle构建工具, 提供使用Android plugin DSL
classpath "com.android.tools.build:gradle:4.1.2"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
// 全局配置所有项目的project
allprojects {
// 全局仓库配置
repositories {
google()
jcenter()
}
}
// 项目清理任务
task clean(type: Delete) {
delete rootProject.buildDir
}
接着我们根据DSL分析一下这些配置:
- buildscript{}:Project的方法,配置此项目的构建脚本类路径。闭包的代理为ScriptHandler。Dependencies和repositories都是ScriptHandler的方法,dependencies方法闭包的代理为DependencyHandler。Repositories方法闭包的代理为RepositoryHandler。
- allprojects{}:Project的方法,配置此项目及其每个子项目,相当于调用for-each对每个项目进行配置。其闭包代理为当前项目和子项目的Project对象。因为是Project对象,所以在里面又可以使用该对象做一些全局的配置。其中的dependencies和repositories是Porject对象的方法(和ScriptHandler不同)。
- Clean任务:该任务是Ddelete类型的任务,任务闭包代理为Delete对象,delete为Delete对象的方法,用以添加需要删除的文件。
子项目/模块中的build.gradle
插件配置
插件配置使用plugins方法配置(也可以用apply,但推荐使用plugins),其中可以配置三项:
- Id:插件的唯一ID。
- Version:插件的版本号,有的插件需要强制指定。
- Apply:是否应用该插件,默认为true。
下面是Android Studio默认生成的代码:
plugins {
id 'com.android.application'
}
Android本身支持两种插件:com.android.application和com.android.library,前者用于构建Android可执行应用程序,最终产品为.apk文件,后者用于构建Android库最终产品为.aar文件,这类似于Java项目中的application和java-library。
依赖配置
配置项目依赖使用Project对象中的dependencies方法,传入闭包的代理为DependencyHandler,添加依赖一般使用implementation或testImplementation,依赖第三方库的完整写法为(map风格):
implementation group: "", name: "", version:""
group就是maven中的groupid,name就是maven中的artifactid,也可以合并起来写,这是一种常见的形式,例如(字符串风格):
implementation 'commons-lang:commons-lang:2.6'
如果项目依赖相关的本地jar包,则可以将这些jar包以文件组的形式传给implementation,例如:
implementation files('spring.jar', 'hibernate.jar', 'mybatis.jar')
implementation fileTree('libs')
files和fileTree都是project对象的方法,要注意的是,files是用来收集文件/文件夹的,它不负责往下继续遍历文件;fileTree传入一个目录,会将目录及其子目录下的所有文件遍历出来。
Android构建配置
Android{}闭包开始表示Android相关的构建,所有Android相关的配置都在这个闭包里面进行配置。
android支持以下配置:
- aaptOptions{}:指定Android资源打包工具(AAPT)的选项。
- adbOptions{}:指定ADB调试桥的选项,例如APK安装选项。
- buildTypes{}:封装此项目的所有构建类型配置。
- compileOptions{}:指定Java编译器选项,例如Java源代码和生成的字节码的语言级别。
- dataBinding{}:指定数据绑定库的选项。
- defaultConfig{}:指定Android插件应用于所有构建变体(所有渠道)的默认变量属性。
- dexOptions{}:指定DEX工具的选项,例如启用库预索引。
- externalNativeBuild{}:使用CMake或ndk-build配置外部本地构建,下面会讨论配置使用CMake编译C++。
- jacoco{}:配置用于离线检测和覆盖率报告的JaCoCo版本。
- lintOptions{}:为检测工具指定选项。
- packagingOptions{}:指定选项和规则,决定哪些文件Android插件包到你的APK。
- productFlavors{}:封装此项目的所有产品渠道配置,可实现多渠道配置。
- signingConfigs{}:封装可以应用于BuildType和ProductFlavor配置的签名配置,要注意的是仅用这个配置是无法进行签名的,需要在buildTypes{}或productFlavors{}中指定。
- sourceSets{}:封装所有变体的源集配置。
- splits{}:指定用于构建多个APK或APK拆分的配置。
- testOptions{}:指定Android插件应该如何运行本地和测试的选项。
签名信息配置:signingConfigs{}
signingConfigs {
debug {
storeFile file('test.jsk')
storePassword '123456'
keyAlias 'test'
keyPassword '123456'
}
release {
storeFile file('test.jsk')
storePassword '123456'
keyAlias 'test'
keyPassword '123456'
}
}
也可以在Project Structure中填写相关的签名信息,生成APK的方式有两种:
- 使用Build->Build apk生成APK。
- Build->Generate Signed APK填写签名信息生成Apk,另外,如果你还没有签名密钥,则可以在这里生成。
- 使用gradle build/assemble/assembleDebug/assembleRelease命令也可以生成,只是要注意build是否需要运行单元测试。
另外,要注意的是:当使用gradle build或Build>Build apk生成APK的时候,仅仅使用signingConfigs{}配置签名,APK是不会被签名的。
还有一个问题是:有些商店必须要使用 .keystore文件来进行签名,这时需要转换一下,将jks转为keystore,直接用命令行,先生成.p12文件,用p12生成keystore。
keytool -importkeystore -srckeystore D:\test.jks -srcstoretype JKS -deststoretype PKCS12 -destkeystore test.p12
keytool -v -importkeystore -srckeystore D:\test.p12 -srcstoretype PKCS12 -destkeystore D:\test.keystore -deststoretype JKS
通过以下命令来验证签名信息:
keytool -v -list -keystore D:\test.keystore
但是我觉得不如直接使用keystore签名就是了,使用以下命令直接生成keystore签名,以后统一使用keystore签名就是了:
keytool -genkey -alias test -keypass 123456 -keyalg RSA -keysize 1024 -validity 1000 -keystore C:/Users/Administrator/test.keystore -storepass 123456
SDK和构建工具版本配置
compileSdkVersion 30
buildToolsVersion "30.0.3"
其中compileSdkVersion用于指定SDK的版本,类似JDK的版本。BuildToolsVersion用于指定Android项目的构建工具版本,其中SDK版本可以参考:https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels,构建工具版本可以参考:https://developer.android.com/studio/releases/build-tools.html。
一般这两个用最新版本就行了,对于构建工具版本:当使用Android plugin 3.0.0或更高版本时,这个属性是可选的。默认情况下,插件使用的是您所使用的插件版本所需的构建工具的最低版本。
应用构建默认配置:defaultConfig{}
// 默认配置, 用于所有渠道的默认值
defaultConfig {
// APP唯一包名
applicationId "com.org.android.appname"
// 最低兼容SDK版本
minSdkVersion 16
// 目标SDK版本
targetSdkVersion 30
// 版本号
versionCode 1
// 版本名称
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
这些配置作为应用所有渠道构建的默认值,多渠道使用productFlavors{}配置,你也可以在productFlavors{}中覆盖这些默认值。
以上配置是Android studio生成的默认配置,参考DefaultConfig获取更多配置。
应用构建类型:buildTypes{}
// 构建类型配置
buildTypes {
debug {
shrinkResources false
minifyEnabled false
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
buildConfigField "Boolean", "DEBUG_MODE", 'true'
}
release {
//是否优化zip
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources true
//启用代码混淆
minifyEnabled true
//混淆规则配置文件
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
//指明签名文件位置
signingConfig signingConfigs.release
buildConfigField "Boolean", "DEBUG_MODE", 'false'
}
}
以上是一些常用配置,更多配置请参考BuildType。
在上面的配置中你可以看到,这里是使用signingConfig配置应用的签名的,使用的就是上面配置的签名信息,在这里配置的签名在打包的时候会被真正使用到。
源码集设置:sourceSets{}
sourceSets {//目录指向配置
main {
jniLibs.srcDirs = ['libs']//指定lib库目录
}
}
注意,Android插件使用自己的源集实现,也就是说Android有自己的默认项目标准结构,建议遵循Android的规范。更多关于你可以在这个区块中配置的属性的信息,见AndroidSourceSet。
打包配置:packagingOptions{}
packagingOptions{
//pickFirsts做用是 当有重复文件时 打包会报错 这样配置会使用第一个匹配的文件打包进入apk
// 表示当apk中有重复的META-INF目录下有重复的LICENSE文件时 只用第一个 这样打包就不会报错
pickFirsts = ['META-INF/LICENSE']
//merges何必 当出现重复文件时 合并重复的文件 然后打包入apk
//这个是有默认值得 merges = [] 这样会把默默认值去掉 所以我们用下面这种方式 在默认值后添加
merge 'META-INF/LICENSE'
//这个是在同时使用butterknife、dagger2做的一个处理。同理,遇到类似的问题,只要根据gradle的提示,做类似处理即可。
exclude 'META-INF/services/javax.annotation.processing.Processor'
}
你可以看到,这个配置一般用于处理打包文件重复的问题,需要在这里实现相关的处理逻辑。
代码分析:lintOptions{}
//程序在编译的时候会检查lint,有任何错误提示会停止build,我们可以关闭这个开关
lintOptions {
abortOnError false //即使报错也不会停止打包
checkReleaseBuilds false //打包release版本的时候进行检测
}
多渠道配置:productFlavors{}
android {
productFlavors {
wandoujia {}
xiaomi {}
_360 {}
//...
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}
另外,还要添加风味维度(什么鬼),什么意思呢?多渠道的意思是,同一个app使用不同的方式进行打包(不同包名,manifest标记信息),这是对应不同厂商的渠道(小米、华为、应用宝等)。而风味维度的意思是对同一个渠道再次细化,对应同一个厂商的不同机型,但是一般使用一个维度(厂商)就行了。
android {
flavorDimensions "color"
}
另外要注意的是:上面的渠道配置基本足够了,建议不要重新覆盖默认值的包名,如果多个渠道同一个app包名不同,提交应用可能会造成冲突。
多渠道需要结合统计才行的,否则没多大意义,例如使用友盟统计,在统计配置里可以添加渠道信息,这时就引用了上面配置的不同渠道信息。在清单文件的application下添加:
<meta-data android:value="000" android:name="UMENG_APPKEY"/>
<!--添加渠道号 这里使用的${UMENG_CHANNEL_VALUE} 如果是豌豆荚平台 这里就写成豌豆荚 如果是应用宝就写成应用宝-->
<meta-data android:value="${UMENG_CHANNEL_VALUE}" android:name="UMENG_CHANNEL"/>
Android Gradle完整配置
下面是Android Studio项目的完整gradle配置,但这是一般的Android项目。Android开发还有一种特殊的文件:JNI开发,引用C/C++动态库或静态库等,接下来我们就讨论这个问题。
plugins {
// Android项目构建插件
id 'com.android.application'
}
// Android配置
android {
// 签名信息配置: debug + release
signingConfigs {
debug {
storeFile file('C:/Users/Administrator/test.keystore')
storePassword '123456'
keyAlias 'test'
keyPassword '123456'
}
release {
storeFile file('C:/Users/Administrator/test.keystore')
storePassword '123456'
keyAlias 'test'
keyPassword '123456'
v1SigningEnabled true
v2SigningEnabled true
}
}
// 使用的SDK版本
compileSdkVersion 30
// 使用的构建工具版本
buildToolsVersion "30.0.3"
// 默认配置, 用于所有渠道的默认值
defaultConfig {
// APP唯一包名
applicationId "com.id"
// 最低兼容SDK版本
minSdkVersion 16
// 目标SDK版本
targetSdkVersion 30
// 版本号
versionCode 1
// 版本名称
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
// 构建类型配置
buildTypes {
debug {
shrinkResources false
minifyEnabled false
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
buildConfigField "Boolean", "DEBUG_MODE", 'true'
}
release {
//是否优化zip
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources true
//启用代码混淆
minifyEnabled true
//混淆规则配置文件
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
//指明签名文件位置
signingConfig signingConfigs.release
buildConfigField "Boolean", "DEBUG_MODE", 'false'
}
}
sourceSets {//目录指向配置
main {
jniLibs.srcDirs = ['libs']//指定lib库目录
}
}
packagingOptions{
//pickFirsts做用是 当有重复文件时 打包会报错 这样配置会使用第一个匹配的文件打包进入apk
// 表示当apk中有重复的META-INF目录下有重复的LICENSE文件时 只用第一个 这样打包就不会报错
pickFirsts = ['META-INF/LICENSE']
//merges何必 当出现重复文件时 合并重复的文件 然后打包入apk
//这个是有默认值得 merges = [] 这样会把默默认值去掉 所以我们用下面这种方式 在默认值后添加
merge 'META-INF/LICENSE'
//这个是在同时使用butterknife、dagger2做的一个处理。同理,遇到类似的问题,只要根据gradle的提示,做类似处理即可。
exclude 'META-INF/services/javax.annotation.processing.Processor'
}
//程序在编译的时候会检查lint,有任何错误提示会停止build,我们可以关闭这个开关
lintOptions {
abortOnError false //即使报错也不会停止打包
checkReleaseBuilds false //打包release版本的时候进行检测
}
flavorDimensions "color"
productFlavors {
wandoujia {}
xiaomi {}
_360 {}
//...
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.navigation:navigation-fragment:2.2.2'
implementation 'androidx.navigation:navigation-ui:2.2.2'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
Android JNI开发和Gradle配置
在Android开发中,使用C/C++的整体思路是:
- 根据指定的native函数生成头文件,Java调用的native函数有指定的格式,所以要使用javah生成。
- 编译Android平台的C/C++代码需要使用NDK。
- 构建C/C++代码有两种方式,一种是ndk原生方式,另一种是使用CMake,这里仅介绍CMake的构建方式。
- C/C++代码一般保存在main/cpp中,cpp目录作为源码目录,所有C/C++代码都放在这里(你也可以尝试放在其它地方,但要注意配置,但是这里的目录结果是推荐的结构)。
- CMake的构建脚本CMakeLists.txt放在main/cpp中。
JNI在Gradle中的配置有两个地方,首先是在defaultConfig{}中配置externalNativeBui{},例如:
defaultConfig {
// 这个块不同于你用来链接Gradle到你的CMake或ndk-build脚本的块。
externalNativeBuild {
// 对于ndk-build,请使用ndkBuild块。
cmake {
// 将可选参数传递给CMake。
arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
// 设置一个标志来为C编译器启用格式化宏常量。
cFlags "-D__STDC_FORMAT_MACROS"
// 为c++编译器设置可选的标志。
cppFlags "-fexceptions", "-frtti"
// 指定Gradle应该构建的CMake项目中的库和可执行目标。
targets "libexample-one", "my-executible-demo"
}
}
}
这里一般是用来配置编译或构建可选参数的,但是一般情况没有特别的需求,可以留空,所以这里的配置是可选的。
JNI构建的重要配置在android{}下的externalNativeBuild{}中,在这里可以配置你需要的cmake或ndk-build构建,例如使用cmake构建,主要是使用path属性指定CMakeLists.txt文件的相对路径(相对项目根目录),另一个属性是version,指定cmake的版本:
android {
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
只要配置好以上信息,基本上编译和打包都是没问题的了,也不同担心so库打不进apk。
总结:Android开发的起点
以上是所有Android项目的一般性完全配置,不能说是全面性的配置,毕竟可能有些开发者还有更多的需求,需要编写更多的任务来完成,但是我这里的目的是得到一份基本的Gradle配置,保证每新建一个Android项目可以直接复制使用了,接着就是埋头实现需求去了。
因为我觉得在构建脚本上花费过多的时间是一个噩梦!——如果你的技术基础不足,你大概有解决不完的IDE相关的问题,简化这个问题,集中在需求上就行了,不然项目完成遥遥无期!
至于后面JNI相关的配置来自于Android自动生成的Native项目,若有可能可以直接新建Native项目,这样从默认配置开始开发就行了。
好了,到这里大概解决了Android项目的基本配置问题了,下面接着研究Java或Java Web的Maven完整配置。