0. 具体源码与插件请参考:
https://github.com/zhaoyubetter/AndroidResourceTools;
1. Android 分模块开发资源重名问题
因为Android中资源名称不能重复,一般在另一个module开始时,我们一般会在此module对应的
gradle文件中,添加一个资源前缀,但这个并不能帮我们重命名资源自动添加前缀,他实现的只是一个
新建资源时,提示你名称必须添加指定的前缀;
如下:
icon_app_drawable.png
添加前缀后:
moduleName_icon_app_drawable.png
通过模块名,并以此模块名为前缀,为该模块内的所有资源统一添加前缀,并替换所有该资源出现的位置,来实现资源名称唯一;
但我们在module开发中,新增资源时,经常忘记添加资源名的前缀,直到集成到主app时,才会意识到
冲突问题,这个时候,手动去改,麻烦且容易出错;
1.1 如何解决此问题
https://developer.android.com/guide/topics/resources/available-resources
通过分析Android工程,分析得出Android App 工程的资源分为2大类:
-
文件夹形式资源
,即每个资源在改特定的文件夹
下,该类型文件夹大约如下:- layout
- drawable
- anim
- color
- menu
- raw
- xml
- mipmap
-
values 类型资源
,即在values
文件夹下,除了id可重用
,其他目前都不允许重用,这些大约有如下:- string
- arrays
- color
- dimens
- bool
- integer
- id
- attr
- style
1.2 发现资源规律
以上资源内容,会出现在2个地方,一是java源代码中,二是资源间的引用,整理表格如下:
资源类型 | 源代码中 | xml引用中 | 备注 |
---|---|---|---|
layout | R.layout.XXX | @layout/XXX | 布局文件 |
drawable | R.drawable.XXX | @drawable/XXX | |
anim | R.anim.XXX | @anim/XXX | |
color | R.color.XXX | @color/XXX | |
mipmap | R.mipmap.XXX | @mipmap.XXX | |
menu | R.menu.XXX | Xml 暂无引用 | |
raw | R.raw.XXX | Xml 暂无引用 | |
xml | R.xml.XXX | Xml 暂无引用 | |
string | R.string.XXX | @string/XXX | |
color | R.color.XXX | @color/XXX | |
dimens | R.dimen.XXX | @dimen/XXX | |
arrays | R.array.XXX | @array/XXX | |
style | R.style.XXX | @style/XXX | 需要考虑到parent,比较复杂 |
attr | R.attr.XXX | 需要特殊处理,主要在自定义属性上 |
从上表格看,大部分资源都是有规律的,也就是我们可以读取源代码文件,与xml文件,来进行整体资源的重命名与整体替换;
Tips: attr 与 style 有些特殊,需要特殊处理;
1.3 资源重名替换的风险性
-
如何保证原子性,即:要么文件全部替换成功,要么全部不替换;
目前没有找到合适方案;
-
如何保证不替换公共库资源,比如:当前模块中a,引用了公共库的资源
common.png
,如何保证common.png
不被重命名成a_common.png
解决:只替换本模块中声明的资源,也就是只读取本模块中res/下定义的资源;
-
attrs
与style
资源的特殊性这2块资源,可能用的不是很多,暂时可以先手动修改一下;麻烦在以下原因:
因为style可能会是继承下来的,所以这块替换容易出问题,后期再考虑;
attrs同样如此,主要用来自定义控件属性这块;后期再考虑;
1.4 方案可行性
如果暂不考虑批量文件替换,重命名事务性问题,通过上面的分析,我们得知,如果要进行资源重命名,我们需要通过程序检索出某资源出现在该模块位置,然后替换之:
因为源代码与xml文件都是文本文件内容,所以我们可以通过正则
全工程检索,然后将用到资源按顺序分别重命名;然后替换到现有文件;从而达到目的;
比如 strings 字符串资源:
-
资源名(资源定义处, xml 中定义):
<string name="default_loading">正在加载…</string>
-
源代码中(引用,源代码包括Java与Kt代码):
String s = getResources().getString(R.string.default_loading);
-
xml中引用
<TextView android:text="@string/default_loading" ... />
假设前缀为:common_ui_
, 那么资源default_loading
出现的所有位置,应该替换成:
-
资源名(资源定义处):
<string name="common_ui_default_loading">正在加载…</string>
-
源代码中(引用):
String s = getResources().getString(R.string.common_ui_default_loading);
-
xml中引用:
<TextView android:text="@string/common_ui_default_loading" .../>
这样就完成了一个string类型资源的替换,类似的,可用这个方式替换其他资源;
2.具体实现
有了上面的分析,我们就可以开始动手设计了,主要用到的是正则相关的知识,不得不说,正则是一把锋利的瑞士军刀,关键时候,能够大显身手;
2.1 类图设计
因为资源分类2大类(独立文件夹与values形式),但是这2大类资源都可能会出现3个地方:
- 定义处;
- 源代码中;
- 资源xml引用中;
设计的整体类图如下:
[图片上传失败...(image-21c97e-1539049496327)]
工作过程简要说明:
-
文件夹形式
为了更好的维护与扩展,将每一种的资源独立形成一个类,继承自BaseFolderResReplace
, 比如layout
,分为3步走:a. 先实现自己的正则表达式;
b. 利用基类中的方法,进行全局替换;
c. 替换后完,需要将资源的文件名重名也就是调用
renameFile
; values形式
资源也就是values文件夹,继承自BaseReplace
,操作与上类似,这里面不需要修改文件名;
2.2 详细设计
考虑到需要经常操作xml文件,而groovy
内置了强大的xml文件处理能力,因为大部分实现采用groovy
来编码,
同时 groovy
也是实现gradle
的灵魂语言,这就为此模块做成gralde
插件提供了保证.
以 layout 为例
源代码如下:
// layout resource
public class LayoutReplace extends BaseFolderResReplace {
// filter
private def final DIR_FILTER = new Tools.DirNamePrefixFilter("layout")
private def final RES_TYPE_NAME = "layout"
LayoutReplace(Object srcFolderPath, Object resFolderPath) {
super(srcFolderPath, resFolderPath)
}
@Override
String getResTypeName() {
return RES_TYPE_NAME
}
@Override
String getJavaRegex() {
// group 6 为名字
return "(R(\\s*?)\\.(\\s*?)layout(\\s*?)\\.(\\s*?))(\\w+)"
}
@Override
String getXmlRegex() {
// group 2为名字
return "(@layout/)(\\w+)"
}
@Override
Set<String> getResNameSet() {
Set<String> layoutNameSet = new HashSet<>()
// 1.获取所有layout开头的文件夹
File[] layoutDirs = resDir.listFiles(DIR_FILTER)
// 2.获取layout名字并存储
layoutDirs?.each { layoutDir ->
layoutDir.eachFile { it ->
layoutNameSet.add(it.name.substring(0, it.name.lastIndexOf(".")))
}
}
return layoutNameSet
}
@Override
void replaceSrc(Set<String> resNameSet, java_regx) throws IOException {
println("---------- layout ----- 替换源代码目录开始")
replaceSrcDir(srcDir, resNameSet, java_regx)
println("---------- layout ----- 替换源代码目录结束")
}
@Override
void replaceRes(Set<String> resNameSet, xml_regx) throws IOException {
println("---------- layout ----- 替换资源目录开始")
// 1.替换文件内容
replaceResDir(resDir, resNameSet, xml_regx, DIR_FILTER)
// 2.修改文件名
renameFile(resDir, DIR_FILTER, RES_TYPE_NAME)
println("---------- layout ----- 替换资源目录结束")
}
}
3. TODO
- 文件的事务操作有待支持;
- Attrs 有待支持;
- style有待支持;
- 其他资源类型有待支持;
- 如果资源名称与Android自带资源名重复时,会发生替换问题,此问题,有待修正;