_StriveG Blog

系统资源的预加载过程

前言

在Zygote进程那篇文章中,提到过,在初始化的时候会预加载系统资源,这样,应用进程在fork了之后,不需要通过加载过程,就可以直接使用这些资源,那么,今天就来看下,这个过程是怎么样的。

ZygoteInit#preloadResources

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private static void preloadResources() {
final VMRuntime runtime = VMRuntime.getRuntime();
try {
mResources = Resources.getSystem();
mResources.startPreloading();
if (PRELOAD_RESOURCES) {
Log.i(TAG, "Preloading resources...");
long startTime = SystemClock.uptimeMillis();
TypedArray ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_drawables);
int N = preloadDrawables(runtime, ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
startTime = SystemClock.uptimeMillis();
ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_color_state_lists);
N = preloadColorStateLists(runtime, ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
}
mResources.finishPreloading();
} catch (RuntimeException e) {
Log.w(TAG, "Failure preloading resources", e);
}
}

上面的过程分为三步

  • 得到Resources对象
  • 得到TypedArray对象
  • preloadDrawables,preloadColorStateLists 加载资源

现在,按照上面的三部分来学习下。

Resources#getSystem

Resources代码资源,提供了许多方法让我们获取,这里获取的是经过映射的资源,resources.arsc。

1
2
3
4
5
6
7
8
9
10
11
public static Resources getSystem() {
synchronized (sSync) {
Resources ret = mSystem;
if (ret == null) {
ret = new Resources();
mSystem = ret;
}
return ret;
}
}

在这里初始化了一个Resources对象,并且赋值给mSystem变量,那么我们现在看下Resources的构造方法。

1
2
3
4
5
6
7
8
9
10
private Resources() {
mAssets = AssetManager.getSystem();
// NOTE: Intentionally leaving this uninitialized (all values set
// to zero), so that anyone who tries to do something that requires
// metrics will get a very wrong value.
mConfiguration.setToDefaults();
mMetrics.setToDefaults();
updateConfiguration(null, null);
mAssets.ensureStringBlocks();
}

在初始化方法中,通过AssetManager.getSystem获取一个AssetManager对象,这个是用来访问原始资源的(assets目录)。并且,将Configuration和DisplayMetrics都设置默认,更新配置,初始化StringBlocks。那么,我们来看下AssetManager.getSystem。这个方法中调用ensureSystemAssets,ensureSystemAssets代码如下:

1
2
3
4
5
6
7
8
9
private static void ensureSystemAssets() {
synchronized (sSync) {
if (sSystem == null) {
AssetManager system = new AssetManager(true);
system.makeStringBlocks(null);
sSystem = system;
}
}
}

可以看到,这里初始化了一个AssetManager,好吧,接着看AssetManager的构造函数。

1
2
3
4
5
6
7
8
9
10
private AssetManager(boolean isSystem) {
if (DEBUG_REFS) {
synchronized (this) {
mNumRefs = 0;
incRefsLocked(this.hashCode());
}
}
init(true);
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
}

这里调用init去初始化,这是个native函数,实现在android_util_AssetManager.cpp中,对应的方法为android_content_AssetManager_init

android_content_AssetManager_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
if (isSystem) {
verifySystemIdmaps();
}
AssetManager* am = new AssetManager();
if (am == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", "");
return;
}
am->addDefaultAssets();
ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}

从代码中来看,分为四步

  • 验证idmaps
  • 生成c++的AssetManager对象
  • 添加默认的assets
  • 设置java层AssetManager的mObject为c++的AssetManager地址。

我们看添加默认的assets那一步。

1
2
3
4
5
6
7
8
9
10
bool AssetManager::addDefaultAssets()
{
const char* root = getenv("ANDROID_ROOT");
LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
String8 path(root);
path.appendPath(kSystemAssets);
return addAssetPath(path, NULL);
}
  • ANDROID_ROOT为system
  • static const char* kSystemAssets = “framework/framework-res.apk”;

