用Python解決Android布局中的字符串硬編碼問題

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

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>
  1. 硬編碼不利于復(fù)用
  2. 硬編碼不利于維護(hù)
  3. 硬編碼性能低于軟編碼

問題的出現(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:textandroid: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)

最后

  1. 將生成的strings.xml文件內(nèi)容最佳到項(xiàng)目字符串資源文件中败晴。
  2. 再將生成的布局文件覆蓋到項(xiàng)目res/layout目錄下。

到此栽渴,字符串硬編碼問題解決尖坤。歡迎對我提出建議或意見,若喜歡請star闲擦!
完整代碼請看:https://github.com/bbsmp/ResovleAndroidHardCodeWithPython.git慢味。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市佛致,隨后出現(xiàn)的幾起案子贮缕,更是在濱河造成了極大的恐慌,老刑警劉巖俺榆,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件感昼,死亡現(xiàn)場離奇詭異,居然都是意外死亡罐脊,警方通過查閱死者的電腦和手機(jī)定嗓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萍桌,“玉大人宵溅,你說我怎么就攤上這事∩涎祝” “怎么了恃逻?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長藕施。 經(jīng)常有香客問我寇损,道長,這世上最難降的妖魔是什么裳食? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任矛市,我火速辦了婚禮,結(jié)果婚禮上诲祸,老公的妹妹穿的比我還像新娘浊吏。我一直安慰自己而昨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布找田。 她就那樣靜靜地躺著歌憨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪午阵。 梳的紋絲不亂的頭發(fā)上躺孝,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機(jī)與錄音底桂,去河邊找鬼。 笑死惧眠,一個(gè)胖子當(dāng)著我的面吹牛籽懦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播氛魁,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼暮顺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了秀存?” 一聲冷哼從身側(cè)響起捶码,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎或链,沒想到半個(gè)月后惫恼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澳盐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年祈纯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叼耙。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腕窥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出筛婉,到底是詐尸還是另有隱情簇爆,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布爽撒,位于F島的核電站入蛆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏匆浙。R本人自食惡果不足惜安寺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望首尼。 院中可真熱鬧挑庶,春花似錦言秸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凳枝,卻和暖如春抄沮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背岖瑰。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工叛买, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蹋订。 一個(gè)月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓率挣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親露戒。 傳聞我的和親對象是個(gè)殘疾皇子椒功,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,867評論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,739評論 22 665
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程俊庇,因...
    小菜c閱讀 6,376評論 0 17
  • 幢幢雪冶忱,悠悠花肛搬,假道汴京霜滿湘校翔。 美人淚褐奴,小軒窗墙基,漏夜流連胡姬床喂链。 昆侖既知恩愛去黍析,一曲陽關(guān)訴衷腸节沦。
    源哲學(xué)閱讀 342評論 0 0
  • 就在剛剛下載了簡書app键思,我想終于是要提筆寫些什么?記錄生活甫贯,記錄思想吼鳞,記錄成長,或是通過文字表達(dá)情感叫搁。 ...
    曉蓮Alian閱讀 205評論 2 3