Gradle在实际网站开发中的用处
我们都知道,任何一门技术都有特定的使用场景,很少有计算机语言能够应用在所有的场景下,也就是说你碰到的这个问题确实可以用gradle去解决,那我们才去考虑使用gradle而不是为了用而用,下面我来讲一下,在我最近的工作中用到gradle的一个实例。
具体的技术背景需求我就不展示说了,与gradle相关的技术点就是,要在编译的时候,自动去生成一个类,然后将一些编译时期应用程序的信息写到这个类中去,具体到我这里的需求就是,要在编译的时候将所有工程中依赖了某个特定plugin(可以假定为所有apply了com.android.application)的module的名字都统计到一个类中去。以供应用程序去使用(实际需求是为所有app module生成路由表).
总结一下我们的需求就是:要统计一个完整的工程中有哪几个工程引入了com.android.application这个插件,那我们应该怎么做呢,当然你可以说,我手动创建一个类,里面一个map每次新增了app module的时候,我手动加一行数据,这种方式固然可以,但是太烦了,而且容易忘记,此时我们gradle自动化的技术就可以派上用场了。
下面,我把核心代码贴出来,大家就可以看明白。
第一步,我定义了一个单独的plugin,也就是说只要引入我这个自定义Plugin你就可以实现这个功能。先来看一下自定义plugin的主体很简单。
没有任何难度,只需要继成自Plugin这个接口,然后重写apply方法即可。这里我就不再多说了,大家不了解自定义Plugin的可以看我的课程或者其它一些文章。
第二步,开始判断当前Project是否包含了com.android.application这个插件。如何判断,也非常简单,只需要你对gradle的API有一定了解就可以知道,下面看我们的代码。
project.afterEvaluate {
if (it.plugins.hasPlugin(AppPlugin)) {
appModuleCompileDependencies << project.name
appModuleCompileDependencies.addAll(
project.getConfigurations().getByName("compile").dependencies.name)
}
// Record router modules' name, include library and app modules.
def routerModule = it.plugins.hasPlugin(
RouterPlugin) && appModuleCompileDependencies.contains(it.project.name)
if (routerModule) modulesSet.add(validateName(it.name))
}
通过it(即当前Project)plugins方法拿到每个Project的PluginContainer实例,然后使用这个PluginContainer去判断,如果为true,则说明是一个app module,我们在最后一行代码,将其存储到一个set中去保存起来。
第三步,上一步完成以后,那我们的moduleSet中就统计到了工程中所有app module,那最后我们要做的就是将这些信息写入到一个类中去,让我们的源码去使用。一开始实现这一步的时候,我是通过gradle去动态实现一个Java类,然后将此类去参与编译,核心就是将生成的java源文件添到加编译环节中,核心API是:
/**
* Adds to the variant a task that generates Java source code.
*
* This will make the generate[Variant]Sources task depend on this task and add the
* new source folders as compilation inputs.
*
* The new source folders are also added to the model.
*
* @param task the task
* @param sourceFolders the source folders where the generated source code is.
*/
void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection sourceFolders);
这个方法是BaseVariant也就是每个最终要生成的变体都可以在编译时期去为其动态的添加要编译的类。第一个参数是生成java源文件对应的Task,第二个参数是生成的Java源文件所在的目录。
这样,就实现了在我们编译的时候,通过gradle动态的去保存一个工程中所有的app类型的module,那到这里是不是就结束了呢,功能上其实已经实现了,但后来我又思考了一下,我们在编译一个工程的时候,android-gradle-plugin这个插件总是会为我们生成一个BuildConfig类,这个类中其实也放的是一些编译期的一些信息,例如:是否是Debug,applictaionId等等,那我就在想,我直接将我们上面保存的信息,直接写入到这个plugin会自动帮我们生成的类不就可以了吗?于是我在最后又优化了一下我们的代码。
大家来看:
variants.all { BaseVariant variant ->
if (variant instanceof ApplicationVariant) {
//为generateBuildConfig这个Task添加额外Action
def generateBuildConfig = variant.generateBuildConfig
generateBuildConfig.doFirst {
def modules = new StringBuilder()
modulesSet.each {
modules.append(it).append(',')
}
items.add(new ClassFieldImpl("String", "ALL_MODULES", "\"${modules}\""))
}
}
为什么可以这样写呢,其实思路很简单,既然BuildConfig这个Java源文件在生成的时候会写入原始的一些信息,那我们是不是找到生成BuildConfigo类的Task,然后为其添加一项我们自己的数据不就可以了吗,看起来确实是可行的,所以我们现在回过头来分析一下上面我最终实现的代码,我们通过variant.generateBuildConfig就找到了生成BuildConfig这个Java源文件的Task,然后我们知道Task都是可以为其添加Action的,所以我们最终通过generateBuildConfg.doFirst{}这个方法为其添加一个数据项即可。这样我们就完成了最终要实现的gradle部分的相关功能。
整个工程的代码还是比较复杂的,这里我只是复制出了gradle相关的一小部分代码,后面其实还有一个工程是专门为所有的app module去生成路由表,由于后面的功能主要是APT注解处理器的相关功能,与我们的gradle关系不大,所以就不再贴代码了。
好,到这里呢,我的这个需求部分就通过gradle完成动态的解决了,完全无需我们手动去修改,其实gradle的功能是非常强大的,我们写Java代码也好,JS代码也好等等,我们控制的都是源码期,如果我们掌握了gradle就可以动态的控制编译时期。对我们能力的提高是非常的有帮助的。最后附一张图,是android-gradle-plugin中各Task的一个执行顺序。(注意:大家只看绿色的Task即可,其它颜色是其它Porject中的Task),