然后调用addAssetPath两个参数的方法,将framework-res.apk加进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
AutoMutex _l(mLock);
asset_path ap;
String8 realPath(path);
if (kAppZipName) {
realPath.appendPath(kAppZipName);
}
ap.type = ::getFileType(realPath.string());
if (ap.type == kFileTypeRegular) {
ap.path = realPath;
} else {
ap.path = path;
ap.type = ::getFileType(path.string());
if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
ALOGW("Asset path %s is neither a directory nor file (type=%d).",
path.string(), (int)ap.type);
return false;
}
}
// Skip if we have it already.
for (size_t i=0; i<mAssetPaths.size(); i++) {
if (mAssetPaths[i].path == ap.path) {
if (cookie) {
*cookie = static_cast<int32_t>(i+1);
}
return true;
}
}
ALOGV("In %p Asset %s path: %s", this,
ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
// Check that the path has an AndroidManifest.xml
Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
kAndroidManifest, Asset::ACCESS_BUFFER, ap);
if (manifestAsset == NULL) {
// This asset path does not contain any resources.
delete manifestAsset;
return false;
}
delete manifestAsset;
mAssetPaths.add(ap);
// new paths are always added at the end
if (cookie) {
*cookie = static_cast<int32_t>(mAssetPaths.size());
}
#ifdef HAVE_ANDROID_OS
// Load overlays, if any
asset_path oap;
for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
mAssetPaths.add(oap);
}
#endif
if (mResources != NULL) {
appendPathToResTable(ap);
}
return true;
}
  • asset_path结构体存储文件路径、文件类型等,结构体定义如下

    1
    2
    3
    4
    5
    6
    7
    8
    struct 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
bool AssetManager::appendPathToResTable(const asset_path& ap) const {
// skip those ap's that correspond to system overlays
if (ap.isSystemOverlay) {
return true;
}
Asset* ass = NULL;
ResTable* sharedRes = NULL;
bool shared = true;
bool onlyEmptyResources = true;
MY_TRACE_BEGIN(ap.path.string());
Asset* idmap = openIdmapLocked(ap);
size_t nextEntryIdx = mResources->getTableCount();
ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
if (ap.type != kFileTypeDirectory) {
if (nextEntryIdx == 0) {
// The first item is typically the framework resources,
// which we want to avoid parsing every time.
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTable(ap.path);
if (sharedRes != NULL) {
// skip ahead the number of system overlay packages preloaded
nextEntryIdx = sharedRes->getTableCount();
}
}
if (sharedRes == NULL) {
ass = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTableAsset(ap.path);
if (ass == NULL) {
ALOGV("loading resource table %s\n", ap.path.string());
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
if (ass != NULL && ass != kExcludedAsset) {
ass = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTableAsset(ap.path, ass);
}
}
if (nextEntryIdx == 0 && ass != NULL) {
// If this is the first resource table in the asset
// manager, then we are going to cache it so that we
// can quickly copy it out for others.
ALOGV("Creating shared resources for %s", ap.path.string());
sharedRes = new ResTable();
sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
#ifdef HAVE_ANDROID_OS
const char* data = getenv("ANDROID_DATA");
LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
String8 overlaysListPath(data);
overlaysListPath.appendPath(kResourceCache);
overlaysListPath.appendPath("overlays.list");
addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
#endif
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTable(ap.path, sharedRes);
}
}
} else {
ALOGV("loading resource table %s\n", ap.path.string());
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
shared = false;
}
if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
if (sharedRes != NULL) {
ALOGV("Copying existing resources for %s", ap.path.string());
mResources->add(sharedRes);
} else {
ALOGV("Parsing resources for %s", ap.path.string());
mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
}
onlyEmptyResources = false;
if (!shared) {
delete ass;
}
} else {
ALOGV("Installing empty resources in to table %p\n", mResources);
mResources->addEmpty(nextEntryIdx + 1);
}
if (idmap != NULL) {
delete idmap;
}
MY_TRACE_END();
return onlyEmptyResources;
}
  • 通过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

1
2
TypedArray ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_drawables);

mResources.obtainTypedArray的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public TypedArray obtainTypedArray(@ArrayRes int id)
throws NotFoundException {
int len = mAssets.getArraySize(id);
if (len < 0) {
throw new NotFoundException("Array resource ID #0x"
+ Integer.toHexString(id));
}
TypedArray array = TypedArray.obtain(this, len);
array.mLength = mAssets.retrieveArray(id, array.mData);
array.mIndices[0] = 0;
return array;
}
  • 先获取id对应的数组长度
  • 然后根据长度生成TypedArray
  • 最后mAssets.retrieveArray赋值,将资源对应的id写入到java层array.mData中。写入部分代码如下

    1
    2
    3
    4
    5
    6
    7
    dest[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方法中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz,
jint id)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
const ResTable& res(am->getResources());
res.lock();
const ResTable::bag_entry* defStyleEnt = NULL;
ssize_t bagOff = res.getBagLocked(id, &defStyleEnt);
res.unlock();
return static_cast<jint>(bagOff);
}
  • 首先将java对象转换为对应的c++对象,上面有说到过,AssetManager中的mobject字段存的是对应c++中,AssetManager对象的地址,因此,我们很容易做到转换
  • 通过ResTable的getBagLocked方法获取id,对应的数组长度(该方法的实现在ResourceTypes.cpp中),实现太长,看不懂,略过。

