我们先简要说一下AssetManager的一些常识,后面会有文章详谈。

首先,AssetManager在构造的时候,会把系统资源包也就是framework-res.apk加载进去,这里所谓的加载,就是创建ResTable这个类的对象mResources,打开这个资源缩包,然后把里面的resources.arsc读到内存中去,然后解析,把对应的数据填入到mResources中。

然后,我们还通过addAssetPath方法添加了我们的Apk本身,以及所有的资源库,AssetManager同样也会打开并解析他们的resources.arsc,然后会把他们的内容添加到mResources这个对象中去。

另外,android的系统资源包,编译时用的是sdk里的android.jar,但运行时用的是framework-res.apk。它们的差异主要体现在一些不公开的资源,android.jar里是无法访问的,我们在这里可以简单认为它们没有区别。

还是以Android资源管理中的SharedLibrary和Dynamic Reference-------之aapt的处理(二)中提到的  libaa.apk、lib11.apk、libαα.apk三个库为例。它们的包id都是0x00,android源生资源库是 android.jar,它的包id是0x01。

我们编译App的时候aapt 命令中这样写:aapt package -f -I libαα.apk -I android.jar -I lib11.apk -I libaa.apk  .......

请注意这四个-I参数的顺序,这样我们的App编译出来之后,在它的resources.arsc中会通过RES_TABLE_LIBRARY_TYPE记录自己的依赖的库:

0x02----->libαα.apk

0x03----->lib11.apk

0x04----->libaa.apk

但是如果在我们的App的AndroidManifest.xml中这么写:

<uses-library android:required="false" android:name="libaa" />

<uses-library android:required="false" android:name="libαα" />

<uses-library android:required="false" android:name="lib11" />

那么,根据我们在Android资源管理中的SharedLibrary和Dynamic Reference-------之Framework的处理(三)中分析的流程,ResTable对象也就是mResources会先加载Android源生的系统库android.jar,然后加载我们的App本身app.apk,然后依次加载libaa.apk、libαα.apk、lib11.apk,根据我们前面讲的ResTable的尿性,这次在运行时它加载的过程,我们走一次:

先加载framework-res.apk,  包id:0x01           包名:android

再加载App自身,     包id :0x7f            包名:com.app

然后libaa.apk,           包id:0x02            包名: libaa

然后libαα.apk,           包id:0x03            包名: libαα

然后lib11.apk,           包id:0x04            包名: lib11

在这里,我们再重复一次,ResTable在加载资源库的时候,第一次遇到包id为0x00的包时(前文我们讲过共享资源库的包id均为0x00)的时候,就会把它的id记录为0x02;第二次,记录为0x03;第三次记录为0x04.......

我们画一张图来表示此时ResTable对象mResources的结构:

这里的PackageGroup是ResTable中的一个概念,它里面可以放多个Package,一个Package我们可以简单认为对应一个资源包。在这里,一共有我们的com.app、android、libaa、lib11、libαα五个PackageGroup,每个PackageGroup里都只有一个Package。

其实,我们很容易发现,ResTable现在已经翻车了,而且是大型车祸现场!!!

我们假设App.apk引用了,lib11.apk包里的一个资源,那么在编译的时候,根据依赖库的id,这个资源的包id为0x03,假设其type 为0x09,entry为0x078a,则这个资源的id将会是0x0309078a。

可是,当我们的App运行起来,加载完资源并使用的时候,遇到0x0309078a,它将会去哪个包里找资源呢?

没错,就是libαα.apk!

因为运行时,我们App的依赖库和编译时加载的顺序不一样,在运行时,0x03对应的包变成了libαα!

这下全乱套了,乱套了,乱套了!

怎么办?赶紧补救啊!怎么补救?再做一次包关系的映射!我们就叫它Dynamic Reference吧!

我们先看一下注释吧,这个类是用来存储共享库ID的。共享库在编译的时候会被分配一个ID,在运行时加载的时候,也会被分配一个ID,由于编译时和运行时这些共享库加载的顺序可能不一样,所以我们需要DynamicRefTable来保存运行时ID和编译时ID的映射关系。

到这里,Dynamic Reference的概念我们应该都能理解了吧!那么我们就一起看看它是怎么去做这个映射的!代码不多,先说一下它的私有成员吧,假设我们在运行时要找0x0309078a:

mAssignedPackageId 运行时PackageGroup被分配的Id,android PackageGroup id为0x01、com.app PackageGroup 为0x7f、libaa PackageGroup为0x02 、libαα PackageGroup为0x03、lib11 PackageGroup为0x04。

