Android更新资源文件浅思考

前言

最近在看 《深入探索Android热修复技术原理7.3Q.pdf》 时,遇到一个之前没有注意过的问题:关于资源修更新的Android的版本兼容?作为程序员我们需要非常严谨的思路,是什么导致了资源的修复更新需要做版本兼容?

这个问题是使我写下这边文章的原因,下边我们带着问题来找答案~!~!~!

这个问题的解释网上答案比较少,在滴滴的插件化框架相关文章 VirtualAPK 资源篇阿里云移动热修复(Sophix) 相关文章
Android热修复升级探索——资源更新之新思路 中 都有一句概括性质的话语:

AndroidL之后资源在初始化之后可以加载,而在AndroidL之前是不可以的。因为在Android KK及以下版本,addAssetPath只是把补丁包的路径添加到了mAssetPath中,而真正解析的资源包的逻辑是在app第一次执行AssetManager::getResTable的时候。

FTSC

为了比较完整的对前面提出的问题做解答,下边我在老罗写的 Android应用程序资源管理器(Asset Manager)的创建过程分析 这篇文章的基础上分析。

跟踪getResourceText

我们都知道在Android中获取资源调用的 Resources.getText(int id) 内部都是在调用 AssetManager.getResourceText(id) 真正对资源进行管理的是 AssetManager

下边我们就以 getResourceText 方法的调用顺序引子查找:

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
public final class AssetManager {
......
/*package*/ static AssetManager sSystem = null;
private native final void init(boolean isSystem);
......
//构造方法
public AssetManager() {
synchronized (this) {
......
init(false);
......
//每个AssetManager实例都会初始化系统的资源
ensureSystemAssets();
}
}
private static void ensureSystemAssets() {
synchronized (sSync) {
if (sSystem == null) {
AssetManager system = new AssetManager(true);
system.makeStringBlocks(false);
sSystem = system;
}
}
}
......
//添加资源路径
public final int addAssetPath(String path) {
synchronized (this) {
int res = addAssetPathNative(path);
if (mStringBlocks != null) {
makeStringBlocks(mStringBlocks);
}
return res;
}
}
private native final int addAssetPathNative(String path);

/*package*/ final CharSequence getResourceText(int ident) {
synchronized (this) {
TypedValue tmpValue = mValue;
int block = loadResourceValue(ident, (short) 0, tmpValue, true);
if (block >= 0) {
if (tmpValue.type == TypedValue.TYPE_STRING) {
return mStringBlocks[block].get(tmpValue.data);
}
return tmpValue.coerceToString();
}
}
return null;
}
//查找并加载资源
private native final int loadResourceValue(int ident, short density, TypedValue outValue,
boolean resolve);
}

AssetManager.init方法的C层实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//frameworks/base/core/jni/android_util_AssetManager.cpp
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
if (isSystem) {
verifySystemIdmaps();
}
//构造C++层的AssetManager的对象
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));
}

AssetManager.loadResourceValue方法的C层实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//frameworks/base/core/jni/android_util_AssetManager.cpp
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz, jint ident, jshort density, jobject outValue, jboolean resolve)
{
/***部分代码省略***/
//这行代码最重要,通过获取C层的AssetManager的成员变量ResTable来获取资源
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 (block >= 0) {
return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);
}
return static_cast<jint>(block);
}
//getResources实际获取的是ResTable
const ResTable& AssetManager::getResources(bool required) const
{
const ResTable* rt = getResTable(required);
return *rt;
}

我们可以看到获取资源实际是在操作 C层的AssetManager的成员变量ResTable 。如果没有将资源加入到 ResTable 那么是无法获取到的。下边我们分别看看AndroidL和AndroidL之前 addAssetPath 方法的实现。

ResTable的构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//frameworks/base/libs/androidfw/AssetManager.cpp
const ResTable* AssetManager::getResTable(bool required) const
{
ResTable* rt = mResources;
if (rt) {
return rt;
}
/***部分代码省略***/
const size_t N = mAssetPaths.size();
for (size_t i=0; i<N; i++) {
//遍历mAssetPaths将其ResTable中
/***部分代码省略***/
}
/***部分代码省略***/
return rt;
}

如果已经创建那么直接返回,如果没有那么将 mAssetPaths 集合中的资源路径全部添加到 ResTable 中后返回。下边我们继续看看资源的路径是怎么被插入到 mAssetPaths 中的,或者是怎么被直接插入到 ResTable 中的。

