Android布局中的硬编码
什么是Android布局中的硬编码
Android里的硬编码指在布局里直接填写值(如尺寸、颜色、字符等),而非对相关资源的引用。这里以android:text为例:
硬编码:
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="你好,我是硬编码"
android:textSize="@dimen/li_16sp_size"/>
软编码:
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@stirng/hard_code"
android:textSize="@dimen/li_16sp_size"/>
“你好,我是硬编码”在字符串资源里是这样的:
你好,我是硬编码
硬编码的优缺点
在Android布局中硬编码有什么优缺点呢,在我看来除了方便外,并没有别的优点,然而这个“优点”会为后面的维护扩展带来困难,所以也算不得什么优点,大致可认为这是Android开发中的一个坏习惯。这个坏习惯我们尽量要改掉,因为:
硬编码不利于复用
硬编码不利于维护
硬编码性能低于软编码
问题的出现
由于大多数场景下项目并没有涉及国际化,加上自己的一些不良习惯,经常在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:text、android:hint、tools:text、尺寸相关的android:textSize、android:layout_width 、android:layout_height等这里我仅关注android:text、android:hint、tools: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)
最后
将生成的strings.xml文件内容最佳到项目字符串资源文件中。
再将生成的布局文件覆盖到项目res/layout目录下。