關(guān)于游戲SDK ,public.xml 合并的那些事

關(guān)于sdk的那點(diǎn)事,多次在sdk群看到同行討論R文件。所以寫出我的思路,分享一下自己的經(jīng)驗(yàn)。

那么開始吧,

首先一般發(fā)現(xiàn)需要做合并R操作的, 基本上都是遇到了,沒有使用”行規(guī)“, 動(dòng)態(tài)獲取資源id,而是使用R.id的方式

在說明前, 先暫且稱 原母包為A包,需要替換的資源包為B包 , 并且需知道 public.xml是用來固定資源id的

那么就有

方式一 同時(shí)刪除 A與B的 public.xml 資源ID 由AAPT重新生成

方式二 保留A的 public.xml , 刪除B的 public.xml 即 B的資源id由aapt重新生成

方式三 保留B的 public.xml ,刪除A的 public.xml 即 A的資源id由aapt重新生成

方式四 通過腳本合成 A和B的 public.xml ,并反向修改相關(guān)的R.smali文件

然后A和B 獲取資源的方式組成有

A和B都使用動(dòng)態(tài)獲取資源Id, 上訴4中方法都可以

A使用動(dòng)態(tài)獲取資源id,B 使用R.id的方式 方法三,四 可以輕松解決

A使用R.id,B使用動(dòng)態(tài)獲取, 方法二,四 輕松解決

A和B都使用R.id , 方法四 輕松秒殺

由上面分析, 基本可以得出 方法四是個(gè)萬金油的方法, 而且根據(jù)描述的理解起來也并不困難,但很多同學(xué)到了怎么合并就開始犯愁了,因?yàn)槭诸^上擁有的打包腳本并沒有做這方面的處理

在做合并前,需要先了解一下

1、public.xml的作用

2、資源 ID

可以得出,

1、public.xml是用來固定資源Id的

2、在構(gòu)建時(shí),aapt 工具會(huì)收集您定義的所有資源(盡管是單獨(dú)的文件或文件中的顯式定義)并為它們分配資源 ID。

資源 ID 是一個(gè) 32 位數(shù)字,格式為:PPTTNNNN。PP 是資源用于的包;TT 是資源的類型;NNNN 是該類型中資源的名稱。對于應(yīng)用程序資源,PP 始終為 0x7f

TT 和 NNNN 值由 aapt 任意分配——基本上對于每種新類型,都會(huì)分配和使用下一個(gè)可用數(shù)字(從 1 開始);同樣,對于類型中的每個(gè)新名稱,都會(huì)分配和使用下一個(gè)可用編號(hào)(從 1 開始)。

擁有上面的知識(shí)后,基本上合并public.xml也就并沒有難度了

邏輯可以參考如下

因?yàn)镻P始終為0x7f

value = TTNNNN , 資源id為 0x7f + value

可以這么表示 [type][name] = value

那么,我們合并邏輯就可以是, 用A包作為母本,B包融合進(jìn)A包

即 A的public.xml 先固定

然后從B包的public.xml 里面把每個(gè)資源id 拿出出來

先判斷是否有type, 再判斷是否有name 若[type][name] 存在就跳過,若name不存在,就拿type的maxid + 1

python代碼如下


