關(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)槭诸^上擁有的打包腳本并沒有做這方面的處理
在做合并前,需要先了解一下
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、回編,完成
文筆有限,歡迎大家一起探討。