Android Gradle配置完整详细分析

本文概述

  • 前言
  • 项目顶层build.gradle
  • 子项目/模块中的build.gradle
  • 依赖配置
  • Android Gradle完整配置
  • Android JNI开发和Gradle配置
  • 总结:Android开发的起点
前言
Android Gradle配置完整详细分析

文章图片
之前我们讨论了Gradle和Groovy的相关内容,本文具体介绍在Android Studio中使用Gradle的详细配置,希望得到一个Android项目的普遍配置,旨在在每个Android项目中重用这个配置。若无意外,每次新建项目,我们只需要复制这份配置即可,毕竟这个gradle配置也不见得需要经常改动。
项目顶层build.gradle
Android 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.p12keytool -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="https://www.lsbin.com/000" android:name="UMENG_APPKEY"/> < !--添加渠道号这里使用的${UMENG_CHANNEL_VALUE}如果是豌豆荚平台 这里就写成豌豆荚如果是应用宝就写成应用宝--> < meta-data android:value="https://www.lsbin.com/${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 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 Gradle配置完整详细分析】好了,到这里大概解决了Android项目的基本配置问题了,下面接着研究Java或Java Web的Maven完整配置。

    推荐阅读