我在做的一个项目需要有换肤功能,换肤的方案是采用第三方库 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);}

而项目同时还在紧张地迭代,没有充分的时间去处理这些代码,只能是每次迭代中逐渐地修改。

原始情况

一开始,皮肤包的生成是在另一个项目中,制作一个皮肤包的步骤如下:

  1. 把资源拷贝到皮肤包项目;
  2. 打包皮肤包项目,生成 apk;
  3. 将 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(':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].outputFileinto releaseDirrename {"${project.name}.skin"}}}}
}

这样就创建了自定义任务 deployReleaseSkin 了。
在上面的代码中,我还给任务声明了 groupdescription,这是为了能在任务树中看到(Android Studio 右边的 Gradle 面板,或是通过 ./gradlew :skins:yourskin:tasks 命令打印出)。

不过上面的任务还是有些不足之处。首先我们的皮肤包是会提交到版本控制系统的,那么当皮肤包的源文件没有修改,并且 app 项目中的皮肤包也没有修改时,执行部署任务时就不需要重复更新。在翻了 Gradle 用户指南之后,我又对上面的自定义任务作了少量修改,让它支持 UP-TO-DATE。修改很简单,只需要指定任务的输入及输出即可,完整代码如下:

android.applicationVariants.all { variant ->if (variant.buildType.name == "release") {def variantName = variant.name.capitalize()def releaseDir = new File(project(':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}"inputs.files(variant.outputs[0].outputFile)outputs.dir releaseDir}deployTask.doLast {copy {from variant.outputs[0].outputFileinto releaseDirrename {"${project.name}.skin"}}}}
}

最终的皮肤包全自动部署

上面的半自动部署,已经把最初所提的问题都解决了。但是在经过一段时间后,我又开始难以接受了。

首先,在 Android 3.1.0 当中,同步 Gradle 文档时会报错,提示 project(':app') 里没有 android 这个属性,尽管命令行里编译完全通过,并且不影响项目运行。只是在 Android Studio 的运行配置上,对皮肤包的项目会打一个红叉。最后我是给它加了个 try-catch,使得在 Android Studio 中同步构建通过。
其次,每次修改了皮肤包之后,都需要手动执行部署任务让其更新,我相当不喜欢这种没有人生意义的重复操作。
再者,皮肤包的修改比较频繁,每次都要把新的皮肤包给提交到版本控制系统中。皮肤包是从项目代码中生成的,并且生成的成本不高,重新生成也不会有其他影响,从版本控制系统的使用原则来看,它理应被版本控制系统忽略。

所以,我需要实现皮肤包的全自动部署,它可以满足以下条件:

  1. 当皮肤有修改时,下次 app 编译,会生成新的皮肤包放到 assets 中。
  2. 当皮肤包没有修改时,不会重复生成。
  3. 皮肤包不需要提交到版本控制系统中。当 assets 中不包含时,它会生成并部署。
  4. 当去掉对某个皮肤包的依赖时,清理再构建,assets 只会包含所依赖的皮肤包。

上面的第 2 个和第 3 个条件,其实也就是 Gradle 任务的 UP-TO-DATE 的实现,在前面我们已经实现过了,所以只需要关注第 1 个和第 4 个问题。

在 Android Gradle 项目中,assets 是可以配置为多个目录的。所以我的想法是新增一个 assets 目录,专门用于放皮肤包。然后注入 app 的构建任务,在生成 assets 资源之前部署皮肤包到该目录。

首先我们新定义两个常量,分别表示另一个 assets 文件夹及其内部的 skins 文件夹的路径。如下:

ext {// external assetsEXTERNAL_ASSETS='externalAssets'// skin assets to deploy the skin files that generated by skin moduleSKIN_ASSETS=EXTERNAL_ASSETS+'/skin'
}

然后修改皮肤包的部署任务,改为将皮肤包复制到新的 assets 目录。代码如下:

android.applicationVariants.all { variant ->if (variant.buildType.name == "release") {def variantName = 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[0].outputFile)outputs.dir releaseDir}deployTask.doLast {copy {from variant.outputs[0].outputFileinto releaseDirrename {"${project.name}.skin"}}}}
}

接着,修改 app 的 build.gradle 脚本,在 android{} 节点中对 sourceSets 进行配置,新增一个 assets 目录:

    sourceSets {main {assets.srcDirs = ['src/main/assets', EXTERNAL_ASSETS]}}

然后在命令行执行一下我们的 app 的构建任务,找到生成 assets 资源的任务名称。这里执行的时候我们不需要真正的执行,所以要带上参数 -m,表示以 dry run 模式执行。

./gradlew :app:assemble -m

输出结果这里省略。
我们会看到,对于 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()
}

对了,还得把原来 assets 中的皮肤包删掉。
到这里就大功告成了,完美解决上面提出的全部问题。

接下来就可以放心地修改皮肤包资源,大胆地进行代码整理优化重构而不用担心忘了部署皮肤包资源了。