mLookupTable用来存储运行时包ID,它的下标用来表示编译时包的ID,所以它实际就是一个映射表。

mEntries用来存储编译时包名和ID的对应关系。

我们来看看它的几个关键方法的实现:

构造方法很简单,mAssignedPackageId的初始化,这里传过来的就是这个DynamicRefTable所在的PackageGroup的id。另外,这里还会确定Android系统资源包、App包、MTK资源包的id的映射关系。由于它们的包ID是固定的,并不会变,所以它们的值和下标是相等的。

load方法是在加载我们的App本身的时候被调用的,其实就是解析出ResTable的小本本上记录的编译时依赖的资源包的包名和包id的对应关系。然后记录在mEntries中。

添加运行时的包id和编译时包id的对应关系。注意,下标是编译时包id,value是运行时包id。这个方法在运行时,各个依赖库被加载解析的时候调用,当然也会在资源查询的时候被调用。

这个方法用来完成包编译时资源id到运行时包id的替换。如果传过来的编译时包id是APP_PACKAGE_ID(0x7f),那么不是共享资源库就不需要映射了,直接返回;如果传过来的编译时PackageId是0,那也就是说这个id是本资源包的id(否则,不可能为0,因为如果是其它资源包,那么它们的id肯定不会是0,而是动态分配的),直接用mAssignedPackageId也就是本资源包的动态分配的id;如果既不是0x7f也不是0,那就去映射表里查一下,这个很好理解。

到这里DynamicRefTable我们已经介绍完了,下面我们结合DynamicRefTable一起再来走一遍我们的App资源的编译和加载过程:

假设我们的App中有如下代码:

也就是说,我们的App引用了lib11当中的一个color类型的资源:

step 1:

根据我们之前的分析,编译时lib11的packageGroup被分配的id是0x03,并且假设其type 为0x09,entry为0x078a。所以在这个xml文件里,这个TextColor属性的值将被写成0x0309078a。

step 2:

老规矩,在加载时,先加载系统资源包framework-res.apk并解析之,创建PackagGroup 其id为0x01,包名为android。同时还会为这个PackageGroup创建一个DynamicRefTable实例,并且这个实例的mAssignedPackageId为0x01。

step 3:

加载我们的App本身app.apk并及解析之,创建新的PackageGroup 其id为0x7f,包名com.app。同时也会为这个

PackageGroup创建一个DynamicRefTable实例,并且这个实例的mAssignedPackageId为0x7f。然后,它还会调用该DynamicRefTable实例的load()方法,解析我们的apk编译时所依赖的共享资源库:

mEntries[libαα] = 0x02;

mEntries[lib11] = 0x03;

mEntries[libaa] = 0x04;

step 4:

加载我们的资源库libaa.apk并及解析之,创建新的PackageGroup 其id为0x02,包名libaa。同时也会为这个

PackageGroup创建一个DynamicRefTable实例,并且这个实例的mAssignedPackageId为0x02。然后,它会调用我们的App的PackageGroup的DynamicRefTable实例的addMapping(libaa,0x02)方法,向我们的应用PackageGroup添加映射关系:

addMapping方法,先根据共享库的名称,也就是libaa,在mEntries中找到其对应的编译时id为0x04,于是:

mLookupTable[0x04] = 0x02;

这样就建立了libaa的包id映射关系:编译时0x04------->运行时0x02

step 5:

加载我们的资源库libαα.apk并及解析之,创建新的PackageGroup 其id为0x03,包名libαα。同时也会为这个

PackageGroup创建一个DynamicRefTable实例,并且这个实例的mAssignedPackageId为0x03。然后,它会调用我们的App的PackageGroup的DynamicRefTable实例的addMapping(libαα,0x03)方法,向我们的应用PackageGroup添加映射关系:

addMapping方法,先根据共享库的名称,也就是libαα,在mEntries中找到其对应的编译时id为0x02,于是:

mLookupTable[0x02] = 0x03;

这样就建立了libαα的包id映射关系:编译时0x02------->运行时0x03

step 6:

加载我们的资源库lib11.apk并及解析之,创建新的PackageGroup 其id为0x04,包名lib11。同时也会为这个

PackageGroup创建一个DynamicRefTable实例,并且这个实例的mAssignedPackageId为0x04。然后,它会调用我们的App的PackageGroup的DynamicRefTable实例的addMapping(lib11,0x04)方法,向我们的应用PackageGroup添加映射关系:

addMapping方法,先根据共享库的名称,也就是lib11,在mEntries中找到其对应的编译时id为0x03,于是:

mLookupTable[0x03] = 0x04;

这样就建立了lib11的包id映射关系:编译时0x03------->运行时0x04

