用Python解决Android布局中的字符串硬编码问题

原文链接:https://bbsmp.github.io

Android布局中的硬编码

  • 什么是Android布局中的硬编码

    Android里的硬编码指在布局里直接填写值(如尺寸、颜色、字符等),而非对相关资源的引用。这里以android:text为例:

硬编码:


软编码:


“你好,我是硬编码”在字符串资源里是这样的:
你好,我是硬编码

  • 硬编码的优缺点
    在Android布局中硬编码有什么优缺点呢,在我看来除了方便外,并没有别的优点,然而这个“优点”会为后面的维护扩展带来困难,所以也算不得什么优点,大致可认为这是Android开发中的一个坏习惯。这个坏习惯我们尽量要改掉,因为:
  1. 硬编码不利于复用
  2. 硬编码不利于维护
  3. 硬编码性能低于软编码

问题的出现

由于大多数场景下项目并没有涉及国际化,加上自己的一些不良习惯,经常在Android布局文件中进行硬编码。这通常不会出什么大问题,然而最近把项目转到Windows环境下开发,居然跑不起来了。报了这样的错误:

错误: Exception while handling step android.databinding.annotationprocessor.ProcessExpressions@572da56d javax.xml.bind.UnmarshalException
- with linked exception:
[org.apache.xerces.impl.io.MalformedByteSequenceException: Invalid byte 2 of 2-byte UTF-8 sequence.]
······

经查发现,这是因为在Windows环境下,布局中databinding相关的中文字符非UTF-8所致,也就是说,这是中文硬编码导致的问题。用Lint分析一下发现硬编码的地方有279个,叉,手动改的话还不改死人?而且手动,这违背程序员懒的美德。怎么办呢?刚好前段时间看了一下Python,那就用Python解决这个问题吧!

利用Python解决字符串硬编码问题

我们要做的事情,就是查找出布局文件里所有的中文字符串,并为其生成一个符合Android字符串资源命名规范的名字,构造字符串资源,然后将布局里所有的字符串替换为字符串资源的引用如@stirng/hard_code。如下:android:text="你好,我是硬编码"—>你好,我是硬编码—>android:text="@stirng/hard_code"。有思路之后,就可以动手了。

首先把项目res/layout文件夹复制出来,然后:

查找所有的硬编码字符串

Android布局文件中,可能硬编码的属性有android:textandroid:hinttools:text、尺寸相关的android:textSizeandroid:layout_widthandroid:layout_height等这里我仅关注android:textandroid:hinttools:text即可。

#属性
#需将`android:`、`tools`替换为原命名空间
attrs = (
    "{http://schemas.android.com/apk/res/android}text",
    "{http://schemas.android.com/apk/res/android}hint",
    "{http://schemas.android.com/tools}text",
)



1.  获取布局文件
def get_layout_files(path):
    '''
    获取所有的布局文件
    :param path: 布局文件路径
    :return:
    '''
    res = []
    files = os.listdir(path)
    for file in files:
        res.append(path + "/" + file)
    return res
2. 解析布局文件,这里我们用lxml的ElementTree来解析,所以我们需要引入:
try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

a. 根文件获取ElementTree的根节点
def get_file_element_tree(file):
    '''
    更具文件名(路径)返回ElementTree根节点
    :param file:
    :return:
    '''
    tree = ET.ElementTree(file=file)
    return tree.getroot()
b. 获取硬编码的属性值
def find_hard_code_attribute_value(tree_root, attrs):
    '''
    获取属性值
    :param tree_root: ElementTree树根
    :param attr: 要获取值的属性
    :return: set() 返回值, 用集合保存,可以去掉重复的元素
    '''
    res = set()
    for attr in attrs:
        root_hard_code = tree_root.get(attr) #  获取根节点的硬编码
        if root_hard_code is not None and len(root_hard_code) and     str(root_hard_code).find("@string/") == -1:
        # 如果属性值不会空且不是软编码()则就是我们要找的硬编码字符串
            res.add(root_hard_code)
        children = tree_root.findall(".//*[@" + attr + "]")
        for child in children:
            hard_code = child.get(attr) # 获取属性值
            if hard_code is not None and len(hard_code) and str(hard_code).find("@string/") == -1:
            # 如果属性值不会空且不是软编码()则就是我们要找的硬编码字符串
                res.add(hard_code)
    return res