def public_merged(self):

        plug_value_path = os.path.join(self._plug_path, "handleres", "values")

        plug_public_file_path = os.path.join(plug_value_path, "public.xml")

        with open(plug_public_file_path, 'r') as xml_file:

            plug_public_xml = ET.parse(xml_file)

        package_public_file_path = os.path.join(self._package_path, "res", "values", "public.xml")

        with open(package_public_file_path, 'r') as xml_file:

            package_xml = ET.parse(xml_file)

        package_unique_id_mapping = {}

        for ele in package_xml.getroot():

            id_type = ele.attrib['type']

            id_name = ele.attrib['name']

            id_value = int(ele.attrib['id'], 16)

            if id_type not in package_unique_id_mapping:

                package_unique_id_mapping[id_type] = {}

                package_unique_id_mapping[id_type]["maxId"] = 0

                package_unique_id_mapping[id_type]["nameList"] = []

            package_unique_id_mapping[id_type]["nameList"].append(id_name)

            if id_value > package_unique_id_mapping[id_type]["maxId"]:

                package_unique_id_mapping[id_type]["maxId"] = id_value

        all_type_max_id = 0

        for type_map in package_unique_id_mapping.values():

            if type_map["maxId"] > all_type_max_id:

                all_type_max_id = type_map["maxId"]

        for ele in plug_public_xml.getroot():

            id_type = ele.attrib['type']

            id_name = ele.attrib['name']

            if id_type not in package_unique_id_mapping.keys():

                package_unique_id_mapping[id_type] = {}

                package_unique_id_mapping[id_type]["maxId"] = 0

                package_unique_id_mapping[id_type]["nameList"] = []

                package_unique_id_mapping[id_type]["nameList"].append(id_name)

                new_all_type_max_id = ((all_type_max_id >> 16) + 1) << 16

                package_unique_id_mapping[id_type]["maxId"] = new_all_type_max_id

                all_type_max_id = new_all_type_max_id

                ele.set("id", ("0x%x" % new_all_type_max_id))

                package_xml.getroot().append(ele)

                continue

            if id_name in package_unique_id_mapping[id_type]["nameList"]:

                continue

            max_id_value = package_unique_id_mapping[id_type]["maxId"]

            new_id_value = max_id_value + 1

            ele.set("id", ("0x%x" % new_id_value))

            package_unique_id_mapping[id_type]["maxId"] = new_id_value

            package_unique_id_mapping[id_type]["nameList"].append(id_name)

            package_xml.getroot().append(ele)

        package_xml.write(package_public_file_path, "UTF-8")

到這里基本上已經(jīng)把public.xml合并完成了, 但是需要注意的是如果是用R.id 的方式去拿資源id的話, A包是資源ID是正常的, B包由于是合并進(jìn)A包會(huì)導(dǎo)致B包的資源Id需要更新。
思路就是把B包下的smali文件遍歷,拿到R$*.xml 然后把里面的資源id更新為合并后的public.xml的資源id
python腳本如下

    def change_smali_with_public_xml(self, smali_path):
        public_xml_path = os.path.join(self._package_path, "res", "values", "public.xml")
        et_public_xml = ET.parse(public_xml_path)
        r_file_name_list = ["R$xml.smali", "R$style.smali", "R$string.smali", "R$raw.smali", "R$layout.smali",
                            "R$id.smali",
                            "R$drawable.smali", "R$dimen.smali", "R$color.smali", "R$attr.smali", "R$array.smali",
                            "R$anim.smali"]
        unique_id_mapping = {}
        public_xml_root = et_public_xml.getroot()

        for ele in list(public_xml_root):
            res_type = ele.get('type')
            res_name = ele.get('name')
            res_id = ele.get('id')
            if res_type not in unique_id_mapping:
                unique_id_mapping[res_type] = {}
            unique_id_mapping[res_type][res_name] = res_id
        for file_name in r_file_name_list:
            file_path = os.path.join(smali_path, file_name)
            dot_index = file_name.index('.')
            res_type = file_name[2:dot_index]
            if os.path.exists(file_path):
                with codecs.open(file_path, 'r', 'utf-8') as f:
                    lines = f.readlines()
                    for line in lines:
                        res_name = get_res_name_for_line(line)
                        if res_name is not None:
                            new_id = unique_id_mapping.get(res_type).get(res_name)
                            if new_id is not None:
                                new_line = update_res_id(line, new_id)
                                old_line_index = lines.index(line)
                                lines[old_line_index] = new_line

                    out_lines = lines

                with codecs.open(file_path, 'w', 'utf-8') as f:
                    f.writelines(out_lines)

主要代碼也就這么多了。

可能有的伙伴在已有的基礎(chǔ)上加上上述腳本可能不太符合他們的流程。
另外附送其他的思路:
1、把A,B包的public.xml 刪除,合并資源
2、重新生成包C
3、C包解包
4、在C包包名下拿到對應(yīng)新生成的R文件
5、替換到原來A,B包的R文件
6、修改替換后的R文件的路徑地址
7、回編,完成

文筆有限,歡迎大家一起探討。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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