也就是说,此时我们的应用app.apk所对应的PackageGroup的DynamicRefTable 实例中的数据如下:

mEntries[libαα] = 0x02; //libαα PackageGroup 编译时id:0x02

mEntries[lib11] = 0x03;//lib11 PackageGroup 编译时id:0x03

mEntries[libaa] = 0x04;//libaa PackageGroup 编译时id:0x04

mLookupTable[0x04] = 0x02;//libaa PackageGroup 编译时id:0x04------->运行时id:0x02

mLookupTable[0x02] = 0x03;//libαα PackageGroup 编译时id:0x02------->运行时id:0x03

mLookupTable[0x03] = 0x04;//lib11 PackageGroup 编译时id:0x03------->运行时id:0x04

当然,还有

mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;//0x7f
            mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;//0x01
            mLookupTable[SYS_MTK_PACKAGE_ID] = SYS_MTK_PACKAGE_ID;//0x08

这些固定ID,由于是固定的,也就没啥好说的了。

step 7:

当加载完所有的资源,构造布局文件里的那个TextView的时候,就要解析这个android:textColor属性,并拿到它的值:

我们看到,它会去我们应用app对应的DynamicRefTable里查找:

它会先看一下数据的类型是不是DYNAMIC_REFERENCE,如果不是就没必要查找了,直接返回。我们的例子里,肯定             是的,所以它会走到lookupResourceId()方法。

step 8:

lookupResourceId()方法的代码上面已经贴出来了,这里就不重复了,只说一下具体流程:

它会取出这个资源id 0x0309078a所对应的包id:0x03,然后到mLookupTable里去查寻(注意,这里是根据索引,也就是下标的查寻,速度很快的哈),找到mLookupTable[0x03] = 0x04;,然后对这个资源的id做修正:

0x0309078a---------> 0x0409078a

完了把数据的类型改为静态引用,这个时候,它将会去0x04所对应的PackageGroup(也就是lib11)里找资源了!

到目前为止,ResTable那个小本本上的漏洞,是不是已经被补上了呢?费了老大劲,我们终于把这个翻车现场清理好了!

^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^

不过别高兴得太早,还记得我们之前提到的那两个疑问吗?0x02~0x07之间的这些包id给谁用,这个疑问已经解开,当然是给资源共享库用呀!可是另外一个疑问还记得吗?

我们在编译资源共享库的时候,顺便导出了它们的R.java文件,我们发现它们里面的资源id都不是final的,并且还多了一个onResourcesLoaded方法。这个方法还会去改变R.java文件中的id的包id,为什么呢,在什么时候调用呢?

为了解开这个谜团,我们回到frameworks/base/core/java/android/app/LoadedApk.java

它内部有这么一个方法:

public Application makeApplication(boolean forceDefaultAppClass,
                Instrumentation instrumentation) {

...........

SparseArray<String> packageIdentifiers = getAssets(mActivityThread).getAssignedPackageIdentifiers();
                final int N = packageIdentifiers.size();
                for (int i = 0; i < N; i++) {
                      final int id = packageIdentifiers.keyAt(i);
                      if (id == 0x01 || id == 0x7f) {
                            continue;
                      }

rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
                 }

...........

}

我们看到,在makeApplication的时候,它竟然去重写R文件里的资源了!这里稍做解释:getAsset方法拿到我们的AssetManager实例,getAssignedPackageIdentifiers()方法获取的是运行时各个PackageGroup对应的包id和包名的对应关系。我们要注意的是,这些R.java(确切地说是R.class)文件是在编译我们的库的时候就生成的,它的包id都是0x00,也就是说它里面的资源都是形如0x00tteeee的。

一目了然,根据包名,反射R文件,调用onResourcesLoaded方法,里面所有资源的id做一次修正,改为运行时的正确id。

这样我们也就可以在我们的应用app.apk的java代码里通过:

final int color = getResources.getColor(lib11.R.text_color_alert);

这种方式来引用共享库里的资源了!

我们来做个总结吧:

1.Android不仅支持so这样的共享库,也支持资源共享库。。

2.为了实现这套机制,Android从aapt、AssetManager以及Framework层面做了许多工作

3.特别是资源共享库编译时和运行时加载顺序不一致的问题,Android处理得相当Nice,通过Dynamic Reference,硬生生地把翻了的车重新翻了过来,然后跑得还挺溜!

OK,资源共享库和Dynamic Reference相关的问题就介绍到这里,由于水平有限,又是挤出业余时间匆匆写就,难免有不足甚至是错误,欢迎大犇批评指正!

