*本篇文章已授权微信公众号 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'
}

OK,大功告成!我们来看看最后的成果把! 
这里写图片描述

三、结语

本插件最重要的是理解为何我们可以在task前做操作以及在哪个task前做操作。 
如果是看完我之前三篇文章的童鞋相信很容易理解此文,其实无非就是对gradle以及groovy语法的运用而已,这个就需要时间的积累了。 
好了,此系列暂时到此就结束了,需要看Demo的童鞋请点击下面的传送们: 
github地址 
如果对您有帮助的话,希望给个star鼓励一下~谢谢

最最最后:我也把该项目上传到了jcenter,可以直接使用哦~具体参见github说明

Categories: gradle

发表评论

电子邮件地址不会被公开。