前言
在Zygote进程那篇文章中,提到过,在初始化的时候会预加载系统资源,这样,应用进程在fork了之后,不需要通过加载过程,就可以直接使用这些资源,那么,今天就来看下,这个过程是怎么样的。
ZygoteInit#preloadResources
|
|
上面的过程分为三步
- 得到Resources对象
- 得到TypedArray对象
- preloadDrawables,preloadColorStateLists 加载资源
现在,按照上面的三部分来学习下。
Resources#getSystem
Resources代码资源,提供了许多方法让我们获取,这里获取的是经过映射的资源,resources.arsc。
|
|
在这里初始化了一个Resources对象,并且赋值给mSystem变量,那么我们现在看下Resources的构造方法。
|
|
在初始化方法中,通过AssetManager.getSystem获取一个AssetManager对象,这个是用来访问原始资源的(assets目录)。并且,将Configuration和DisplayMetrics都设置默认,更新配置,初始化StringBlocks。那么,我们来看下AssetManager.getSystem。这个方法中调用ensureSystemAssets,ensureSystemAssets代码如下:
|
|
可以看到,这里初始化了一个AssetManager,好吧,接着看AssetManager的构造函数。
|
|
这里调用init去初始化,这是个native函数,实现在android_util_AssetManager.cpp中,对应的方法为android_content_AssetManager_init
android_content_AssetManager_init
|
|
从代码中来看,分为四步
- 验证idmaps
- 生成c++的AssetManager对象
- 添加默认的assets
- 设置java层AssetManager的mObject为c++的AssetManager地址。
我们看添加默认的assets那一步。
|
|
- ANDROID_ROOT为system
- static const char* kSystemAssets = “framework/framework-res.apk”;
然后调用addAssetPath两个参数的方法,将framework-res.apk加进去。
|
|
asset_path结构体存储文件路径、文件类型等,结构体定义如下
12345678struct asset_path{asset_path() : path(""), type(kFileTypeRegular), idmap(""), isSystemOverlay(false) {}String8 path;FileType type;String8 idmap;bool isSystemOverlay;};kAppZipName一般为null,static const char* kAppZipName = NULL; //“classes.jar”;
- 如果已经在mAssetPaths,就反悔
- 如果路径下没有AndroidManifest.xml文件,返回false
- 添加到mAssetPaths中
- appendPathToResTable将资源进行解析添加
AssetManager::appendPathToResTable
|
|
- 通过openIdmapLocked解析资源包中的idmap表
- ap.type != kFileTypeDirectory,不是目录,就是资源包,如果nextEntryIdx为0,则为framework.apk资源包,解析并保存在sharedRes中,这个是共享的资源。
- sharedRes == NULL 表示为应用资源包,这解析保存在ass中,
- nextEntryIdx == 0 && ass != NULL,在zygote进程第一次调用才成立,也就是预加载资源,这个时候把framework.apk的资源索引表创建起来,并添加到mZipSet中
- 如果是目录,就创建resources.arsc的asset对象,这个不是共享的。
- 如果存在sharedRes,就保存到mResources中,否则,这是应用的资源,则把应用的资源加入到mResources中。
上面的比较乱,总结一下。简单来说就是这样的,就是对资源包进行解析,并加入到mResources中,如果是framework.apk,就是共享的,否则就是应用的资源包,不共享。
TypedArray
|
|
mResources.obtainTypedArray的代码如下:
|
|
- 先获取id对应的数组长度
- 然后根据长度生成TypedArray
最后mAssets.retrieveArray赋值,将资源对应的id写入到java层array.mData中。写入部分代码如下
1234567dest[STYLE_TYPE] = value.dataType;dest[STYLE_DATA] = value.data;dest[STYLE_ASSET_COOKIE] = block != kXmlBlock ?static_cast<jint>(res.getTableCookie(block)) : -1;dest[STYLE_RESOURCE_ID] = resid;dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;dest[STYLE_DENSITY] = config.density;
getArraySize获取资源id对应的数组长度
这是个native方法,对应的实现在android_util_AssetManager.cpp的android_content_AssetManager_getArraySize方法中,代码如下:
|
|
- 首先将java对象转换为对应的c++对象,上面有说到过,AssetManager中的mobject字段存的是对应c++中,AssetManager对象的地址,因此,我们很容易做到转换
- 通过ResTable的getBagLocked方法获取id,对应的数组长度(该方法的实现在ResourceTypes.cpp中),实现太长,看不懂,略过。
preloadDrawables来说明资源的预加载过程
|
|
- 首先通过getResourceId获取资源id
- getDrawable获取资源
getResourceId 获取资源id
|
|
- 其中STYLE_NUM_ENTRIES为6,
- 从赋值id的代码中,知道第四个为id值,因此STYLE_RESOURCE_ID为3
getDrawable
|
|
- getValue查找与id对应的资源,没找到对应的就抛出NotFoundException异常
- loadDrawable 去加载
在getValue中,会调用AssetManager的getResourceValue方法,这个是native方法,实现在android_uitl_AssetManager.cpp的android_content_AssetManager_loadResourceValue中
android_content_AssetManager_loadResourceValue
|
|
- 得到ResTable对象
- resolveReference查找是否有匹配的资源
- 如果有,就将数据复制给copyValue这个java层对象
具体的代码就不往下追了。
loadDrawable
经过上面的步骤,资源的信息就保存在了TypedValue中,解析来就是通过loadDrawable去加载了。
|
|
- 根据判断是否是ColorDrawable,赋值不同的cache和key
- 如果不是预加载,就从cache中找,找到返回
- 从预加载数组中得到key对应的ConstantState
- 根据cs是否为null以及是不是ColorDrawable,来得到的Drawable
- 设置主题相关的
- 进行缓存
而我们预加载资源的主要过程是loadDrawableForCookie。这个方法是从xml或者流里面加载Drawable。
核心代码如下:
|
|
关于Drawable的生成过程,这里就不说了。
占坑
- aapt资源打包过程
- 其他