*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
系列文章:
从Android Plugin源码开始彻底理解gradle构建:初识AndroidDSL(一)
从Android Plugin源码开始彻底理解gradle构建:Extension(二)
从Android Plugin源码开始彻底理解gradle构建:Task(三)
经过前面几篇文章的学习(什么?你还没看,赶紧去补补!),对gradle已经有了大致的了解了,当学习完后一定需要码代码来巩固一下,所谓:talk is cheap show me the code!今天就带大家来一场gradle实战,做一个有实用价值的自定义插件。
不知道大家有没有遇到过这样的需求:公司有一款产品,而客户需要将公司产品做制定化操作,如:修改app包名、appIcon、appName、以及引导页等一些资源文件,以便客户展示他自己的广告。这样可能会有很多定制化产品需要去打包,以前采用一个一个手动更改并打包,一打就是一下午,随着定制化越来越多,每次更新都要这样打,估计都快疯了吧。
这个时候大家首先会想到利用Android系统的多渠道打包方法productFlavors,比如这样:
android {
productFlavors {
xiaomi{
applicationId "com.xiaomi.cn"
}
google{
applicationId "com.google.cn"
}
huawei{
applicationId "com.huawei.cn"
}
}
}
这样就可以修改包名,appName等一些需求,但是资源文件可能就不太方便了,可能有童鞋会说,在res下面多放几张图片,然后利用manifestPlaceholders来修改,没错,这样的方式也可以实现,不过万一你公司的渠道定制包很很多呢?100个、500个,难道需要放那么多张没用的图片进去?那app得多大。
其实主要就是这两个问题:
1、打包时资源文件无法自动更换
2、若手动更换,一个一个打成百上千的包那时间成本可不是盖的
那么今天,我们就来写一个自动替换资源文件的gradle插件,彻底解决这个问题。
首先我们需要写个自定义插件,具体步骤我在第一篇系列文章里提到过,也有推荐文章,这里我就放出插件结构就好(文末有DEMO,大家可以有需要的话可以查看)
首先我们需要写一个Plugin,并重写他的apply方法:
public class ResourceFlavorsPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
//这里写自定义内容
}
}
首先我们理一下我们思路:
1、我们需要打很多渠道包
2、每个渠道包都需要修改包名、资源文件等
第一点我们利用AndroidDSL来实现:
android {
flavorDimensions "define"
productFlavors {
"define1" {
dimension "define"
applicationId "com.atom.define1"
manifestPlaceholders.put("appName", "定制1")
}
"define2" {
dimension "define"
applicationId "com.atom.define2"
manifestPlaceholders.put("appName", "定制2")
}
//其他渠道省略....
}
}
这个很简单就不多说了,下面就到我们需要做的事情:修改资源。
我们需要在打每个渠道包之前把对应资源文件修改成相应的,而我们每次打包都需要运行assemble系列方法,比如
打所有发布版的包:assembleRelease
打define1的发布版的包:assembleDefine1Release
以此类推,而根据上一篇文章我们又知道assemble依赖了许多task,这样的话我们就好办了,只需要在执行资源合并task之前就修改资源文件就好了。
查看源码发现preBuild这个task就是最先执行的几个task之一,所以我们需要获取到该task,执行他的doFirst即可
project.android.applicationVariants.all { variant ->
String variantName = variant.name.capitalize()
def variantFlavorName = variant.flavorName
Task preBuild = project.tasks["pre${variantName}Build"]
if (variantFlavorName == null || "" == variantFlavorName) {
return
}
preBuild.doFirst {
//在这里替换资源文件
println "${variantFlavorName} resource is changed!"
}
}
利用applicationVariants获取variant,然后就能获取到variantName也就是渠道包的打包方式,然后做一些字符串拼接,就获取到相应task名称,然后再从Project的taskContainer里取出就好。
下一步就需要修改资源文件了,而gradle如何实现这一操作呢?我也不知道,这时候只能求助官方了,通过一些时间的查阅,我终于在官方文档中找到了这个方法,其实很简单:
这是在project下的一个方法,官方文档介绍的很详细了,连demo都有,可以说相当良心了。
from和into后面分别跟源文件和被替换的文件就可以了,当然,文件夹也行,所以我们的代码就变成了这样
project.android.applicationVariants.all { variant ->
//...
preBuild.doFirst {
project.copy {
from "../resourceDir/${variantFlavorName}"
into "../app/src/main/res"
}
println "${variantFlavorName} resource is changed!"
}
}
为了更好的扩展性,能够在build文件中设置源文件位置、名称等,我们可以用extension来操作,首先创建一个pojo类
public class FlavorType {
/**
* 存放渠道包图片的路径
*/
String resourceDir
/**
* 主项目名
*/
String appName
}
再稍微修改一下我们的代码:
project.extensions.add("rfp", FlavorType)
project.afterEvaluate {
FlavorType ext = project.rfp
def resourceDir = ext.resourceDir
def appName = ext.appName
project.android.applicationVariants.all { variant ->
//...
preBuild.doFirst {
project.copy {
from "../${resourceDir}/${variantFlavorName}"
into "../${appName}/src/main/res"
}
println "${variantFlavorName} resource is changed!"
}
}
}
这样就可以在build文件中自定义了,就像这样:
rfp{
resourceDir 'definepic'
appName 'app'
}
本插件最重要的是理解为何我们可以在task前做操作以及在哪个task前做操作。
如果是看完我之前三篇文章的童鞋相信很容易理解此文,其实无非就是对gradle以及groovy语法的运用而已,这个就需要时间的积累了。
好了,此系列暂时到此就结束了,需要看Demo的童鞋请点击下面的传送们:
github地址
如果对您有帮助的话,希望给个star鼓励一下~谢谢
最最最后:我也把该项目上传到了jcenter,可以直接使用哦~具体参见github说明