“Gradle的配置太多了,经常版本更新还有变化,而且它还能扩展,记是记不住了,只能用到再搜了,哎,难顶”
真的难顶,但是我想挑战一下…
本文介绍的重点:
前置必读:https://blog.csdn.net/yechaoa/article/details/130174456
Gradle
的配置主要是用来管理Gradle自己的运行环境和我们的项目,这句话听起来有点抽象,用大白话拆解一下:
org.gradle.jvmargs=-Xmx2048m
等,这类配置往往相对固定的,因为它是跟随项目走的,即使是多团队协作,大家基本也都是用的同一个环境。为什么会有优先级的说法呢,是因为Gradle的配置不仅丰富,而且渠道还不是单一的,这种渠道的不单一,也说明了Gradle优秀的扩展性和定制性。
比如我们要运行编译,既可以用Android Studio自带的可视快捷按钮,也可以使用命令行,而这两种编译方式的取配置是不一样的(根据优先级),那自然编译的结果也不一样,所以搞清楚配置的优先级也能帮助我们在不同场景下的使用需求。
优先级分以下4种(由高到低
):
Command-line flags
:命令行标志,如–stacktrace,这些优先于属性和环境变量;System properties
:系统属性,如systemProp.http.proxyHost=somehost.org存储在gradle.properties文件中;Gradle properties
:Gradle属性,如org.gradle.caching=true,通常存储在项目根目录或GRADLE_USER_HOME环境变量中的gradle.properties文件中;Environment variables
:环境变量,如GRADLE_OPTS,由执行Gradle的环境源;以上4种,我们较常用的是命令行标志和项目根目录的gradle.properties文件。
Android Studio Dolphin | 2021.3.1
Gradle 7.4
看一下新建项目之后的初始配置:
有两个一级目录,分别是app
和Gradle Scripts
,Gradle Scripts里面除了proguard-rules.pro用于混淆之外,其余的6
个文件配置今天都会介绍到。
为了把这些文件的关系看的更直观一点,打印个tree看看:
.
├── README.md
├── app
│ ├── build.gradle
│ ├── ...
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── local.properties
└── settings.gradle
.
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat
顾名思义,wrapper
是对Gradle的一层封装,封装的意义在于可以使Gradle的版本跟着项目走,这样这个项目就可以很方便的在不同的设备上运行,比如开源项目一般都不会把gradle文件夹设置到gitignore
文件里,就是为了保证你clone下来是可以运行的,在团队协作上也是如此。
下面介绍一下上面这几个文件的作用:
gradle-wrapper.jar
:主要是Gradle的运行逻辑,包含下载Gradle;gradle-wrapper.properties
:gradle-wrapper的配置文件,核心是定义了Gradle版本;gradlew
:gradle wrapper的简称,linux下的执行脚本gradlew.bat
:windows下的执行脚本重点看一下gradle-wrapper.properties:
#Sun Oct 16 15:59:36 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
Gradle版本的下载地址,可以查看Gradle官方的版本发布,或者去Gradle的Github。
这里有几种类型,分别是all、bin、doc:
doc
:顾名思义,用户文档;bin
:即binary,可运行并不包含多余的东西;all
:包含所有,除了bin之外还有用户文档、sample等;所以一般选择bin就可以了。
Gradle、Android Gradle Plugin、Android Studio三者的版本映射关系查看Android官网Gradle版本说明。
位于项目的根目录下,用于定义适用于项目中所有模块的依赖项。
Gradle7.0之后,project下的build.gradle
文件变动很大,默认只有plugin的引用了,其他原有的配置挪到settings.gradle
文件中了。
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.3.0' apply false
id 'com.android.library' version '7.3.0' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}
plugin格式:
id «plugin id» version «plugin version» [apply «false»]
id和version比较好理解。
apply false
表示不将该plugin应用于当前项目,比如在多项目构建中,我只想在某个子项目依赖该plugin就好了,那可以这么写:
plugins {
id "yechaoa" version "1.0.0" apply false
}
subprojects { subproject ->
if (subproject.name == "subProject") {
apply plugin: 'yechaoa'
}
}
apply plugin在7.0之前的写法:
apply plugin: 'kotlin-android'
// 加上下面
buildscript {
...
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.20"
}
}
除了自带插件和远端插件之外,还可以是我们自定义的脚本插件
,比如自定义了一个common.gradle
apply from: 'common.gradle'
这两种方式的区别:
from
关键字,后面紧跟一个脚本文件,可以是本地的,也可以是网络存在的,如果是网络上的话要使用HTTP URL
。
后面的文章也会讲到插件的依赖管理。
来看下Gradle7.0之前,project下的build.gradle
文件
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.5.0"
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
buildscript中的声明是gradle脚本自身需要使用的资源。可以声明的资源包括依赖项、第三方插件、maven仓库地址等。7.0之后改在settings.gradle中配置了(pluginManagement)。
多聊一点,为什么需要buildscript/pluginManagement,因为项目的编译过程,除了项目本身需要的依赖之外,还有就是Gradle自己运行需要的依赖,比如你的插件要去hook Gradle的生命周期,因为运行时机的不同,所以加了一个script,当然buildSrc也可以解决这个问题。
项目全局属性,多用于自定义,比如把关于版本的信息都利用ext放在另一个新建的gradle文件中集中管理,比如version.gradle,然后apply引用即可,这是较为早期的版本管理方式。
仓库,比如google()、maven()、jcenter()、jitpack等三方托管平台。
当然配置了仓库还不够,我们还需要在dependencies{}里面的配置里,把需要配置的依赖用classpath配置上,因为这个dependencies在buildscript{}里面,所以代表的是Gradle需要的插件。
7.0之后把plugin的配置简化了,由apply+classpath简化为只要apply就可以了。
allprojects块的repositories用于多项目构建,为所有项目提供共同所需依赖包。而子项目可以配置自己的repositories以获取自己独需的依赖包。
运行gradle clean时,执行此处定义的task。
该任务继承自Delete,删除根目录中的build目录。相当于执行Delete.delete(rootProject.buildDir)。其实这个任务的执行就是可以删除生成的Build文件的,跟Android Studio的clean是一个道理。
位于每个 project/module/ 目录下,用于为其所在的特定模块配置 build 设置。
其实跟7.0之前差别也不是太大。
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.yechaoa.gradlex'
compileSdk 32
defaultConfig {
applicationId "com.yechaoa.gradlex"
minSdk 23
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 30
defaultConfig {
applicationId "com.yechaoa.app"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation "com.google.android.material:material:1.3.0"
implementation project(':yutils')
implementation project(':yutilskt')
}
前文介绍了,参考 # 3.2、build.gradle(Project)
应用程序的命名空间。主要用于访问应用程序资源。
是Android插件提供的一个扩展类型,可以让我们自定义Gradle Android工程,是Gradle Android工程配置的唯一入口。
编译所依赖的Android SDK的版本,即API Level,可以使用此API级别及更低级别中包含的API功能。
(api等级对应关系)
是构建该Android工程所用构建工具的版本。
后被废弃了,因为7.0之后,假如你的buildToolsVersion是30.0.1,低于AGP7.0.2所要求的的最低版本30.0.2,那就会使用30.0.2的版本,而不是你指定的版本,后续AGP会应用一个对应的默认的版本。
默认配置,它是一个ProductFlavor。ProductFlavor允许我们根据不同的情况同时生成多个不同的apk包。
包名,app的唯一标识。其实他跟AndroidManifest里面的package是可以不同的,他们之间并没有直接的关系。
是支持Android系统的api level,这里是23,也就是说低于Android 6.0版本的机型不能使用这个app。
是标示app基于哪个Android版本开发的,这里是32,也就是适配到Android 12.0。
版本号,一般用于版本控制。
版本名称,公开信息,默认1.0,7.0之前默认是1.0.0,行业规范3位数,但并没有强制。
用于配置该BuildType是否启用自动拆分多个Dex的功能。一般用程序中代码太多,超过了65535个方法的时候。
多平台编译,生成有so包的时候使用,之前用armeabi的比较多,要求做32/64位适配之后开始拆分(提高性能),'armeabi-v7a’表示32位cpu架构, 'arm64-v8a’表示64位, 'x86’是只模拟器或特定rom。
一般使用第三方提供的SDK的时候,可能会附带so库。
源代码集合,是Java插件用来描述和管理源代码及资源的一个抽象概念,是一个Java源代码文件和资源文件的集合,我们可以通过sourceSets更改源集的Java目录或者资源目录等。
构建类型,在Gradle Android工程中,它已经帮我们内置了release构建类型,一般还会加上debug,两种模式主要区别在于,能否在设备上调试以及签名不一样,其他代码和文件资源都是一样的(如果没有做处理的话-log)。
buildTypes {
debug {
buildConfigField("String", "AUTHOR", "\"yechaoa\"")
minifyEnabled false
}
release {
buildConfigField("String", "AUTHOR", "\"yechaoa\"")
signingConfig signingConfigs.release
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
签名配置,一个app只有在签名之后才能被发布、安装、使用,签名是保护app的方式,标记该app的唯一性。
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
v1SigningEnabled true
v2SigningEnabled true
}
}
官方建议不直接明文写,而是从文件读取。
多渠道打包配置,可以实现定制化版本的需求。
他是BuildConfig文件的一个函数,而BuildConfig这个类是Android Gradle构建脚本在编译后生成的。比如版本号、一些标识位什么的。
选择编译的版本。
开启或关闭构建功能,常见的有viewBinding、dataBinding、compose。
buildFeatures {
viewBinding = true
// dataBinding = true
}
我们知道,Android中的Java/kotlin源代码被编译成class字节码后,在打包成apk的时候被dx命令优化成Android虚拟机可执行的dex文件。
对于这些生成dex文件的过程和处理,Android Gradle插件都帮我们处理好了,Android Gradle插件会调用SDK中的dx命令进行处理。
可以设置编译时所占用的内存(javaMaxHeapSize),从而提升编译速度。
7.0之后已经废弃。
Java编译选项,指定java环境版本。
Kotlin编译选项,通常指定jvm环境。
Compose功能的可选设置。比如指定Kotlin Compiler版本,一般用默认。
用于配置lint选项。Example
我们平时用的最多的大概就这个了,Gradle3.2之后有依赖方式的变更,因为不支持依赖关系细粒度的范围界定。
implementation 'androidx.core:core-ktx:1.7.0'
implementation指令依赖是不会传递的,也就是说当前引用的第三方库仅限于本module内使用,其他module需要重新添加依赖才能用,使用api指令的话可以传递。(后面也会细讲依赖)
一图胜千言:
图源:19snow93
位于项目的根目录下,用于定义项目级代码库设置。
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "GradleX"
include ':app'
插件管理,指定插件下载的仓库,及版本。
依赖管理,指定依赖库的仓库地址,及版本。即7.0之前的allprojects。
顺序决定了先从哪个仓库去找依赖库并下载,一般为了编译稳定,会把阿里的镜像地址(或自建私有仓库)放在Google()仓库之前。
项目名称。
用于指定构建应用时应将哪些模块包含在内,即参与构建的模块。
也可用于动态引用,在编译提速时,会module打成aar依赖来节省编译时间,但是为了开发方便,一般会动态选择哪些module使用源码依赖。
位于项目的根目录下,用于指定 Gradle 构建工具包本身的设置,也可用于项目版本管理。
比如Gradle 守护程序的最大堆大小、编译缓存、并行编译、是否使用Androidx等等。
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# ---------- 编译相关 start ----------
#并行编译
org.gradle.parallel=true
#构建缓存
org.gradle.caching=true
# ---------- 编译相关 end ----------
# ---------- 版本相关 start ----------
yechaoaPluginVersion="1.0.0"
# ---------- 版本相关 end ----------
在版本管理中,可以从gradle.properties
文件中读取版本,不仅可以作用于依赖库,也可作用于依赖插件。本质上是key-value
形式的参数。
pluginManagement {
plugins {
id 'com.yechaoa.gradlex' version "${yechaoaPluginVersion}"
}
}
位于项目的根目录下,用于指定 Gradle 构建配置本地环境属性,也可用于项目环境管理。
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Mon Feb 08 19:07:41 CST 2021
sdk.dir=/Users/yechao/Library/Android/sdk
ndk.dir=/Users/yechao/Library/Android/ndk
sdk.dir
:SDK 的路径ndk.dir
:NDK 的路径,已废弃,用SDK目录下的NDK目录。可以用作项目本地调试的一些开关:
isRelease=true
#isDebug=false
#isH5Debug=false
前面介绍了这么多的Gradle配置,最常用的就是app > build.gradle了,这个文件里的配置是最多的,也是跟Android开发打交道最多的,那么,这些配置是哪里来的呢?
再来回顾一下app > build.gradle:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.yechaoa.gradlex'
compileSdk 32
defaultConfig {
applicationId "com.yechaoa.gradlex"
minSdk 23
targetSdk 32
versionCode 1
versionName "1.0"
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
}
按结构可以分为3部分,plugins、android和dependencies。
在上一篇入门文章中我们讲到,Gradle是一个通用的自动化构建工具,通用
也就是说,不仅可以构建Android项目,还可以构建java、kotlin、swift等项目。再结合android{}
里面的配置属性,我们可以确定,那这就是Android项目专属的配置DSL
了。
既然如此,如果让你来设计Gradle的架构框架你会怎么做?
这种架构通常都是底层通用能力
+上层定制化
的经典设计,上层定制化也就是说Android项目有Android的配置DSL,Java项目有Java配置的DSL,不同项目有不同配置。
那么回到问题本身,android{}
是怎么来的,创建项目就有,那是跟随开发工具来的?好像也不对啊,Android Studio不仅可以开发Android项目啊,别的也行,而且别的开发工具也能开发Android项目呢。那既然不是跟随开发工具,又要做到定制化,那是怎么来的。
回到build.gradle
配置结构的第一部分:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
注意到,这里有一个名为com.android.application的plugin
,看名字也是android项目专属无疑了,再去源码里看一下这个plugin是干嘛的。
看Gradle源码有个比较简单的方式,就是直接拉依赖,比如我现在用的Gradle7.4,直接依赖对应版本:
implementation "com.android.tools.build:gradle:7.4"
如果发现还是点不进去源码,再把下载Gradle的类型改了,gradle-7.4-bin.zip > gradle-7.4-all.zip
。完事重新sync就可以直接点进源码了。
提炼几个信息:
这几个信息足够验证我们之前的猜想了,android{}是通过插件的方式引入的。
然后点进去:
@HasInternalProtocol
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
...
/**
* Configures the dependencies for this project.
*
*
This method executes the given closure against the {@link DependencyHandler} for this project. The {@link
* DependencyHandler} is passed to the closure as the closure's delegate.
*
*
Examples:
* See docs for {@link DependencyHandler}
*
* @param configureClosure the closure to use to configure the dependencies.
*/
void dependencies(Closure configureClosure);
...
}
是一个dependencies
函数,接收一个闭包
。
再来看BaseAppModuleExtension 的android:
android(Closure configuration)
也是接收一个闭包,这个闭包其实就是android{}这个配置,配置里面定义的就是我们日常编译运行需要的配置参数,defaultConfig、minSdk、versionCode之类的。
再看下BaseAppModuleExtension源码:
/** The `android` extension for base feature module (application plugin). */
open class BaseAppModuleExtension(
dslServices: DslServices,
bootClasspathConfig: BootClasspathConfig,
buildOutputs: NamedDomainObjectContainer<BaseVariantOutput>,
sourceSetManager: SourceSetManager,
extraModelInfo: ExtraModelInfo,
private val publicExtensionImpl: ApplicationExtensionImpl
) : AppExtension(
dslServices,
bootClasspathConfig,
buildOutputs,
sourceSetManager,
extraModelInfo,
true
), InternalApplicationExtension by publicExtensionImpl {
// Overrides to make the parameterized types match, due to BaseExtension being part of
// the previous public API and not wanting to paramerterize that.
override val buildTypes: NamedDomainObjectContainer<BuildType>
get() = publicExtensionImpl.buildTypes as NamedDomainObjectContainer<BuildType>
override val defaultConfig: DefaultConfig
get() = publicExtensionImpl.defaultConfig as DefaultConfig
override val productFlavors: NamedDomainObjectContainer<ProductFlavor>
get() = publicExtensionImpl.productFlavors as NamedDomainObjectContainer<ProductFlavor>
override val sourceSets: NamedDomainObjectContainer<AndroidSourceSet>
get() = publicExtensionImpl.sourceSets
override val composeOptions: ComposeOptions = publicExtensionImpl.composeOptions
override val bundle: BundleOptions = publicExtensionImpl.bundle as BundleOptions
override val flavorDimensionList: MutableList<String>
get() = flavorDimensions
override val buildToolsRevision: Revision
get() = Revision.parseRevision(buildToolsVersion, Revision.Precision.MICRO)
override val libraryRequests: MutableCollection<LibraryRequest>
get() = publicExtensionImpl.libraryRequests
}
我们可以看到一些熟悉的配置,比如buildTypes、defaultConfig、sourceSets等等。不过这只是一部分,剩下的配置在父类AppExtension
里,就不贴代码了。
然后android{}这个配置标签是怎么创建出来的呢?
/** Gradle plugin class for 'application' projects, applied on the base application module */
public class AppPlugin extends AbstractAppPlugin<...> {
//...
@NonNull
@Override
protected ExtensionData<...> createExtension(...) {
// ...
if (getProjectServices().getProjectOptions().get(BooleanOption.USE_NEW_DSL_INTERFACES)) {
// noinspection unchecked,rawtypes: Hacks to make the parameterized types make sense
Class<ApplicationExtension> instanceType = (Class) BaseAppModuleExtension.class;
BaseAppModuleExtension android =
(BaseAppModuleExtension)
project.getExtensions()
.create(
new TypeOf<ApplicationExtension>() {},
"android",
instanceType,
dslServices,
bootClasspathConfig,
buildOutputs,
dslContainers.getSourceSetManager(),
extraModelInfo,
applicationExtension);
project.getExtensions()
.add(
BaseAppModuleExtension.class,
"_internal_legacy_android_extension",
android);
initExtensionFromSettings(applicationExtension);
return new ExtensionData<>(android, applicationExtension, bootClasspathConfig);
}
BaseAppModuleExtension android =
project.getExtensions()
.create(
"android",
BaseAppModuleExtension.class,
dslServices,
bootClasspathConfig,
buildOutputs,
dslContainers.getSourceSetManager(),
extraModelInfo,
applicationExtension);
initExtensionFromSettings(android);
return new ExtensionData<>(android, applicationExtension, bootClasspathConfig);
}
//...
}
我把多余代码都删了,简而言之,在AppPlugin里用project.getExtensions().create()
方法创建的"android"标签,AppPlugin即id为’com.android.application’的插件,跟自定义Plugin一样配置,后续再展开。
其实所有的DSL配置都是通过插件的方式引入的。
最后我们再来捋一下思路:
我们先是简单介绍了Gradle的配置,以及配置的优先级和初始配置等概念,然后针对我们常用的配置进行了详细的介绍,以及Gradle7.0版本前后的对比,最后通过源码分析介绍了Android核心配置是怎么来的。
源码部分对新手可能有点不太友好,闭包、DSL、Plugin这些概念可能暂时还不太好理解,不过不用担心,后面会继续讲到,先了解下大致流程就行~
写作不易,点个赞吧!
https://github.com/yechaoa/GradleX