皮肤包项目的 Gradle 脚本演化相关推荐

  1. 代理设置。 安卓工作室配置用http代理。gradle可能需要这些http代理设置去访问互联网。例如下载依赖。 你想要复制ide的代理配置到这个项目的gradle属性文件吗?...

    代理设置. 安卓工作室配置用http代理.gradle可能需要这些http代理设置去访问互联网.例如下载依赖. 你想要复制ide的代理配置到这个项目的gradle属性文件吗? 查看更多细节,请参阅开发 ...

  2. 如何查看Android项目的gradle版本和路径

    1.查看项目的gradle版本:打开gradle-wrapper文件可以看到这一行,其实就是所需gradle的下载网址,如果本地没有找到Android会自动到这个网址进行下载.以下面为示例,版本就是5 ...

  3. 关于Android Studio项目的Gradle构建 泡在网上的日子 / 文 发表于2016-02-16 12:16 第2500次阅读 Gradle 3 编辑推荐:稀土掘金,这是一个针对技术开发者的

    http://www.jcodecraeer.com/a/anzhuokaifa/Android_Studio/2016/0216/3969.html 编辑推荐:稀土掘金,这是一个针对技术开发者的一个 ...

  4. 编译C51项目的bat脚本

    使用bat编译C51项目 rem 使用bat编译C51项目@echo off&setlocal enabledelayedexpansionset BIN_PATH=E:\Tools\Keil ...

  5. Android插件化开发之动态加载本地皮肤包进行换肤

    Android插件化开发之动态加载本地皮肤包进行换肤 前言: 本文主要讲解如何用开源换肤框架 android-skin-loader-lib来实现加载本地皮肤包文件进行换肤,具体可自行参考框架原理进行 ...

  6. 手动将web项目的class文件打成jar包,手动打jar包,java -cvf,IDE打包底层指令

    手动将web项目的class文件打成jar包. 我们的项目在使用IDE进行编译后,在项目的target目录下将会生成class文件.我们可以将class文件打成jar包. 使用的到命令为: 在targ ...

  7. 【Flutter】插件包选择 ( 查看文档是否全面 | 查看插件包的更新版本次数 | 查看使用示例 | 查看 GitHub 项目的 Star Fork Issues )

    文章目录 一.插件包选择 二.查看文档是否全面 三.查看插件的更新版本次数 四.查看使用示例 五.查看 GitHub 项目的 Star Fork Issues 一.插件包选择 开发 Flutter 时 ...

  8. Maven(三):将web项目的war包热部署到远程Tomcat服务器

    相关阅读: Maven(一):安装与环境配置.使用Maven搭建web项目 Maven(二):常用命令.依赖管理 Maven(三):将web项目的war包热部署到远程Tomcat服务器 Maven(四 ...

  9. Gradle项目的jar发布到私有仓库

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 常见场景 作为java库的开发者,如何让其他项目用上自 ...

最新文章

  1. arm服务器芯片尺寸,华为第四代ARM服务器自研芯片Hi1620规格曝光 全球首款7nm工艺的数据中心用ARM处理器...
  2. AS4下搭建cacti
  3. DPDK — 数据平面开发技术
  4. Android 混淆文件project.properties和proguard-project.txt
  5. (LeetCode 92)Reverse Linked List II
  6. VB6.0 怎样启用控件comdlg32.ocx
  7. 文件系统服务器管理论文,Linux管理磁盘和文件系统
  8. 如何迁移#SNMP到.NET Core平台的一些体会
  9. Python3 爬虫爬取中国图书网(淘书团) 记录
  10. MaxCompute技术人背后的故事:从ApacheORC到AliORC
  11. HTML图形映射技术
  12. 慎用javascript:void(0) 【转】
  13. python jupyter notebook 上传文件_使用jupyter notebook将文件保存为Markdown,HTML等文件格式...
  14. Unity3D游戏开发之换装系统的实现
  15. 《大学章句》光剑续编
  16. Mirillis Action! v4.17.0 高清游戏视频录制软件
  17. macbook air未能与服务器,少量2018款Macbook Air存在问题:苹果已通知维修但并未告知详细情况...
  18. 144hz和60hz测试软件,专业FPS玩家讲解:60Hz与144Hz刷新率的问题
  19. IDEA创建SSM(Spring+SpringMVC+Mybatis)项目-Jar包版
  20. 读书笔记:《C++ PrimerPlus》 第九章~第十一章

热门文章

  1. FLASH 3D滑动相册 源码
  2. Scrapy ImagesPipline的重写和使用
  3. 第三章【ADFS集成Exchang实现OWA\ECP单点登录SSO】配置AD证书服务(配置ADCS)
  4. centos php5-curl,Centos5 下 安装php的 curl 扩展
  5. 2022养老院人员定位管理系统软件解决方案
  6. MCCMNC是6位时锁卡失败 - MTK物联网在线解答 - 技术论坛
  7. 关于电脑插耳机没有声音的解决办法
  8. C#HttpUtility.UrlEncode大写问题
  9. element布局容器大小_element中el-container容器与div布局区分详解
  10. 加载JavaScript脚本方式