生成strings.xml文件

1. 根据硬编码的字符串,生成对应的符合Android字符串资源命名规范的名称,用字典保存,字典key为硬编码字符串,value为对应的名称。
def generate_name_of_hard_code_string(hard_codes):
    '''
    根据硬编码字符串生成符合规范的名字,这里我们根据这样的规则生成名字:
    a、英文字符串,则用其本省(出去空格、标点等)
    b、中文字符串,则为其单字节拼音用"_"连接,如"硬编码"对应的名称为"yin_bian_ma",
        这里的拼音转换我们通过pypinyin库来实现,如果涉及到分词,还需要安装jieba
    :param hard_codes:
    :return: 返回类型为字典,字典的键为硬编码的值,值则为根据硬编码生成的符合strings资源文件命名规范的字符串
    '''
    res = dict()
    for hard_code in hard_codes:
        hc = re.sub("""[\s+\.\!\/_,\{\}:$%^*()?+\"\']+|[+——+!:,\\\ 。?、~@#¥%……&*()]+""", "", hard_code) #去除特殊字符
        py = ''
        if hc is None or len(hc) == 0: #如果去除字符后为,则硬编码为特殊字符,这是我们就要随机命名
            py = generate_random_string(15)
        else:
            py = lazy_pinyin(hc)
            py = '_'.join(py)[0:25].strip() #限制长度,去除空格
        try:
            res[str(hard_code)] = py
        except Exception as e:
            print(e)
            pass

    return res

2.根据上一步生成的字典,构造strings.xml文件
def generate_strings_xml(file, dict):
    '''
    根据字典生成strings.xml文件
    :param file: 文件路径
    :param dict: 硬编码字符串和其名称构成的字典
    :return:
    '''
    f = open(file,"w")
    strings = []
    strings.append('\n')
    for (k, v) in dict.items():
        temp = '\t' + k + '\n'
        strings.append(temp)
    strings.append("")
    try:
        f.writelines(strings)
        f.close()
        print("strings.xml文件生成成功")
    except Exception as e:
        print(e)
        print("strings.xml文件生成失败")
        pass
检查生成的strings.xml文件,手动进行命名优化。

替换硬编码字符串

1. 读取strings.xml中的字符串资源,构造字典。
def get_string_and_name_from_stringXML(file):
    '''
    读取strings.xml中的字符串资源,用字典保存
    :param file:
    :return:
    '''
    res = {}
    root = get_file_element_tree(file)
    strings = root.findall(".//string")
    for str in strings:
        res[str.text] = str.get("name")
    return res

2.替换布局文件中的硬编码
def replace_hard_code(src_file, des_file, dicts):
    '''
    用字符串引用替换所有的字符
    :param src_file: 带替换的布局文件
    :param des_file: 替换后的文件
    :param dict: 硬编码字典
    :return:
    '''
    lines = open(src_file).readlines()
    new_lines = []
    for line in lines:
        for (k, v) in dicts.items():
            line = line.replace('="' + k +'"',  '="' + "@string/" + v + '"')
        if len(line.strip()) > 0:
            new_lines.append(line)

    with open(des_file, "w") as d_f:
        d_f.writelines(new_lines)

最后

  1. 将生成的strings.xml文件内容最佳到项目字符串资源文件中。
  2. 再将生成的布局文件覆盖到项目res/layout目录下。

到此,字符串硬编码问题解决。欢迎对我提出建议或意见,若喜欢请star!
完整代码请看:https://github.com/bbsmp/ResovleAndroidHardCodeWithPython.git。

你可能感兴趣的:(用Python解决Android布局中的字符串硬编码问题)