欢迎转载,转载时请注明出处和作者
作者:kerwin
原文地址:
http://www.jianshu.com/p/9620a40c203f
之前已经整体的对组件化框架进行了概述:Android组件化开发框架,这篇文章只针对组件化的编译脚本的配置进行详述。
我们先回顾一下组件化的目标,可复用、热插拔、灵活发布。那么要达到这些个目标,显然不只是代码层就能完成的工作。其中灵活发布就是我们这一篇需要重点研究的内容。
如何才能算是可灵活发布呢?
- 组件可以独立运行
- 组件可以独立发布
- 组件有独立的版本
android 的gradle配置中有2种Module,library、application。其中library是发布为aar的子Module,application是可以运行打包为apk的主Module,如果我们希望组件能独立运行,则组件必须是application,但是application发布后是apk文件,apk是不能被其它Module所引用的。
为了解决上述问题,我们需要Module同时具有application以及library的特性。所以,最理想的方式是直接这样添加:
可惜这样是不行的,编译时会出现如下错误:
也就是说application与library都有android这个结构,是冲突的,不能同时存在,于是我们立马想到改成这样:
很棒~!因为编译通过了,而且只需要修改isApp的值就能动态修改Module的类型。但是如果每个Module都是这样简单的设置就可以的话,那也就不会有这篇文章了。
想一想,还有什么没考虑到的呢?
- ApplicationId在Library内无法识别
- Library与Application的AndroidMenifest文件是不一样的
- 组件在单独运行时的测试代码以及资源等应该与发布代码进行隔离
- 说好的版本管理呢?
通过上面的尝试,我们确定了build在编译时是可以采用动态参数进行动态调整的。下面我们继续完善之前未完成的工作。
1、ApplicationId在Library内无法识别
解决办法:修改module的build.gradle内容
boolean isApp = false
if (isApp) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
...
defaultConfig {
if (isApp) {
multiDexEnabled true
applicationId "com.bamboo.component"
}
...
}
...
}
2、Library与Application的AndroidMenifest文件是不一样的
3、组件在单独运行时的测试代码以及资源等应该与发布代码进行隔离
解决办法:添加Module在单独运行时的测试代码以及测试资源存放文件夹。
boolean isApp = true
if (isApp) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
...
defaultConfig {
if (isApp) {
multiDexEnabled true
applicationId "com.bamboo.component"
}
...
}
if (isMyApp) {
flavorDimensions "run"
productFlavors {
run {
dimension "run"
minSdkVersion 21
}
}
}
...
}
配置好上面的代码后,还需要修改Module的目录结构,修改后如下:
这里是利用了gradle的sourceSets方案,每一个flavor都会匹配一个对应的sourceSet,每个sourceSet都有自己的源码文件夹。文件夹说明:
/src/main : 核心源码默认文件夹
/src/run : 在切换module为可运行模式时的扩展源码文件夹
/src/androidTestRun、/src/testRun :扩展的测试代码文件夹
需要特别注意的地方
main文件夹中的class不能引用run中的class,因为run文件夹中的class在作为library发布的时候是非源码文件夹,不参与编译,如果引用了里面的代码编译时会报错。而run文件夹的类可以随意引用java中的类。main文件夹与run文件夹不能出现重复的类。
main文件夹中不能引用run文件夹中的资源,原因同上,并且不能出现重复的资源。
main/AndroidManifest.xml和run/AndroidManifest.xml中的配置信息在可执行模式时编译器会合并两个文件。所以run/AndroidManifest.xml中配置可运行时需要的额外内容即可。
最后我们在run/java中添加我们的测试入口类TestActivity.java,在run/res中添加测试layout文件activity_test.xml,在run/AndroidManifest.xml中设置TestActivity为Main入口。
将isApp设置为true
,我们就能直接运行我们的Module。
将isApp设置为false
,就能将Module发布成aar。
进阶优化
到这里其实已经能完成我们的前3个目的,只需要修改一个配置,就能随意更改Module的类型,但是如果Module有很多个的时候,想同时修改几个,就得修改好几个build.gradle文件,而且,每改一次build.gradle文件就需要全部重新build一次,这样有点浪费时间。
那我们能不能修改配置之后不需要重新build就能修改Module的类型呢?
答案是可以。
第一步:在工程的根目录(与setting.gradle文件同级)新建include.properties文件,然后添加如下内容:
applications=module
第二步:在Module的build.gradle文件中添加如下代码:
static findProperty(String propertiesFile, String propertyName) {
if (propertiesFile != null) {
java.util.Properties properties = new java.util.Properties()
InputStream inputStream = new File(propertiesFile).newDataInputStream()
properties.load(inputStream)
def propertyValue = properties.getProperty(propertyName)
return propertyValue == null ? "" : propertyValue
}
return "";
}
static verifyProperties(Project project, String propertyName, String property) {
return findProperty(project.getProjectDir().absolutePath + '/include.properties', propertyName).split(",").contains(property)
}
第三步:修改isApp的取值:
boolean isApp = verifyProperties(rootProject, 'applications', name)
原理:
第一步解析:include.properties用来配置哪些Module作为Application来使用,其中后面的module是Module的名称,如果想配置多个Module为Application类型,用','隔开。
第二步解析:gradle的脚本语言是Groovy,Groovy语言是java代码的扩展语言,所以我们可以在build.gradle中使用几乎所有的java语法来灵活设置我们的脚本。这部分代码主要是读取include.properties文件中的属性值。
第三步解析:读取include.properties 中的applications属性值,并验证当前Module是否已配置在其中,如果在就设置isApp为true,否则为false。
这样修改以后,只需要修改include.properties文件中的内容,然后点击下图中的刷新按钮就能切换Module的类型。
4、说好的版本管理呢?
解决这个问题之前,先思考一下为什么需要版本管理?
为了存档,为了热修复的时候可以不用全部重新发布Module
我们使用常规的compile project('...')的方式引入Module时是无法进行版本管理的。版本只对已发布到maven仓库的library才有效。
那难道我们要把组件都发布成maven吗?是的。
那难道我们要把组件都发布到远处仓库吗?不是的。
我们可以把组件发布到本地仓库,甚至于Project文件夹目录的本地仓库。下面是发布到Project文件夹的方案解析。
首先:我们在工程根目录的build.gradle中添加如下代码:
buildscript {
...
}
allprojects {
apply plugin: 'maven'
repositories {
jcenter()
maven {
//本地maven仓库,路径是./projectName/.repo-tmp
url 'file://' + project.rootProject.rootDir + File.separator + '.repo-tmp'
}
}
configurations.all {
// check for updates every build
resolutionStrategy.cacheChangingModulesFor 1, 'seconds'
}
}
subprojects { subp ->
//发布后的group名称
project.group = 'com.bamboo.component'
subp.afterEvaluate {
//只能发布设置了版本号的Module
if (!(subp.version + '').equals('unspecified')) {
if (subp.extensions.findByName('android') != null
&& !getPlugins().hasPlugin("com.android.application")) {
android.libraryVariants.all { variant ->
if (variant.name.equals('release')) {
def generateandroidSourcesJar = task("generate${variant.name.capitalize()}SourcesJar", type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
artifacts {
archives generateandroidSourcesJar
}
}
}
} else if (subp.extensions.findByName('android') == null) {
//java工程
//生成source 文件
task('sourcesJar', type: Jar) {
from sourceSets.main.java.srcDirs
classifier = 'sources'
}
artifacts {
archives sourcesJar
}
}
uploadArchives {
repositories.mavenDeployer {
repository(url: 'file://' + project.rootProject.rootDir + File.separator + '.repo-tmp')
pom.groupId = project.group
pom.artifactId = project.name
pom.version = project.version
}
}
}
}
}
然后在需要发布的Module的build.gradle中添加版本号:
version = '1.0'
android{
...
}
刷新工程,会发现右侧的task list多了一组task,如下图upload task group。
点击upload下面的uploadArchives,即可将你的Module发布到Project下的 .repo-tmp 文件夹中,如下图:
然后修改我们的引用Module的方式:
原引用方式:
compile project(':module')
本地maven引用方式:
compile 'com.bamboo.component:module:1.0'
本地maven的方案到这里就讲完了。
发布到本地仓库的优缺点:
优点:
- 可以进行组件的版本管理,
- 节约整个工程运行时的build时间。
- 方便重用。
缺点是每次Module修改的时候不能实时生效需要重新发布到仓库刷新后才能生效。
写在最后
组件化的配置以及在实践中需要注意的地方不是一篇文章就能说完的,所以如果大家在使用上述方案时遇到了什么麻烦,可留言,我们可以一起研究学习。
本文示例源码,源码把读取include配置的代码放到了buildSrc中,实际效果相同。