Gradle模块化代码精炼:让你的gradle代码控制在100行以内
Posted 郭霖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gradle模块化代码精炼:让你的gradle代码控制在100行以内相关的知识,希望对你有一定的参考价值。
近日,一则关于腾讯控告4399游戏的消息在网上传开,原因很简单,原来是4399出了一款名为格斗猎人的游戏,因为游戏风格与腾讯DNF相似,因此吃了这次官司。腾讯公司以侵害腾讯商标享有的合法权益怒告4399,最终法院判决4399侵权,赔款500万。
本篇来自 wustor 的投稿,分享了一个Gradle模块化配置的实现技巧,希望大家会喜欢!
https://www.jianshu.com/u/94dc45995a85
我们知道,android Studio是利用gradle进行构建的,我们经常接触到的gradle脚本是build.gradle。build.gradle有两个,一个在project下,一个是在app目录下。随着项目的迭代,我们会在app目录下的gradle中添加很多依赖,project下的gradle却不会发生很大的变化,所以会导致app下面的gradle文件越来越大,有时候查找对应的方法以及task非常不方便,尤其是在集成了tinker热修复之后,app下面的gradle已经达到了将近1000多行。最近刚好有时间认真研究了一下gradle,确切地说是groovy,然后通过脚本依赖实现了gradle解耦,成功的把app目录下的gradle代码控制在100行以内。
常见配置
通常的做法是在project目录下新建一个config.gradle文件,如下:
ext {
android = [
compileSdkVersion: 25,
buildToolsVersion: "25.0.3
]
supportLibrary = "25.4.0"
tinkerVerison = "1.9.1"
dependencies = [
"multidex" : "com.android.support:multidex:1.0.1",
"okhttp3" : "com.squareup.okhttp3:okhttp:3.9.0"
}
然后在project下的build.gradle文件中引用
apply from: "config.gradle"
再接着在app目录下的build.gradle中获取并使用
apply plugin: 'com.android.application'
apply from: "package.gradle"
def cfg = rootProject.ext.android
def librarys = rootProject.ext.dependencies
android {
compileSdkVersion cfg.compileSdkVersion
buildToolsVersion cfg.buildToolsVersion
dexOptions {
jumboMode = true
}
//此处省略一万行代码 }
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs') compile librarys["multidex"] compile librarys["okhttp3"] //此处省略一万行代码 }
这种方式能够将我们的gradle统一进行管理,虽然并不能减少app的目录下的build.gradle的代码量之前,但是觉得够用了,本身对基于groovy的gradle不是很熟,虽然随着项目迭代,app目录下的build.gradle代码量越来越大,尤其是当项目集成了tinker之后,而后集成了packer-ng-plugin打包,以及加入了一些自定义的Task之后,代码会显得非常臃肿,有时候改一个东西,需要找很久,由于对groovy不是很熟,在网上也看过一些文章,基本上都是在介绍gradle的基本知识以及依赖统一管理,加上在gradle里面写代码没有提示,一度让我以为这可能就是build.gradle的最终版了,直到最近项目刚上线,稍微有点空闲,然后决定彻底简化一下gradle代码。
url优化
buildTypes {
release {
minifyEnabled true
shrinkResources true
buildConfigField "String", "AlphaUrl", "\"releaseUrl1\""
// buildConfigField "String", "AlphaUrl", "\"releaseUrl2\""
}
debug {
minifyEnabled true
buildConfigField "String", "AlphaUrl", "\"debugUrl1\""
// buildConfigField "String", "AlphaUrl", "\"debugUrl2/\""
}
}
在config中添加代码
url = [
"debug" : "debugUrl1",
//"debug" : "debugUrl2", //"debug" : "debugUrl3",
"release": "releaseUrl1",
// "release": "releaseUrl2"
]
重新引用
//获取
def url = rootProject.ext.url
//使用 //debug
buildConfigField "String", "AlphaUrl", "\"${url["debug"]}\""
//release
buildConfigField "String", "AlphaUrl", "\"${url["release"]}\""
依赖优化
先看一看之前的代码
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs') compile librarys["multidex"] compile librarys["supportAppcompat"] //此处省略一万行代码
实际上library就是一个Map,其实这句代码转化成Java就是
compile fileTree(include: ['*.jar'], dir: 'libs')
HashMap<String,String> hashMap=new HashMap<>();
compile hashMap.get("multidex")
compile hashMap.get("supportAppcompat")
其实可能你也知道了,实际上我们完全可以写一个循环来简化这些代码,就跟HashMap的遍历一样
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs') librarys.each { k, v -> compile v }
}
模块化配置
在我们进行常规的gradle配置中,我们并没有在project下的build目录添加很多代码,只是新建了一个config.gradle文件,然后再project目录下添加了一行依赖,所以就能够调用config中的代码,同样的,我们也可以把tinker,packer-ng-plugin,以及自定义task的配置文件用一个gradle文件进行配置,然后在app的目录下进行引用,实际上就是利用了gradle的插件依赖。
tinker配置
首先在app的目录下新建一个tinker.grale配置文件,为什么是tinker目录下,因为tinker的运行需要依赖'com.android.application'这个插件,所以必须放在这个目录下,然后复制粘贴tinker的配置代码,注意不要忘记修改tinker的id,我是在project目录下统一进行配置的tinker,所以只需要调用config中的代码
//此处省略一万行代码
def getTinkerIdValue() {
return rootProject.ext.tinker.id
}
//此处省略一万行代码
然后在app下面的build.gradle中进行引用,注意看一下这行代码的位置,不要放在最开始,因为在依赖tinker.gradle文件的时候,不然你是打不了tinker的补丁的,tinker需要读取application的一些信息,放在buildTypes 之后,当时我也是调试了好久。
apply plugin: 'com.android.application'
def cfg = rootProject.ext.android
def librarys = rootProject.ext.Dependencies
def tinker = rootProject.ext.tinker
def url = rootProject.ext.url
buildTypes {
//此处省略一万行代码 }
apply from: "tinker.gradle"
到此,tinker就可以使用了,下面继续配置walle的gradle
packer配置
新建package.gradle文件
apply plugin: 'packer' packer {
archiveNameFormat = '${buildType}-v${versionName}-${channel}'
archiveOutput = new File(project.rootProject.buildDir, "apks")
channelList = ['xiaomi','meizu']
}
}
在app下的build.gradle中添加依赖
apply plugin: 'com.android.application'
apply from: "package.gradle"
task配置
虽然Android Studio的application自带了很多task,但是并不能满足我们有些需求,比如我需要用Python将测试包上传至fir,就需要自定义task,所以我也打算把这部分给分离出来,新建upload.gradle
ext {
//此处省略一万行代码
startUpload = this.&startUpload
}
//上传至fir
def startUpload() {
//此处省略一万行代码
}
在project中引用
apply from: "upload.gradle"
在app的build.gradle中可以直接调用
task toFir << {
startUpload()
}
toFir <<,其实是gradle的语法,如果不加<<的话,每次编译的时候都会执行这个task,加了<<,只有执行这个task的时候才会执行里面的代码
只有81行,这样一来,调试就很轻松了,哪个脚本除了问题,就直接去调试相应的脚本就好了,不用在自己的gradle里面改来改去。
tinker 测试
运行命令gradlew tinkerpatchDebug或者打开右侧的可视化工具栏点击tinker下的tinkerpatchDebug,运行测试,运行结果:
Result: final signed patch result: G:\Note\ChuangMei\app\build\outputs\tinkerPatch\debug\patch_signed.apk, size=2110 Result: final signed with 7zip patch result: G:\Note\ChuangMei\app\build\outputs\tinkerPatch\debug\patch_signed_7zip.apk, size=2439 Warning: patch_signed_7zip.apk is bigger than patch_signed.apk 329 byte, you should choose patch_signed.apk at these time!
Tinker patch done, total time cost: 8.806000s
Tinker patch done, you can go to file to find the output G:\Note\ChuangMei\app\build\outputs/tinkerPatch/debug
-----------------------Tinker patch end-------------------------
BUILD SUCCESSFUL in 3m 16s
packer测试
运行命令gradlew clean apkDebug运行测试,运行结果:
> Task :app:apkDebug
============================================================
PackerNg - https://github.com/mcxiaoke/packer-ng-plugin ============================================================
Variant: debug
Input: G:\Note\ChuangMei\app\build\outputs\apk\app-debug.apk
Output: G:\Note\ChuangMei\build\apks
Channels: [xiaomi meizu]
Generating: debug-v1.6.3-xiaomi.apk
Generating: debug-v1.6.3-meizu.apk
Outputs: G:\Note\ChuangMei\build\apks
task测试
运行命令 gradlew task toFir,运行结果:
开始上传至fir
http://api.fir.im/apps success_apk:{"is_completed":true}
success_icon:{"is_completed":true}
上传结束 with value 0
几点说明
gradle所放位置
为什么有的是放在project目录下,有的是放在app的目录下,因为gradle引用默认的是当前路径,这样放我引用的时候就不需要去配置所引用的gradle路径,当然如果你原因放置在同一个目录下面也是OK的,只需要在apply的时候加上引用的path即可。
引用位置
为什么有的引用是放在头部,有的引用需要放置在中间,这个取决于引用的插件是否需要读取application的配置信息,如果是tinker,必须放置在中间,因为它生成patch包需要获取很多application信息,如果是packer打包的话,则不需要,这个需要格外留意一下,不然会有很多莫名其妙的错误。
方法调用
在同一个gradle脚本里面,方法调用是很简单的,但是当我们有多个gradle脚本的时候,如何相互调用彼此的方法呢,其实我之前想优化的时候,也是卡在这里,因为属性调用很简单,gradle提供了ext,所以我们可以很容易的获取其他gradle的属性,如果我们现在有两个gradle,一个是first.gradle,一个是second.gradle,我想在second.gradle里面调用first.gradle中的方法,应该怎么做呢?
只需要在first.gradle中进行如下配置
ext{
test= this.&test
}
def test(){
println("我被调用了")
}
然后在second.gradle中进行配置
//直接调用
startUpload()
//通过task调用 task toFir << {
startUpload()
}
package
需要配置签名,在keystore.properties中进行配置,可以进行多渠道打包
tinker
为了保证对gradle进行模块化分离不影响项目的构建,所以我都是用自己的真实项目进行构建的,因为简单的Demo很难模拟真实项目中的构建环境,所以demo中我只提供了gradle的配置文件,没有进行tinker的配置,但是已经对tinker进行了模块化分离。
upload
https://github.com/wustor/GradleModule
如果有什么问题欢迎留言哈,谢谢!
欢迎长按下图 -> 识别图中二维码
以上是关于Gradle模块化代码精炼:让你的gradle代码控制在100行以内的主要内容,如果未能解决你的问题,请参考以下文章
Android Gradle 插件Gradle 自定义 Plugin 插件 ② ( buildSrc 目录中实现 Gradle 插件 | 实现 Gradle 插件代码 | 模块引入插件并编译 )
Android Gradle 插件Gradle 自定义 Plugin 插件 ⑥ ( 在 buildSrc 模块中依赖 Android Gradle 插件 | 完整代码示例 )
Android Gradle 插件Gradle 自定义 Plugin 插件 ⑥ ( 在 buildSrc 模块中依赖 Android Gradle 插件 | 完整代码示例 )