Android布局中的硬編碼
-
什么是Android布局中的硬編碼
Android里的硬編碼指在布局里直接填寫值(如尺寸、顏色、字符等)钥顽,而非對相關(guān)資源的引用来破。這里以
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>
- 硬編碼的優(yōu)缺點(diǎn)
在Android布局中硬編碼有什么優(yōu)缺點(diǎn)呢窿凤,在我看來除了方便外仅偎,并沒有別的優(yōu)點(diǎn),然而這個(gè)“優(yōu)點(diǎn)”會為后面的維護(hù)擴(kuò)展帶來困難卷玉,所以也算不得什么優(yōu)點(diǎn)哨颂,大致可認(rèn)為這是Android開發(fā)中的一個(gè)壞習(xí)慣。這個(gè)壞習(xí)慣我們盡量要改掉相种,因?yàn)椋?/li>
- 硬編碼不利于復(fù)用
- 硬編碼不利于維護(hù)
- 硬編碼性能低于軟編碼
問題的出現(xiàn)
由于大多數(shù)場景下項(xiàng)目并沒有涉及國際化威恼,加上自己的一些不良習(xí)慣,經(jīng)常在Android布局文件中進(jìn)行硬編碼寝并。這通常不會出什么大問題箫措,然而最近把項(xiàng)目轉(zhuǎn)到Windows環(huán)境下開發(fā),居然跑不起來了衬潦。報(bào)了這樣的錯(cuò)誤:
錯(cuò)誤: 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.]
······
經(jīng)查發(fā)現(xiàn)斤蔓,這是因?yàn)樵赪indows環(huán)境下,布局中databinding相關(guān)的中文字符非UTF-8所致,也就是說镀岛,這是中文硬編碼導(dǎo)致的問題弦牡。用Lint分析一下發(fā)現(xiàn)硬編碼的地方有279個(gè),叉漂羊,手動改的話還不改死人驾锰?而且手動,這違背程序員懶的美德走越。怎么辦呢椭豫?剛好前段時(shí)間看了一下Python,那就用Python解決這個(gè)問題吧!
利用Python解決字符串硬編碼問題
我們要做的事情旨指,就是查找出布局文件里所有的中文字符串赏酥,并為其生成一個(gè)符合Android字符串資源命名規(guī)范的名字,構(gòu)造字符串資源谆构,然后將布局里所有的字符串替換為字符串資源的引用如@stirng/hard_code
裸扶。如下:android:text="你好,我是硬編碼"
—><string name="hard_code">你好搬素,我是硬編碼</string>
—>android:text="@stirng/hard_code"
姓言。有思路之后瞬项,就可以動手了蔗蹋。
首先把項(xiàng)目res/layout
文件夾復(fù)制出來何荚,然后:
查找所有的硬編碼字符串
Android布局文件中,可能硬編碼的屬性有android:text
猪杭、android:hint
餐塘、tools:text
、尺寸相關(guān)的android:textSize
皂吮、android:layout_width
戒傻、android:layout_height
等這里我僅關(guān)注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的根節(jié)點(diǎn)
def get_file_element_tree(file):
'''
更具文件名(路徑)返回ElementTree根節(jié)點(diǎn)
: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() 返回值, 用集合保存麻裳,可以去掉重復(fù)的元素
'''
res = set()
for attr in attrs:
root_hard_code = tree_root.get(attr) # 獲取根節(jié)點(diǎn)的硬編碼
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. 根據(jù)硬編碼的字符串口蝠,生成對應(yīng)的符合Android字符串資源命名規(guī)范的名稱,用字典保存津坑,字典key為硬編碼字符串,value為對應(yīng)的名稱妙蔗。
def generate_name_of_hard_code_string(hard_codes):
'''
根據(jù)硬編碼字符串生成符合規(guī)范的名字,這里我們根據(jù)這樣的規(guī)則生成名字:
a疆瑰、英文字符串眉反,則用其本省(出去空格穆役、標(biāo)點(diǎn)等)
b寸五、中文字符串,則為其單字節(jié)拼音用"_"連接孵睬,如"硬編碼"對應(yīng)的名稱為"yin_bian_ma"播歼,
這里的拼音轉(zhuǎn)換我們通過pypinyin庫來實(shí)現(xiàn),如果涉及到分詞掰读,還需要安裝jieba
:param hard_codes:
:return: 返回類型為字典秘狞,字典的鍵為硬編碼的值,值則為根據(jù)硬編碼生成的符合strings資源文件命名規(guī)范的字符串
'''
res = dict()
for hard_code in hard_codes:
hc = re.sub("""[\s+\.\!\/_,\{\}:$%^*()?+\"\']+|[+——+5讣:烁试,\\\ 。拢肆?减响、~@#¥%……&*()]+""", "", hard_code) #去除特殊字符
py = ''
if hc is None or len(hc) == 0: #如果去除字符后為靖诗,則硬編碼為特殊字符,這是我們就要隨機(jī)命名
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.根據(jù)上一步生成的字典刊橘,構(gòu)造strings.xml文件
def generate_strings_xml(file, dict):
'''
根據(jù)字典生成strings.xml文件
:param file: 文件路徑
:param dict: 硬編碼字符串和其名稱構(gòu)成的字典
: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文件,手動進(jìn)行命名優(yōu)化颂鸿。
替換硬編碼字符串
1. 讀取strings.xml中的字符串資源促绵,構(gòu)造字典。
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文件內(nèi)容最佳到項(xiàng)目字符串資源文件中败晴。
- 再將生成的布局文件覆蓋到項(xiàng)目
res/layout
目錄下。
到此栽渴,字符串硬編碼問題解決尖坤。歡迎對我提出建議或意見,若喜歡請star闲擦!
完整代碼請看:https://github.com/bbsmp/ResovleAndroidHardCodeWithPython.git慢味。