作者
大家好,我叫小鑫,也能够叫我蜡笔小鑫;
自己17年毕业于中山大学,于2018年7月加入37手游安卓团队,曾经上任于久邦数码担任安卓开发工程师;
目前是37手游安卓团队的海外负责人,负责相关事务开发;一起统筹一些基础建设相关工作。
背景
在做插件化过程中,宿主需求用到插件的资源,涉及到加载插件的资源;
因为插件是以apk的办法存在的,所以插件的ID和宿主的ID可能导致重复;
为了处理这个问题,需求把插件的资源ID重新排一遍,才给宿主加载和运用。
资源加载
Android项目中的资源惯例是经过R文件来索引的。打包的时分aapt将工程中的资源名与id在R文件中映射起来。运用资源时是经过Resources获取的,如:
resources.getDrawable(xxxid)
那么怎样加载插件中的资源呢?先看Resources的结构办法:
public Resources(AssetManager assetManager, DisplayMetrics metrics, Configuration config) {
}
Resources的初始化依靠:AssetManager,DisplayMetrics和Configuration。
在AssetManager中有一个办法
public int addAssetPath(String path)
该办法可用于AssetManager添加外部资源,可是AssetManager中该办法不对外揭露,需求反射调用,代码大概如下:
val method = AssetManager::class.java.getDeclaredMethod("addAssetPath", String::class.java)
method.isAccessible = true
method.invoke(hostAssetManager, apk)
资源抵触计划
经过上述的处理后,插件的资源和宿主的资源是混合再一起的,因为一切的资源索引不做处理时都是以0x7faabbbb格局按次序生成的。所以会存在插件资源ID和宿主资源ID抵触的状况(留意:资源索引共四个字节32位,第一个字节代表PackgeID,第二个字节代表TypeID,后两个字节代表资源值)
怎样处理抵触呢?有几种计划:
计划一:资源阻隔
资源阻隔便是宿主和插件运用不同的Resources目标,这样运用的资源文件不同,不存在抵触。代码调整如下:
//经过反射生成一个新的AssetManager实例
val am = AssetManager.class.newInstance()
//添加资源
val method = AssetManager::class.java.getDeclaredMethod("addAssetPath", String::class.java)
method.isAccessible = true
method.invoke(am, apk)
//生成新的Resources目标
val pluginResources = Resources(am, hostResources.displayMetrics, hostResources.configuration)
插件运用这个PluginResources。可是这儿会有一些缺陷:
1、宿主和插件的资源无法同享,运用起来不太便利
2、PluginResouces中不包括体系资源,在某些用到体系资源的场景会报错。如:加载前端页面,运用select标签时会运用到体系的一些资源
3、某些事务场景下,插件的Resources和宿主的Resources需求用同一个。如:游戏发行sdk,sdk相关界面非activity,运用到的Resources是游戏研制方activity(在宿主中)。
计划二:修正资源id
修正资源id,当时常用的计划有:
计划1:Android的资源id是aapt生成的,修正aapt,让插件中的资源id不从0x7f开始,比方从0x6f开始
计划2:生成插件apk后,修正插件apk中resources.arsc文件。resources.arsc文件是有固定格局的文件
可经过解析该文件修正资源索引值。可是存在必定缺陷:
1、修正难度较大,需求了解resources.arsc格局
2、只修正resources.arsc是不够的,因为代码中运用的是R类,还一起需求修正代码中的值;
补白:虽然改起来费事,可是仍是有大神搞定了的,具体戳这儿>>>
计划3:经过反编译修正插件中的public.xml文件中的索引值,以及修正smali中的R类值
public.xml这个文件是哪来的?
该文件是apktool在反编译apk时,根据apk包中的resources.arsc文件生成。 没看过resource.arsc? (自己拖个apk到IDE看吧)
public.xml有什么作用
publc.xml是aapt在打包资源时用来固定资源id的,假如资源在public.xml中有对应的id了,那么打包资源时就用已经有的id。
public.xml中的id的格局
共四个字节32位,第一个字节代表PackgeID,第二个字节代表TypeID,后两个字节代表资源值
通常体系资源PackageID是01,而咱们自己的资源PackageID是7f
TypeID,比方attr为01,string为02。可是并不固定,并不必定attr便是01。可是在public.xml中,同类型的该字节必定是相同的,否则回编译会失利。
R类
R类这儿有个知识点,library模块中生成的R类中的成员的值不是常量,不带final。app模块生成的R类的值是常量值。而常量值在java编译时会被优化,终究代码中输出的便是常量值,而不是R.id.xxx这样。而library的因为是变量,不会被优化,代码中会保存R.id.xxx
R类和public.xml的联系
从本质上讲,其实并没有啥联系。可是因为在代码中咱们会运用R.id去查找资源,这就关联上了。假如都用getIdentifier的办法先获取id,那把R类删了也没事。
public.xml打包后对应的便是resources.arsc中的值,而资源值生成Java类,这个类便是R类。也便是说平常运用R类,便是用里边的索引值去到resources.arsc中找到对应资源位置,再去加载。
修正步骤如下:
1、反编译插件apk
2、修正public.xml,将0x7faabbbb中的aa的值调高,比方0x7f010001→0x7f510001,因为第二个字节是TypeID,是按次序添加的,而这个type实际上是没有多少的。修正成比较大的值后就不会抵触了。 (具体修正可参阅提供代码中的PublicXmlBean类实现)
3、修正R$x.smali的值,反编译后R类便是一些smali代码,扫描smali代码,将其中的值也都按相同的规则增大。这样R类和resources.arsc依旧是对应的,代码中的R类正常。可是这儿需求留意的是application模块的R类因为是final常量值,会被处理成常量。这种状况下修正R类其实不会有作用。而library的状况则能够。因而需求将涉及到R的代码及资源文件下沉为library模块依靠。
4、回编译,得到修正资源索引后的插件
代码戳这儿>>>
源码工程简介
概要
程序入口
插件生成小tips
插件源码工程中的插件apk结构(也便是plugin.apk):
实际代码下沉为resmodule作为library模块,app模块中并无实际功能,只是作为application模块引证library,这样就能够输出咱们的插件plugin.apk。 这样做的意图上面有说,其实便是防止咱们的代码等在app模块,被弄成常量了。
作用
用工具修正前的R.layout.activity_main的值为2131296284,转换为16进制为0x7f09001c
用工具修正后再次检查R.layout.activity_main的值为2136539164,即0x7f59001c
总结
本文介绍了插件资源加载的多种计划,能够按照自己的运用场景以及团队技能才能挑选适宜的计划
欢迎交流
过程中有问题或者需求交流的同学,能够扫描二维码加老友,然后进群进行问题和技能的交流等;