另外,关于PackageGroup、Package、Type、Entry、Res_value这些概念大家不必过于在意,后面我们介绍AssetManager的时候会详细说。

关于包id,可能有些混乱,同一个包,我们要注意区分,编译时包id、运行时包id以及R.java中的包id,这三者有时候并不完全相同。

下期我们介绍更666666的Sony提出的 Runtime Resources Overlay也就是RRO机制,敬请期待。

Android资源管理中的SharedLibrary和Dynamic Reference-------之AssetManager的处理(四)相关推荐

  1. 【Android FFMPEG 开发】Android Studio 中 配置 FFMPEG 库最小兼容版本 ( undefined reference to 'atof' )

    文章目录 FFMPEG 最小兼容版本 注意事项 FFMPEG 最小兼容版本 注意事项 1 . 最小兼容版本 : 在 Ubuntu 中编译 FFMPEG 时 , 需要指定头文件 与 NDK 的依赖库 , ...

  2. android开发使用c+_如何在Android项目中开始使用C ++代码

    android开发使用c+ by Onur Tuna 通过Onur Tuna 如何在Android项目中开始使用C ++代码 (How to start using C++ code in your ...

  3. android开发过程中遇到的问题

    记录android开发过程中遇到的问题. 1.在一个xml中能否使用同一个include多次 http://www.apkbus.com/android-104152-1-1.html android ...

  4. 记录的Android开发过程中遇到的问题。

    180508  更新 网上下载demo 本地studio版本和demo版本不一致处理方式 修改两处 1 项目的build.gradle 里面classpath 2修改项目目录下 gradle--> ...

  5. Android开发中应避免的重大错误

    by Varun Barad 由Varun Barad Android开发中应避免的重大错误 (Critical mistakes to avoid in Android development) A ...

  6. android sqlite 中 创建表 不要使用 IF NOT EXISTS + TA...

    2019独角兽企业重金招聘Python工程师标准>>> android sqlite 中 创建表 不要使用 "IF NOT EXISTS " + TABLE_NA ...

  7. 5 个 Android 开发中比较常见的内存泄漏问题及解决办法

    Android开发中,内存泄漏是比较常见的问题,有过一些Android编程经历的童鞋应该都遇到过,但为什么会出现内存泄漏呢?内存泄漏又有什么影响呢? 在Android程序开发中,当一个对象已经不需要再 ...

  8. Android Studio中的代码格式快捷方式

    本文翻译自:Code formatting shortcut in Android Studio I have started developing with Android Studio . 我已经 ...

  9. Android 10 中有关限制非 SDK 接口的更新

    Android 10 中有关限制非 SDK 接口的更新 目录 浅灰和深灰列表的命名发生变化 非 SDK 接口的代码注释 在 Android 10 中授予对非 SDK 接口的访问权限 Android 1 ...

最新文章

  1. 图的连通性和连通分量_英语,人口,连通性和露营地
  2. js---PC端滑动进度条
  3. Centos进入紧急模式解决方法
  4. 中间人攻击-http流量嗅探
  5. Javascript基础之-强制类型转换(一)
  6. qt designer 插入图片_老同学春节祝福语图片
  7. 装linux服务器进去配置界面,在CentOS 8 Linux上安装和配置SuiteCRM的步骤
  8. leetCode 204. Count Primes 哈希 求素数
  9. MySQL索引结构--由 B-/B+树看
  10. ASP.NET MVC传送参数至服务端
  11. python数据抓取与实战_Python数据抓取技术与实战 pdf
  12. 攻防世界-music-高手进阶区-miscmisc
  13. 二叉树的层次遍历算法
  14. Android studio实现语音转文字功能
  15. oracle 创建新的表空间,oracle创建表空间新建新用户并受权
  16. 很特别的01背包讲解教程.....
  17. A股市场统计(营业收入增长率、净利润增长率及净利润比市值近十年的平均数、中位数)
  18. Nvidia GPU虚拟化
  19. 高数_证明_绝对收敛的级数也收敛
  20. vue中请求到的数据赋值给data 对象

热门文章

  1. 如何重新设置苹果id密码_苹果手机ID密码忘了?别着急,这二种方法轻松帮你搞定!...
  2. 3、以太坊智能合约开发(语法开发学习)
  3. wait-ify工作原理(学习笔记)
  4. java编写程序上机实验,《Java程序设计》上机实验
  5. Mac删除软件之后图标还在怎么办?
  6. java零基础学习第九天
  7. ArduPilot之遥控器数据读取
  8. 获取iOS设备唯一标识
  9. 金三银四,中高级测试面经,我不信你能看完!
  10. Qt之鼠标滑过控件由箭头变成手型