addAssetPath的差异

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
//frameworks/base/libs/androidfw/AssetManager.cpp
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);
String8 pathCM(root);
pathCM.appendPath(kCMSDKAssets);
return addAssetPath(path, NULL) & addAssetPath(pathCM, NULL);
}


bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
AutoMutex _l(mLock);
asset_path ap;
String8 realPath(path);
if (kAppZipName) {
realPath.appendPath(kAppZipName);
}
/***部分代码省略***/
//将path添加到mAssetPaths中
mAssetPaths.add(ap);
if (mResources != NULL) {
size_t index = mAssetPaths.size() - 1;
//添加到ResTable中
appendPathToResTable(ap, &index);
}
// new paths are always added at the end
if (cookie) {
*cookie = static_cast<int32_t>(mAssetPaths.size());
}
/***部分代码省略***/
return true;
}

以上是Android5.1的源码,我们发现无论是否初始化过 ResTable 我们都可以直接调用 addAssetPath 是可以添加资源。

下边我们看看Android4.4的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::addAssetPath(const String8& path, void** cookie)
{
AutoMutex _l(mLock);
asset_path ap;
String8 realPath(path);
if (kAppZipName) {
realPath.appendPath(kAppZipName);
}
/***部分代码省略***/
//将path添加到mAssetPaths中
mAssetPaths.add(ap);
// new paths are always added at the end
if (cookie) {
*cookie = (void*)mAssetPaths.size();
}
/***部分代码省略***/
return true;
}

我们发现 addAssetPath 只是将 path 添加到了 mAssetPaths 里面,但是并没法添加到 ResTable 中。
如果AndroidL之前调用 addAssetPath 没有初始化 ResTable 那么这次添加就是有效的,否则添加无效。下边我们接着看看 ResTable 的初始化时机。

ResTable的初始化时机

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
public final class AssetManager {
/***部分代码省略***/
//构造方法
public AssetManager() {
synchronized (this) {
......
init(false);
......
//每个AssetManager实例都会初始化系统的资源
ensureSystemAssets();
}
}
private static void ensureSystemAssets() {
synchronized (sSync) {
if (sSystem == null) {
AssetManager system = new AssetManager(true);
//初始化系统的字符串块
system.makeStringBlocks(false);
sSystem = system;
}
}
}
/*package*/ final void makeStringBlocks(StringBlock[] seed) {
final int seedNum = (seed != null) ? seed.length : 0;
final int num = getStringBlockCount();
mStringBlocks = new StringBlock[num];
if (localLOGV) Log.v(TAG, "Making string blocks for " + this
+ ": " + num);
for (int i=0; i<num; i++) {
if (i < seedNum) {
mStringBlocks[i] = seed[i];
} else {
mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
}
}
}
private native final int getStringBlockCount();
/***部分代码省略***/
}

避免大家往上翻看,我又将 AssetManager 的沟通方法摘出来放大家看看。我们在初始化系统的资源时调用了 AssetManagermakeStringBlocks 方法,最后调用了 C层getStringBlockCount 方法。

AssetManager.getStringBlockCount的C层的实现:

1
2
3
4
5
6
7
8
9
10
//frameworks/base/core/jni/android_util_AssetManager.cpp
static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
//初次调用资源,进行初始化ResTable
return am->getResources().getTableCount();
}

好了,我们找到了 ResTable 的时机,它是发生在java层的 AssetManager 构造的时候。

结论

  • Android中进行资源管理的是 AssetManager
  • 资源由C层 AssetManagerResTable 提供;
  • ResTable 构造是遍历 mAssetPaths 中的资源路径;
  • AndroidL addAssetPath 方法可以直接将资源路添加到 ResTable 中使用;
  • AndroidL之前 addAssetPath 方法只是将资源路径添加到了mAssetPaths 中;
  • ResTable 构造包含在Java层 AssetManager 的构造中的;

文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦~!~!

想阅读作者的更多文章,可以查看我 个人博客 和公共号:

>振兴书城
文章目录
  1. 1. 前言
  2. 2. FTSC
    1. 2.1. 跟踪getResourceText
    2. 2.2. ResTable的构造
    3. 2.3. addAssetPath的差异
    4. 2.4. ResTable的初始化时机
  3. 3. 结论
,