皮肤包项目的 Gradle 脚本演化
我在做的一个项目需要有换肤功能,换肤的方案是采用第三方库 ThemeSkinning 的实现(在其基础上修复若干 bug)。皮肤的制作是把相关的资源放在一个 app module 中打包成 apk,当然资源的命名要和原有项目中的命名一致。目前的皮肤加载方式,是把皮肤包放到 assets 中去加载。这是背景一。
背景二,这个项目是我所接手过来的。虽然表面上是使用了皮肤加载库,但是项目中还有大量遗留的没有使用皮肤库的写死的代码,都是判断当前主题是哪一个,然后返回默认资源或者是指定皮肤的资源。这些代码都类似如下:
String theme = ConfigUtil.getTheme();
if (theme.equals(Constant.SKIN_ORANGE)) {
return ResourcesCompat.getColor(getActivity().getResources(), R.color.color_orange_theme, null);
} else {
return ResourcesCompat.getColor(getActivity().getResources(), R.color.default_theme, null);
}
而项目同时还在紧张地迭代,没有充分的时间去处理这些代码,只能是每次迭代中逐渐地修改。
原始情况
一开始,皮肤包的生成是在另一个项目中,制作一个皮肤包的步骤如下:
- 把资源拷贝到皮肤包项目;
- 打包皮肤包项目,生成 apk;
- 将 apk 文件拷贝到项目中的 assets 内的指定文件,并修改名字。
看似并不繁琐。但其实这个过程对我来说是很痛苦的,因为实际上,我遇到了以下问题:
- 更新资源时,需要在两个 Android Studio 中切换。
- 接手的项目质量比较差,资源命名很不规范,且不少资源单词拼错,每次修改时都需要切换项目同时修改,并且更新皮肤包。
- 每次要更新皮肤时,都需要打包,然后等打包完成,再拷贝。
- 由于是两个不同的项目,两边的修改历史是分开的,资源需要修改名称时是分别提交的,而不是作为一次原子性的提交,因此在修改历史上难以形成有较的追溯。
项目整合
基于上述所说的情况,我做了第一次整改。我将皮肤项目作为一个子模块,加到项目中。项目的布局如下:
root project
⊢ app module
⊢ library module
∟ skins (folder)
∟ skin module
也就是在根项目下建立一个 skins 的文件夹,将皮肤包项目作为一个 app module 类似放于其中。以后如果有新增皮肤包的需求,可以在这一文件夹下继续建立皮肤包模块。
整合之后就解决其中的一些问题了。不用再打开两个 Android Studio,不用改一个资源提交两个项目,提交两次(一个项目一次)。项目资源和皮肤包资源可以一次修改并一起提交,提交历史能够形成良好的追溯。
皮肤包的半自动部署
由于背景二所提到的问题,没有足够的时间对皮肤相关代码进行重构及制作一个完整的皮肤包,所以每次只有逐渐处理遗留代码,并重新制作皮肤包。上面的项目整合并没有优化皮肤包的制作过程。这种重复的过程,理应交由脚本去做,于是我写了一个部署皮肤的任务。
这个任务实现起来也简单。因为皮肤包是在 app 模块的 assets目录下的一个固定目录中,所以其实只是把编译好的皮肤包的 apk 给复制过去并重命名一下就好了。这是整体的思路。
再详细拆解一下:
- app 项目可以通过
project(':app')
获得。 - 拿到 app 的 project 对象之后,通过
android.sourceSets.main.assets.srcDirs[0]
可以拿到项目中的 assets 目录。 - 部署皮肤的任务应该依赖于皮肤包的
assembleRelease
任务,这样我们就不用每次部署之前先去执行皮肤包的assembleRelease
任务。
代码如下:
android.applicationVariants.all { variant -> if (variant.buildType.name == "release") { def variantName = variant.name.capitalize() def releaseDir = new File(project('www.thd178.com:app').android.sourceSets.main.assets.srcDirs[0], "skin") Task deployTask = task "deploy${variantName}Skin"(dependsOn: "assemble${variantName}") { group = 'install' description "Copy the output apk to target folder and rename it for ${variant.name}" } deployTask.doLast { copy { from variant.outputs[0].outputFile into releaseDir rename { "${project.name}.skin"
自定义任务deployReleaseSkin
了。
在上面的代码中,我还给任务声明了 group
及 description
,这是为了能在任务树中看到(Android Studio 右边的 Gradle 面板,或是通过 ./gradlew :skins:yourskin:tasks
命令打印出)。
不过上面的任务还是有些不足之处。首先我们的皮肤包是会提交到版本控制系统的,那么当皮肤包的源文件没有修改,并且 app 项目中的皮肤包也没有修改时,执行部署任务时就不需要重复更新。在翻了 Gradle 用户指南之后,我又对上面的自定义任务作了少量修改,让它支持 UP-TO-DATE
。修改很简单,只需要指定任务的输入及输出即可,完整代码如下:
android.applicationVariants.all { variant ->
if (variant.buildType.name == "www.taohuayuan178.com/ release") {
def variantName = variant.name.capitalize()
def releaseDir = new File(project(':app').android.sourceSets.main.assets.srcDirs[0], "skin")
Task deployTask = task "deploy${variantName}www.365soke.cn Skin"(dependsOn: "assemble${variantName}") {
group = 'install'
description www.mcyllpt.com "Copy the output apk to target folder and rename it for ${variant.name}"
inputs.files(variant.outputs[0].outputFile)
outputs.dir releaseDir
}
deployTask.doLast {
copy {
from variant.outputs[0].outputFile
into releaseDir
rename {
"${project.name}.skin"
最终的皮肤包全自动部署
上面的半自动部署,已经把最初所提的问题都解决了。但是在经过一段时间后,我又开始难以接受了。
首先,在 Android 3.1.0 当中,同步 Gradle 文档时会报错,提示 project(':app')
里没有 android
这个属性,尽管命令行里编译完全通过,并且不影响项目运行。只是在 Android Studio 的运行配置上,对皮肤包的项目会打一个红叉。最后我是给它加了个 try-catch
,使得在 Android Studio 中同步构建通过。
其次,每次修改了皮肤包之后,都需要手动执行部署任务让其更新,我相当不喜欢这种没有人生意义的重复操作。
再者,皮肤包的修改比较频繁,每次都要把新的皮肤包给提交到版本控制系统中。皮肤包是从项目代码中生成的,并且生成的成本不高,重新生成也不会有其他影响,从版本控制系统的使用原则来看,它理应被版本控制系统忽略。
所以,我需要实现皮肤包的全自动部署,它可以满足以下条件:
- 当皮肤有修改时,下次 app 编译,会生成新的皮肤包放到 assets 中。
- 当皮肤包没有修改时,不会重复生成。
- 皮肤包不需要提交到版本控制系统中。当 assets 中不包含时,它会生成并部署。
- 当去掉对某个皮肤包的依赖时,清理再构建,assets 只会包含所依赖的皮肤包。
上面的第 2 个和第 3 个条件,其实也就是 Gradle 任务的 UP-TO-DATE
的实现,在前面我们已经实现过了,所以只需要关注第 1 个和第 4 个问题。
在 Android Gradle 项目中,assets 是可以配置为多个目录的。所以我的想法是新增一个 assets 目录,专门用于放皮肤包。然后注入 app 的构建任务,在生成 assets 资源之前部署皮肤包到该目录。
首先我们新定义两个常量,分别表示另一个 assets 文件夹及其内部的 skins 文件夹的路径。如下:
ext {
// external assets
EXTERNAL_ASSETS='externalAssets'
// skin www.chuangyed.com assets to deploy the skin www.chuangyed.com files that generated by skin module
SKIN_ASSETS=EXTERNAL_ASSETS+'www.huayi1.cn /skin'
然后修改皮肤包的部署任务,改为将皮肤包复制到新的 assets 目录。代码如下:
android.applicationVariants.all { variant ->
if (variant.buildType.name == "release") {
def variantName = www.yibaoyule1.com variant.name.capitalize()
def releaseDir = project(':app').file(SKIN_ASSETS)
Task deployTask = task "deploy${variantName}Skin"(dependsOn: "assemble${variantName}") {
group = 'install'
description "Copy the output apk to target folder and rename it for ${variant.name}"
inputs.files(variant.outputs[0www.senta77.com ].outputFile)
outputs.dir releaseDir
}
deployTask.doLast www.senta77.com {
copy {
from variant.outputs[0].outputFile
into releaseDir
rename {
"${project.name}.skin"
接着,修改 app 的 build.gradle
脚本,在 android{}
节点中对 sourceSets
进行配置,新增一个 assets 目录:
sourceSets {
main {
assets.srcDirs = ['www.yibaoyule1.com src/main/assets'www.huarenyl.cn , EXTERNAL_ASSETS]
然后在命令行执行一下我们的 app 的构建任务,找到生成 assets 资源的任务名称。这里执行的时候我们不需要真正的执行,所以要带上参数 -m
,表示以 dry run
模式执行。
./gradlew :app:assemble -m
- 1
输出结果这里省略。
我们会看到,对于 debug 的构建类型,会包含一个 generateDebugAssets
任务,对于 release 的构建类型,会包含一个 generateReleaseAssets
的任务。如果是配置了 product flavor 的,任务名会是 generate + flavor 名称 + 构建类型 + Assets
。这个 generate.*Assets
就是我们要找的生成 assets 的任务了。所以接下来就是给它对加上部署皮肤包任务的依赖。代码如下:
def deployTasks = project.rootProject.getTasksByName('deployReleaseSkin', true)
project.tasks.whenTaskAdded { task ->
if (task.name.matches('generate.*Assets')) {
deployTasks.each {
task.dependsOn it
最后,我们给 clean
任务新增一个删除我们额外定义的 assets 文件夹的操作。
// 清理时自动删除生成外部资源的文件夹
tasks.findByName('clean').doLast {
project.file(EXTERNAL_ASSETS).deleteDir()
}
- 1
- 2
- 3
- 4
对了,还得把原来 assets 中的皮肤包删掉。
到这里就大功告成了,完美解决上面提出的全部问题。
接下来就可以放心地修改皮肤包资源,大胆地进行代码整理优化重构而不用担心忘了部署皮肤包资源了。
皮肤包项目的 Gradle 脚本演化的更多相关文章
- 代理设置。 安卓工作室配置用http代理。gradle可能需要这些http代理设置去访问互联网。例如下载依赖。 你想要复制ide的代理配置到这个项目的gradle属性文件吗?
代理设置. 安卓工作室配置用http代理.gradle可能需要这些http代理设置去访问互联网.例如下载依赖. 你想要复制ide的代理配置到这个项目的gradle属性文件吗? 查看更多细节,请参阅开发 ...
- 快速部署tomcat项目的Shell脚本
为了做集群测试,在每台机器上装了3个tomcat,每次发布项目的时候都要反复敲一些命令,重启tomcat之前先检查tomcat进程有没有停掉,没有还要手动kill该进程. 发布次数多了,操作就比较繁琐 ...
- [转] 快速部署Tomcat项目的Shell脚本
为了做集群测试,在每台机器上装了3个tomcat,每次发布项目的时候都要反复敲一些命令,重启tomcat之前先检查tomcat进程有没有停掉,没有还要手动kill该进程. 发布次数多了,操作就比较繁琐 ...
- 在idea中编写自动拉取、编译、启动springboot项目的shell脚本
idea 开发环境搭建 idea中安装shell开发插件 服务器具备的条件 已经安装 lsof(用于检查端口占用) 已安装 git 安装 maven 有 java 环境 背景 代码提交到仓库后,需要在 ...
- 做一个创建cocos2d-x新项目的shell脚本
1. 进入console目录 cd /Users/apple/Documents/MyArchitecture/Cocos2d-x/Framework/cocos2d-x-3.4/tools/coco ...
- 为什么项目的jar包会和tomcat的jar包冲突?
为什么项目的jar包会和tomcat的jar包冲突? 碰到这个问题,猜测tomcat启动时会将自己的lib和项目的lib在逻辑上归并为一个大的lib,但是并没有做版本区分以及去重,这样相同的包可能就有 ...
- Java文档注释导出帮助文档和项目的jar包导入和导出。
1.1 文档注释导出帮助文档 在eclipse使用时,可以配合文档注释,导出对类的说明文档,从而供其他人阅读学习与使用. 通过使用文档注释,将类或者方法进行注释用@简单标注基本信息.如@au ...
- java全栈day13----Eclipse项目的jar包导出与使用jar包
01eclipse快捷键 Ctrl+T:查看所选中类的继承树 例如,在下面代码中,选中Teacher类名,然后按Ctrl+T,就会显示出Teacher类的继承关系 * B: 查看所选中方法的源代码 ...
- 获取apk项目的MD5值和SHA1值
一些可说可不说的话: * 以前有一个更简单的方法,在as的右边工具栏的 gradle 面板中可以很方便的获取到: * 上次用也是在2年前,时间长了给忘记了,不过我记得我当时写了笔记,这会笔记不在身边, ...
随机推荐
- python3 练习题100例 (十一)
题目十一:举例证明角谷猜想:以一个正整数N为例,如果N为偶数,就将它变为N/2,如果除后变为奇数,则将它乘3加1(即3N+1).不断重复这样的运算,经过有限步后,一定可以得到1. #!/usr/bin ...
- mysql5.6主主复制及keepalived 高可用
1.实验目的 mysql服务器作为生产环境中使用最广泛的数据库软件,以其开源性,稳定性而广泛使用,但同时由于数据存储,读写频率高,极易造成数据库出错,从而给企业造成不可挽回的损失,我们除了做好数据库的 ...
- FIFO的使用场景
(1) 数据的缓冲.如模型图所示,如果数据的写入速率高,但间隔大,且会有突发;读出速率小,但相对均匀.则通过设置相应深度的FIFO,可以起到数据暂存的功能,且能够使后续处理流程平滑,避免前级突发时,后 ...
- CentOS搭建Sqoop环境
Sqoop是一个用来将Hadoop(Hive.HBase)和关系型数据库中的数据相互转移的工具,可以将一个关系型数据库(例如:MySQL ,Oracle ,Postgres等)中的 ...
- LI 标签中让文章标题左对齐,日期右对齐的方法
希望实现标题在左对齐,日期在右对齐,当直接给日期的span加上float:right时,IE8和FF都OK,但IE6/7则会换行,下面给出一个简单有效的解决办法. <!DOCTYPE html ...
- P2419 [USACO08JAN]牛大赛Cow Contest
P2419 [USACO08JAN]牛大赛Cow Contest 题目背景 [Usaco2008 Jan] 题目描述 N (1 ≤ N ≤ 100) cows, conveniently number ...
- C#属性默认值设置
关于在MVC中view中设置默认值,可以象如下设置: 1.关于VIEWMODEL的部分 如果是C# 6.0,网上资料查到说可以 如果语法不支持,只能改回.net 2.0的写法. public cla ...
- javac、jar使用实录
因项目管理部署需要,记录一下过程,以免下次忘记了,再次使用又需要重头再来,只记录正确的操作方式,可能会提到某些错误 建立项目所在目录F:\www 案例一 其下建立项目的java源文件的包目录结构.ja ...
- 虚拟现实-VR-UE4-LEAP-Motion手势识别
点击打开链接今天到手一个新东西,LEAP手势识别仪. 关于LEAP Leap是一家面向PC以及Mac的体感控制器制造公司. 具体信息百度百科http://baike.baidu.com/link?ur ...
- 小程序js脚本模块化调用
可以将一些公共的代码抽离成为一个单独的 js 文件,作为一个模块.模块只有通过 module.exports 或者 exports 才能对外暴露接口. 1. common.js // common.j ...