关于sdk的那点事,多次在sdk群看到同行讨论R文件。所以写出我的思路,分享一下自己的经验。
那么开始吧,
首先一般发现需要做合并R操作的, 基本上都是遇到了,没有使用”行规“, 动态获取资源id,而是使用R.id的方式
在说明前, 先暂且称 原母包为A包,需要替换的资源包为B包 , 并且需知道 public.xml是用来固定资源id的
那么就有
方式一 同时删除 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 ,并反向修改相关的R.smali文件
然后A和B 获取资源的方式组成有
A和B都使用动态获取资源Id, 上诉4中方法都可以
A使用动态获取资源id,B 使用R.id的方式 方法三,四 可以轻松解决
A使用R.id,B使用动态获取, 方法二,四 轻松解决
A和B都使用R.id , 方法四 轻松秒杀
由上面分析, 基本可以得出 方法四是个万金油的方法, 而且根据描述的理解起来也并不困难,但很多同学到了怎么合并就开始犯愁了,因为手头上拥有的打包脚本并没有做这方面的处理
在做合并前,需要先了解一下
2、资源 ID
可以得出,
1、public.xml是用来固定资源Id的
2、在构建时,aapt 工具会收集您定义的所有资源(尽管是单独的文件或文件中的显式定义)并为它们分配资源 ID。
资源 ID 是一个 32 位数字,格式为:PPTTNNNN。PP 是资源用于的包;TT 是资源的类型;NNNN 是该类型中资源的名称。对于应用程序资源,PP 始终为 0x7f
TT 和 NNNN 值由 aapt 任意分配——基本上对于每种新类型,都会分配和使用下一个可用数字(从 1 开始);同样,对于类型中的每个新名称,都会分配和使用下一个可用编号(从 1 开始)。
拥有上面的知识后,基本上合并public.xml也就并没有难度了
逻辑可以参考如下
因为PP始终为0x7f
value = TTNNNN , 资源id为 0x7f + value
可以这么表示 [type][name] = value
那么,我们合并逻辑就可以是, 用A包作为母本,B包融合进A包
即 A的public.xml 先固定
然后从B包的public.xml 里面把每个资源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")
到这里基本上已经把public.xml合并完成了, 但是需要注意的是如果是用R.id 的方式去拿资源id的话, A包是资源ID是正常的, B包由于是合并进A包会导致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)
主要代码也就这么多了。
可能有的伙伴在已有的基础上加上上述脚本可能不太符合他们的流程。
另外附送其他的思路:
1、把A,B包的public.xml 删除,合并资源
2、重新生成包C
3、C包解包
4、在C包包名下拿到对应新生成的R文件
5、替换到原来A,B包的R文件
6、修改替换后的R文件的路径地址
7、回编,完成
文笔有限,欢迎大家一起探讨。