美文网首页
用Python解决Android布局中的字符串硬编码问题

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

作者: bbsmpp | 来源:发表于2017-02-24 17:07 被阅读0次

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

    Android布局中的硬编码

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

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

    硬编码:

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="你好,我是硬编码"
        android:textSize="@dimen/li_16sp_size"/>
    

    软编码:

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@stirng/hard_code"
        android:textSize="@dimen/li_16sp_size"/>
    

    “你好,我是硬编码”在字符串资源里是这样的:
    <string name="hard_code">你好,我是硬编码</string>

    • 硬编码的优缺点
      在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="你好,我是硬编码"—><string name="hard_code">你好,我是硬编码</string>—>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('<resources>\n')
        for (k, v) in dict.items():
            temp = '\t<string name="' + v + '">' + k + '</string>\n'
            strings.append(temp)
        strings.append("</resources>")
        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布局中的字符串硬编码问题

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