Gradle插件学习笔记(三)

3,376 阅读7分钟

今天要聊的就跟android有关了,但是在介绍的时候会引用到前两篇的知识,所以没有看过前两篇文章的朋友,建议先看一下前面提到的概念: Gradle插件学习笔记(一) Gradle插件学习笔记(二)


android编译任务

要讲到android的编译,肯定要先看看android在执行assemble都执行了什么,这个我们可以打印一下任务(如何打印任务?这个我们后面再说,也是个groovy插件):

app工程的preBuild任务开始执行
app工程的preDebugBuild任务开始执行
app工程的checkDebugManifest任务开始执行
app工程的preReleaseBuild任务开始执行
app工程的prepareComAndroidSupportAnimatedVectorDrawable2600Alpha1Library任务开始执行
app工程的prepareComAndroidSupportAppcompatV72600Alpha1Library任务开始执行
app工程的prepareComAndroidSupportConstraintConstraintLayout102Library任务开始执行
app工程的prepareComAndroidSupportSupportCompat2600Alpha1Library任务开始执行
app工程的prepareComAndroidSupportSupportCoreUi2600Alpha1Library任务开始执行
app工程的prepareComAndroidSupportSupportCoreUtils2600Alpha1Library任务开始执行
app工程的prepareComAndroidSupportSupportFragment2600Alpha1Library任务开始执行
app工程的prepareComAndroidSupportSupportMediaCompat2600Alpha1Library任务开始执行
app工程的prepareComAndroidSupportSupportV42600Alpha1Library任务开始执行
app工程的prepareComAndroidSupportSupportVectorDrawable2600Alpha1Library任务开始执行
app工程的prepareDebugDependencies任务开始执行
app工程的compileDebugAidl任务开始执行
app工程的compileDebugRenderscript任务开始执行
app工程的generateDebugBuildConfig任务开始执行
app工程的generateDebugResValues任务开始执行
app工程的generateDebugResources任务开始执行
app工程的mergeDebugResources任务开始执行
app工程的processDebugManifest任务开始执行
app工程的processDebugResources任务开始执行
app工程的generateDebugSources任务开始执行
app工程的incrementalDebugJavaCompilationSafeguard任务开始执行
app工程的javaPreCompileDebug任务开始执行
app工程的compileDebugJavaWithJavac任务开始执行
app工程的compileDebugNdk任务开始执行
app工程的compileDebugSources任务开始执行
app工程的mergeDebugShaders任务开始执行
app工程的compileDebugShaders任务开始执行
app工程的generateDebugAssets任务开始执行
app工程的mergeDebugAssets任务开始执行
app工程的transformClassesWithDexForDebug任务开始执行
app工程的mergeDebugJniLibFolders任务开始执行
app工程的transformNativeLibsWithMergeJniLibsForDebug任务开始执行
app工程的transformNativeLibsWithStripDebugSymbolForDebug任务开始执行
app工程的processDebugJavaRes任务开始执行
app工程的transformResourcesWithMergeJavaResForDebug任务开始执行
app工程的validateSigningDebug任务开始执行
app工程的packageDebug任务开始执行
app工程的assembleDebug任务开始执行

这是执行assembleDebug打印的所有任务,当然你要执行assembleRelease任务肯定是一致的。 那接下来介绍一下常用的几个任务:

  • mergeDebugResources 任务的作用是将所有依赖的aar或library module中的资源合并到app/build/intermediates/res/merged/debug目录里

  • processDebugManifest任务是将所有依赖的aar或library module中AndroidManifest.xml中的节点,合并到项目的AndroidManifest.xml中

  • processDebugResources的作用是调用aapt生成项目和所有aar依赖的R.java,同时生成资源索引文件,把符号表输出到app/build/intermediates/symbols/debug/R.txt

  • compileDebugJavaWithJavac这个任务是用来把java文件编译成class文件,输出的路径是app/build/intermediates/classes/debug 编译的输入目录有

  • transformClassesWithJarMergingForDebug的作用是把compileDebugJavaWithJavac任务的输出app/build/intermediates/classes/debug,和app/build/intermediates/exploded-aar中所有的classes.jar和libs里的jar包作为输入,合并起来输出到app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar,我们在开发中依赖第三方库的时候有时候报duplicate entry:xxx 的错误,就是因为在合并的过程中在不同jar包里发现了相同路径的类

  • transformClassesWithDexForDebug这个任务的作用是把包含所有class文件的jar包转换为dex,class文件越多转换的越慢

使用android提供的插件

如果想在自己的插件中产生干预android编译的行为,肯定要依赖android的gradle插件。这里要说明两种情况:

  • 如果使用buildSrc(不明白是什么的,请查看前两篇文章)的方式,不需要做额外的依赖。
  • 如果是新建java Library的形式需要添加依赖:
dependencies {
    compile gradleApi()
    compile localGroovy()
    compile 'com.android.tools.build:gradle:2.3.3'
}

看看源码

上一篇文章说到了可以通过android的额外属性名访问Project的android属性,从而访问android的编译。 我们今天会通过源码看一下,android到底是如何实现的。 首先是AppPlugin.java文件(即插件的主文件),里面有这么一行代码:

project.getExtensions()
                .create(
                        "android",
                        AppExtension.class,
                        project,
                        instantiator,
                        androidBuilder,
                        sdkHandler,
                        buildTypeContainer,
                        productFlavorContainer,
                        signingConfigContainer,
                        extraModelInfo);

可以看出AppExtension就是android拓展属性的文件,这里要说明的是如果是编译Library,使用的就是LibraryPlugin插件,对应的拓展文件为LibraryExtension。好了我们还是继续说两个文件对比一下不难发现,两个文件都是差不多的,都是继承TestedExtension,只是属性变量不太一样。今天我们还是只说AppExtension。 在这个文件中有一个这样的数组ApplicationVariant:

接着再看看ApplicationVariant是什么,ApplicationVariant只是一个接口,同时有继承了别的类,然后别的类有分别继承了别的类,这里的继承关系有些复杂,我画一个图可能比较好说明。

图有些大了,但是应该可以看明白BaseVariant中的方法比较多,我省略了一下,省略的部分主要是获取上一节打印的Task的方法。暂时应该不会用到。 这里有个重要的方法BaseVariantOutput,我们常用的替换占位符(多渠道打包等)都会用到,这里做个简单的介绍:

以上介绍的方法和类都很重要,想更深了解的朋友不妨仿照下面的demo,多打印些log看看输出的内容都是什么,

写个小demo

如果上面的图认真的看一下,就可以了解variant中包含了android编译的主要功能参数,所以要下手,只能从这里下手了。 在这里我们先举个简单的小例子(在之前的demo中做个修改,不熟悉的朋友请参考前两章):

 void apply(Project project) {
        project.extensions.create("deep", MyExtension)
        project.afterEvaluate {
            MyExtension extension = project['deep'];
            String a = extension.aaa
            String b = extension.bbb
            println("deep:${a},${b}")
            project.tasks.getByName("preDebugBuild") {
                it.doFirst {
                    project.android.applicationVariants.all { variant ->
                        variant.outputs.each { output ->
                            output.processManifest.doLast {
                                def manifestFile = "${project.getProjectDir().absolutePath}/build/intermediates/manifests/full/${variant.dirName}/AndroidManifest.xml"
                                def updatedContent = new File(manifestFile).getText('UTF-8').replaceAll("vvvvv", "ccccc")
                                new File(manifestFile).write(updatedContent, 'UTF-8')
                            }
                        }
                    }
                }
            }
        }
    }

这段代码的作用很明显是修改AndroidManifest中的字符串vvvvv,改成ccccc。说白了作用就是替换字符串,跟替换占位符是一个作用。看看上面有个重要的写法:

output ->
                            output.processManifest.doLast {}

这是什么意思呢,根据上面我们介绍的源码,可以知道这个就是在processManifest Task之后再去执行,再由上面介绍到的内容processManifest是合并AndroidManifest的任务,在合并之后,马上去修改AndroidManifest文件,保证了占位符的替换。 再修改一下这个文件,咱们看看output的file是什么:

project.tasks.getByName("preDebugBuild") {
                it.doFirst {
                    project.android.applicationVariants.all { variant ->
                        variant.outputs.each { output ->
                            println("out put="+output.getOutputFile())
                        }
                    }
                }
            }

看看输出:

有时候我们可能需要修改一下apk的名字,根据上面的源码,我们找到了一个方法就是setOutputFile,来我们在打打log,看一下:

  project.tasks.getByName("preDebugBuild") {
                it.doFirst {
                    project.android.applicationVariants.all { variant ->
                        variant.outputs.each { output ->
                                println("getDirName:"+output.getDirName())
                                output.setOutputFile(new File("${project.getProjectDir().absolutePath}/build/${output.getName()}_aaa.apk"))
                                println("out put=" + output.getOutputFile())
                        }
                    }
                }
            }

看看结果:

还有生成的apk:

总结

这次介绍的内容比较多,要想多掌握,可能需要多调试几次,多打打log才能全部掌握,喜欢的用户可以根据我上面提到的方法,多试几次。 接下来的文章可能会侧重介绍groovy的语法,因为有用户微信公众号给我留言说写的代码,看着有些别扭。还有可能会介绍一些其他gradle的插件,比如上面提到的打印任务日志等等。 喜欢的朋友可以关注我的公众号,第一时间看到文章: