applicationId ‘com.example.android.paid’
resValue “string”,‘tag’,‘paid’
dimension ‘isFree’
}
domestic{
dimension ‘area’
}
overseas {
dimension ‘area’
}
}
通过上面的定义,我们就拥有了四种组合,分别是
-
freeDomestic
免费国内版 -
freeOverseas
免费国际版 -
paidDomestic
付费国内版 -
paidOverseas
付费国际版
这里有个注意的地方是,flavor组合的顺序是根据flavorDimensions
的元素排序决定的。假如我们将
isFree
和area
的顺序颠倒一下
flavorDimensions “area”,‘isFree’
那么原先的freeDomestic
将变成domesticFree
。这会造成什么影响呢?
实际上第一个flavor是具有高优先级的。 假如free和domestic都定义了各自的包名
productFlavors{
flavorDimensions “area”,‘isFree’
free {
applicationId ‘com.example.android.free’
dimension ‘isFree’
}
domestic{
dimension ‘area’
applicationId ‘com.example.android.domestic’
}
}
那么最终的包名将会是com.example.android.domestic
。
在某些情况下,app模块包含了某些flavors而library模块却没有,在这种情况下,app无法和library的flavor相匹配,通过指定matchingFallbacks
来兜底。例如下面这个例子,app依赖了library
//app build.gradle
productFlavors{
flavorDimensions ‘isFree’
free {
dimension ‘isFree’
matchingFallbacks = [‘demo’]
}
paid {
dimension ‘isFree’
}
}
//library build.gradle
productFlavors{
flavorDimensions ‘isFree’
demo {
dimension ‘isFree’
}
paid {
dimension ‘isFree’
}
}
当执行assembleFreeRelease
时,由于library不存在free
的flavor
,那么会使用demo
进行替代。
如果app不指定matchingFallbacks
的话,是无法通过编译的,会报如下错误
Could not resolve all artifacts for configuration ‘:app:freeDebugCompileClasspath’.
Could not resolve project :library.
Required by:
project :app
所以,如果library和app都定义了ProductFlavor
,那么需要对齐,否则需要指定matchingFallbacks
进行兜底。注意,library和app需要定义在同个dimension
下。
========================================================================
SourceSet
即源代码集,我们可以使用 SourceSet
代码块更改 Gradle 为源代码集的每个组件收集文件的位置。这样我们就无需改变文件的位置。换句话说,有了SourceSet
,我们可以按照自己的偏好指定代码和资源的路径
属性
以下是AndroidSourceSets提供的属性
| Property | Description |
| — | — |
| aidl | Android AIDL目录 |
| assets | Assets目录 |
| java | Java目录 |
| jni | JNI目录 |
| jniLibs | JNI libs目录 |
| manifest | AndroidManifest路径 |
| name | source set的名称 |
| renderscript | RenderScript目录 |
| res | res资源目录 |
| resources | Java resources目录 |
以上的配置除了manifest对应的是AndroidSourceFile
对象,即为单一文件,其余的都是AndroidSourceDirectorySet
对象,我们来看下AndroidSourceDirectorySet
接口提供了哪些方法。
public interface AndroidSourceDirectorySet extends PatternFilterable {
@NonNull
String getName();
//添加资源路径到集合中,最终AGP会从集合里取出所有的文件
@NonNull
AndroidSourceDirectorySet srcDir(Object srcDir);
//添加多个资源路径到集合中
@NonNull
AndroidSourceDirectorySet srcDirs(Object… srcDirs);
//指定资源的路径,与上面两个方法不同的时候,该方法会覆盖原有的集合
@NonNull
AndroidSourceDirectorySet setSrcDirs(Iterable<?> srcDirs);
//以FileTree形式返回资源
@NonNull
FileTree getSourceFiles();
//返回过滤规则
@NonNull
PatternFilterable getFilter();
//将源文件夹作为一个列表返回
@NonNull
List getSourceDirectoryTrees();
//返回资源文件列表
@NonNull
Set getSrcDirs();
/** Returns the [FileCollection] that represents this source sets. */
@Incubating
FileCollection getBuildableArtifact();
}
因此我们可以修改源集的位置,我们来看一个简单配置
def basePath = projectDir.parentFile.absolutePath
def resPath = new File(basePath, “res”)
def manifestPath = new File(basePath, “AndroidManifest.xml”)
sourceSets {
main {
res.srcDir(resPath)
manifest.srcFile(manifestPath)
}
}
我们可以通过sourceSets
任务来打印具体的配置
:app:sourceSets
//输出
main
Compile configuration: compile
build.gradle name: android.sourceSets.main
Java sources: [app/src/main/java]
//AndroidMnaifest路径被改到app根目录下
Manifest file: AndroidManifest.xml
//可以看刚才添加的res目录
Android resources: [app/src/main/res, res]
Assets: [app/src/main/assets]
AIDL sources: [app/src/main/aidl]
RenderScript sources: [app/src/main/rs]
JNI sources: [app/src/main/jni]
JNI libraries: [app/src/main/jniLibs]
Java-style resources: [app/src/main/resources]
paid
Compile configuration: paidCompile
build.gradle name: android.sourceSets.paid
Java sources: [app/src/paid/java]
Manifest file: app/src/paid/AndroidManifest.xml
Android resources: [app/src/paid/res]
Assets: [app/src/paid/assets]
AIDL sources: [app/src/paid/aidl]
RenderScript sources: [app/src/paid/rs]
JNI sources: [app/src/paid/jni]
JNI libraries: [app/src/paid/jniLibs]
Java-style resources: [app/src/paid/resources]
//省略其他源集
…
方法
| 方法 | 描述 |
| — | — |
| setRoot(path) | 将源集的根设置为给定的路径。源集合的所有条目都位于此根目录下。 |
通过setRoot
方法,我们可以直接指定某个源集的目录,例如如果你有多个ProductFlavor
,并且创建了对应的源集目录,那么我们可以把非main的目录都放到一起,避免src目录太多文件。
sourceSets.all { set ->
if (set.name.toLowerCase().contains(flavor)
&& !set.name.equals(“main”)) {
set.setRoot(“src/other/$flavor”)
}
}
main
源集包含了所有其他构件变体共用的代码和资源,即所有的其他构建变体,src/main是其共同拥有的。
其他源集目录为可选项,如果我们想要为某个单独的构建变体添加特有的代码或者资源,可以创建对应的目录。例如,构建“demoDebug
”这个变体, Gradle 会查看以下目录,并为它们指定以下优先级
-
src/demoDebug/
(build 变体源代码集) -
src/debug/
(build 类型源代码集) -
src/demo/
(产品变种源代码集) -
src/main/
(主源代码集)
当存在重复的资源时,Gradle 将按以下优先顺序决定使用哪一个文件(左侧源集替换右侧源集的文件和设置):
构建变体 > 构建类型[BuildType] > 产品风味[ProductFlavor] > 主源集[main] > 库依赖项
- java/ 目录中的所有源代码将一起编译以生成单个输出
注意的是,java文件是不能被覆盖的,如果我们在main目录中创建了src/main/Utility.java,那么是不能其他源集目录中定义同名文件进行覆盖的,因为,Gradle 在构建过程中会查看这两个目录并抛出“重复类”错误。如果我们想要在不同的 build 类型有不同版本的 Utility.java,只能让每个 build 类型定义各自的文件版本,这样是比较麻烦的。
-
所有Manifest都将合并为一个清单。合并的优先级和上面提到的一致。
-
同样,values/ 目录中的文件也会合并在一起。如果两个文件同名,例如存在两个 strings.xml 文件,按照上述的优先级覆盖。
-
res/ 和 asset/ 目录中的资源会打包在一起。
-
最后,在构建 APK 时,Gradle 会为库模块依赖项随附的资源和清单指定最低优先级。
回顾上面的AndroidSourceDirectorySet
接口,其继承了PatternFilterable
接口
public interface PatternFilterable {
Set getIncludes();
Set getExcludes();
PatternFilterable setIncludes(Iterable includes);
PatternFilterable setExcludes(Iterable excludes);
PatternFilterable include(String… includes);
PatternFilterable include(Iterable includes);
PatternFilterable include(Spec includeSpec);
PatternFilterable include(Closure includeSpec);
PatternFilterable exclude(String… excludes);
PatternFilterable exclude(Iterable excludes);
PatternFilterable exclude(Spec excludeSpec);
PatternFilterable exclude(Closure excludeSpec);
该接口提供了一系列的include
和exclude
方法,我们可以对源集目录做一些过滤。
sourceSets {
main {
java {
exclude ‘com/cooke/library/Test.java’
exclude ‘com/cooke/library/model/**.java’
}
}
}
上面例子提到,其他的源集目录无法覆盖同名java文件,但是我们可以通过SourceSet
对main目录中的java进行exclude.
注意:include
和exclude
并不能对res生效,如果想要对res进行过滤,需要通过定义res/raw/keep.xml
,详见Android文档,这里就不具体展开了。
======================================================================
Variant
即为变体,可以分为ApplicationVariant
和LibraryVariant
,分别对应了apk的变体和aar的变体。变体的构成由BuildType
和ProductFlavor
组合而成.即
variant = buildType * productFlavor
例如上面我们定义了free
和paid
两种productFlavor
,结合debug
和release
两种buildType
,就产生了4种组合,如下图
我们可以遍历ApplicationVariant
或LibraryVariant
列表,干预构建apk和aar的过程。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
Android 基础知识点
Java 基础知识点
Android 源码相关分析
常见的一些原理性问题
希望大家在今年一切顺利,进到自己想进的公司,共勉!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
Android开发知识点,真正体系化!**
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
Android 基础知识点
Java 基础知识点
Android 源码相关分析
常见的一些原理性问题
[外链图片转存中…(img-W7LIDvAj-1712497181065)]
希望大家在今年一切顺利,进到自己想进的公司,共勉!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!