preloadDrawables来说明资源的预加载过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static int preloadDrawables(VMRuntime runtime, TypedArray ar) {
int N = ar.length();
for (int i=0; i<N; i++) {
int id = ar.getResourceId(i, 0);
if (false) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
if (mResources.getDrawable(id, null) == null) {
throw new IllegalArgumentException(
"Unable to find preloaded drawable resource #0x"
+ Integer.toHexString(id)
+ " (" + ar.getString(i) + ")");
}
}
}
return N;
}
  • 首先通过getResourceId获取资源id
  • getDrawable获取资源

getResourceId 获取资源id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int getResourceId(int index, int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) {
final int resid = data[index+AssetManager.STYLE_RESOURCE_ID];
if (resid != 0) {
return resid;
}
}
return defValue;
}
  • 其中STYLE_NUM_ENTRIES为6,
  • 从赋值id的代码中,知道第四个为id值,因此STYLE_RESOURCE_ID为3

getDrawable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
if (value == null) {
value = new TypedValue();
} else {
mTmpValue = null;
}
getValue(id, value, true);
}
final Drawable res = loadDrawable(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
return res;
}
  • getValue查找与id对应的资源,没找到对应的就抛出NotFoundException异常
  • loadDrawable 去加载

在getValue中,会调用AssetManager的getResourceValue方法,这个是native方法,实现在android_uitl_AssetManager.cpp的android_content_AssetManager_loadResourceValue中

android_content_AssetManager_loadResourceValue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
jint ident,
jshort density,
jobject outValue,
jboolean resolve)
{
if (outValue == NULL) {
jniThrowNullPointerException(env, "outValue");
return 0;
}
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
const ResTable& res(am->getResources());
Res_value value;
ResTable_config config;
uint32_t typeSpecFlags;
ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
if (kThrowOnBadId) {
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return 0;
}
}
uint32_t ref = ident;
if (resolve) {
block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
if (kThrowOnBadId) {
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return 0;
}
}
}
if (block >= 0) {
return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);
}
return static_cast<jint>(block);
}
  • 得到ResTable对象
  • resolveReference查找是否有匹配的资源
  • 如果有,就将数据复制给copyValue这个java层对象

具体的代码就不往下追了。

loadDrawable

经过上面的步骤,资源的信息就保存在了TypedValue中,解析来就是通过loadDrawable去加载了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
if ((id >>> 24) == 0x1) {
final String name = getResourceName(id);
if (name != null) {
Log.d("PreloadDrawable", name);
}
}
}
final boolean isColorDrawable;
final DrawableCache caches;
final long key;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
caches = mColorDrawableCache;
key = value.data;
} else {
isColorDrawable = false;
caches = mDrawableCache;
key = (((long) value.assetCookie) << 32) | value.data;
}
// First, check whether we have a cached version of this drawable
// that was inflated against the specified theme.
if (!mPreloading) {
final Drawable cachedDrawable = caches.getInstance(key, theme);
if (cachedDrawable != null) {
return cachedDrawable;
}
}
// Next, check preloaded drawables. These may contain unresolved theme
// attributes.
final ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
if (cs != null) {
dr = cs.newDrawable(this);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
dr = loadDrawableForCookie(value, id, null);
}
// Determine if the drawable has unresolved theme attributes. If it
// does, we'll need to apply a theme and store it in a theme-specific
// cache.
final boolean canApplyTheme = dr != null && dr.canApplyTheme();
if (canApplyTheme && theme != null) {
dr = dr.mutate();
dr.applyTheme(theme);
dr.clearMutated();
}
// If we were able to obtain a drawable, store it in the appropriate
// cache: preload, not themed, null theme, or theme-specific.
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
}
return dr;
}
  • 根据判断是否是ColorDrawable,赋值不同的cache和key
  • 如果不是预加载,就从cache中找,找到返回
  • 从预加载数组中得到key对应的ConstantState
  • 根据cs是否为null以及是不是ColorDrawable,来得到的Drawable
  • 设置主题相关的
  • 进行缓存

而我们预加载资源的主要过程是loadDrawableForCookie。这个方法是从xml或者流里面加载Drawable。

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(this, rp, theme);
rp.close();
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
dr = Drawable.createFromResourceStream(this, value, is, file, null);
is.close();
}

关于Drawable的生成过程,这里就不说了。

占坑

  • aapt资源打包过程
  • 其他

最近访客