美文网首页
组件化路上之二 Android 资源重名问题解决

组件化路上之二 Android 资源重名问题解决

作者: zhaoyubetter | 来源:发表于2018-10-09 09:45 被阅读385次

    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 资源重名替换的风险性

    1. 如何保证原子性,即:要么文件全部替换成功,要么全部不替换;

      目前没有找到合适方案;

    2. 如何保证不替换公共库资源,比如:当前模块中a,引用了公共库的资源 common.png,如何保证

      common.png 不被重命名成 a_common.png

      解决:只替换本模块中声明的资源,也就是只读取本模块中res/下定义的资源;

    3. attrsstyle 资源的特殊性

      这2块资源,可能用的不是很多,暂时可以先手动修改一下;麻烦在以下原因:

      因为style可能会是继承下来的,所以这块替换容易出问题,后期再考虑;

      attrs同样如此,主要用来自定义控件属性这块;后期再考虑;

    1.4 方案可行性

    如果暂不考虑批量文件替换,重命名事务性问题,通过上面的分析,我们得知,如果要进行资源重命名,我们需要通过程序检索出某资源出现在该模块位置,然后替换之:

    因为源代码与xml文件都是文本文件内容,所以我们可以通过正则全工程检索,然后将用到资源按顺序分别重命名;然后替换到现有文件;从而达到目的;

    比如 strings 字符串资源:

    1. 资源名(资源定义处, xml 中定义):

      <string name="default_loading">正在加载…</string>
      
    2. 源代码中(引用,源代码包括Java与Kt代码):

      String s = getResources().getString(R.string.default_loading);
      
    3. xml中引用

      <TextView
          android:text="@string/default_loading"
          ...
          />
      

    假设前缀为:common_ui_, 那么资源default_loading 出现的所有位置,应该替换成:

    1. 资源名(资源定义处):

      <string name="common_ui_default_loading">正在加载…</string>
      
    2. 源代码中(引用):

      String s = getResources().getString(R.string.common_ui_default_loading);
      
    3. xml中引用:

      <TextView
          android:text="@string/common_ui_default_loading"
          .../>
      

    这样就完成了一个string类型资源的替换,类似的,可用这个方式替换其他资源;

    2.具体实现

    有了上面的分析,我们就可以开始动手设计了,主要用到的是正则相关的知识,不得不说,正则是一把锋利的瑞士军刀,关键时候,能够大显身手;

    2.1 类图设计

    因为资源分类2大类(独立文件夹与values形式),但是这2大类资源都可能会出现3个地方:

    1. 定义处;
    2. 源代码中;
    3. 资源xml引用中;

    设计的整体类图如下:

    [图片上传失败...(image-21c97e-1539049496327)]

    工作过程简要说明:

    1. 文件夹形式为了更好的维护与扩展,将每一种的资源独立形成一个类,继承自BaseFolderResReplace, 比如layout ,分为3步走:

      a. 先实现自己的正则表达式;

      b. 利用基类中的方法,进行全局替换;

      c. 替换后完,需要将资源的文件名重名也就是调用 renameFile

    2. 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

    1. 文件的事务操作有待支持;
    2. Attrs 有待支持;
    3. style有待支持;
    4. 其他资源类型有待支持;
    5. 如果资源名称与Android自带资源名重复时,会发生替换问题,此问题,有待修正;

    相关文章

      网友评论

          本文标题:组件化路上之二 Android 资源重名问题解决

          本文链接:https://www.haomeiwen.com/subject/jyfeaftx.html