支持资源文件替换的多渠道打包插件(四)

*本篇文章已受权微信公众号 guolin_blog (郭霖)独家发布
系列文章:
从Android Plugin源码开始完全理解gradle构建:初识AndroidDSL(一)java

从Android Plugin源码开始完全理解gradle构建:Extension(二)android

从Android Plugin源码开始完全理解gradle构建:Task(三)git

1、写在前面

通过前面几篇文章的学习(什么?你还没看,赶忙去补补!),对gradle已经有了大体的了解了,当学习完后必定须要码代码来巩固一下,所谓:talk is cheap show me the code!今天就带你们来一场gradle实战,作一个有实用价值的自定义插件。github

不知道你们有没有遇到过这样的需求:公司有一款产品,而客户须要将公司产品作制定化操做,如:修改app包名、appIcon、appName、以及引导页等一些资源文件,以便客户展现他本身的广告。这样可能会有不少定制化产品须要去打包,之前采用一个一个手动更改并打包,一打就是一下午,随着定制化愈来愈多,每次更新都要这样打,估计都快疯了吧。
这个时候你们首先会想到利用Android系统的多渠道打包方法productFlavors,好比这样:web

android  {
    productFlavors {
        xiaomi{
        applicationId  "com.xiaomi.cn"
        }
        google{
        applicationId  "com.google.cn"
        }
        huawei{
        applicationId  "com.huawei.cn"
        }
    }
}

这样就能够修改包名,appName等一些需求,可是资源文件可能就不太方便了,可能有童鞋会说,在res下面多放几张图片,而后利用manifestPlaceholders来修改,没错,这样的方式也能够实现,不过万一你公司的渠道定制包很不少呢?100个、500个,难道须要放那么多张没用的图片进去?那app得多大。
其实主要就是这两个问题:
一、打包时资源文件没法自动更换
二、若手动更换,一个一个打成百上千的包那时间成本可不是盖的
那么今天,咱们就来写一个自动替换资源文件的gradle插件,完全解决这个问题。api

2、自定义插件

首先咱们须要写个自定义插件,具体步骤我在第一篇系列文章里提到过,也有推荐文章,这里我就放出插件结构就好(文末有DEMO,你们能够有须要的话能够查看)
自定义插件结构
首先咱们须要写一个Plugin,并重写他的apply方法:bash

public class ResourceFlavorsPlugin implements Plugin<Project> { 
 
  

    @Override
    void apply(Project project) {
        //这里写自定义内容

    }
}

首先咱们理一下咱们思路:
一、咱们须要打不少渠道包
二、每一个渠道包都须要修改包名、资源文件等
第一点咱们利用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便可app

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里取出就好。ide

下一步就须要修改资源文件了,而gradle如何实现这一操做呢?我也不知道,这时候只能求助官方了,经过一些时间的查阅,我终于在官方文档中找到了这个方法,其实很简单:
copy
这是在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,大功告成!咱们来看看最后的成果把!
这里写图片描述

3、结语

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

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