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

原文链接: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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,401评论 25 707
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,702评论 22 664
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,350评论 0 17
  • 幢幢雪,悠悠花,假道汴京霜满湘。 美人泪,小轩窗,漏夜流连胡姬床。 昆仑既知恩爱去,一曲阳关诉衷肠。
    源哲学阅读 340评论 0 0
  • 就在刚刚下载了简书app,我想终于是要提笔写些什么?记录生活,记录思想,记录成长,或是通过文字表达情感。 ...
    晓莲Alian阅读 204评论 2 3