[有误!!!随后更正]
在程序运行的过程当中,可以使用PathClassLoader与DexClassLoader等加载外部的jar包或apk包,并使用反射来获取到包内的类,就可以实现程序运行时动态扩展的功能。
但如果在已经加载完一个外部包后,需要对包进行更新,且程序此时不能重启,能否简单地处理呢?
开始的时候我想既然类+加载器才可以确定一个类,那如果在加载完包并更新完包后再使用DexClassLoader,使用再使用新的类加载器去加载包里的类,那么理应来说是可以加载到新的包里面的类的。然后事实上并不成功。针对这个问题翻看了一下源码,这里作个记录。
找到类加载的代码,这里使用Class.cpp中的findClassNoInit作为例子。
1 | static ClassObject* findClassNoInit(const char* descriptor, Object* loader, |
在进入findClassNoInit后首先虚拟机会先去查找一下是否已经加载过这个全限定名(descriptor)的类(比如说,有一个cn.itrunner.test的一个类,全限定名就是Lcn/itrunner/test。其中的转换方法在 dalvik/vm/Misc.cpp::dvmDotToDescriptor),检查的方法为dvmLookupClass,源码及注释如下。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22dalvik/vm/oo/Class.cpp
ClassObject* dvmLookupClass(const char* descriptor, Object* loader,
bool unprepOkay)
{
ClassMatchCriteria crit;
void* found;
u4 hash;
crit.descriptor = descriptor; //类的全限定名
crit.loader = loader; 加载时传入的类加载器
hash = dvmComputeUtf8Hash(descriptor); //使用全限定名计算hash值
dvmHashTableLock(gDvm.loadedClasses); //锁定hash表
found = dvmHashTableLookup(gDvm.loadedClasses, hash, &crit,
hashcmpClassByCrit, false); //在hash表中查找是否在相同hash值的对象,有就返回
dvmHashTableUnlock(gDvm.loadedClasses); //释放hash表
......
return (ClassObject*) found;
}
如上可知,相同的全限定名会得到一个相同的hash值。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
74void* dvmHashTableLookup(HashTable* pHashTable, u4 itemHash, void* item,
HashCompareFunc cmpFunc, bool doAdd)
{
HashEntry* pEntry;
HashEntry* pEnd;
void* result = NULL;
assert(pHashTable->tableSize > 0);
assert(item != HASH_TOMBSTONE);
assert(item != NULL);
/* jump to the first entry and probe for a match */
pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)]; //初始key为hash值与hash表大小的与值
pEnd = &pHashTable->pEntries[pHashTable->tableSize]; //指向hash表底
while (pEntry->data != NULL) { //pEntry->data指向的是一个ClassMatchCriteria类型,里面是类的全限定名及其类加载器
// 不断地找出合适的ClassMatchCriteria对象,假如已经缓存过的话
if (pEntry->data != HASH_TOMBSTONE &&
pEntry->hashValue == itemHash &&
(*cmpFunc)(pEntry->data, item) == 0)
{
/* match */
//ALOGD("+++ match on entry %d", pEntry - pHashTable->pEntries);
break;
}
pEntry++;
if (pEntry == pEnd) { /* wrap around to start */
if (pHashTable->tableSize == 1)
break; /* edge case - single-entry table */
pEntry = pHashTable->pEntries;
}
//ALOGI("+++ look probing %d...", pEntry - pHashTable->pEntries);
}
if (pEntry->data == NULL) {
//找不到的时候就需要把新加载的类添加到hash表中
if (doAdd) {
pEntry->hashValue = itemHash;
pEntry->data = item;
pHashTable->numEntries++;
/*
* We've added an entry. See if this brings us too close to full.
*/
if ((pHashTable->numEntries+pHashTable->numDeadEntries) * LOAD_DENOM
> pHashTable->tableSize * LOAD_NUMER)
{
if (!resizeHash(pHashTable, pHashTable->tableSize * 2)) {
/* don't really have a way to indicate failure */
ALOGE("Dalvik hash resize failure");
dvmAbort();
}
/* note "pEntry" is now invalid */
} else {
//ALOGW("okay %d/%d/%d",
// pHashTable->numEntries, pHashTable->tableSize,
// (pHashTable->tableSize * LOAD_NUMER) / LOAD_DENOM);
}
/* full table is bad -- search for nonexistent never halts */
assert(pHashTable->numEntries < pHashTable->tableSize);
result = item;
} else {
assert(result == NULL);
}
} else {
// 否则就直接把缓存的数据返回就可以了
result = pEntry->data;
}
return result;
}
dvmHashTableLookup会不断地从现有的hash表中找出hash值与参数相等的且pEntry->data不为空的,假如可以在hash表中找到合适的,那么就会直接把对应的pEntry->data数据返回,这个其实就是ClassMatchCriteria类型的一个对象,里面包含了类的全限定名与类加载器引用,这就是为什么第二次加载的时候还是会加载到旧的类,且换已经替换了也还能找到对应的类,因为类已经在旧的加载器中加载过了,所以已经不需要再从dex文件中再次读二进制来构建ClassObject,且类加载器指向的也是旧的引用,即使在java层用了新的类加载器,但实际上用了还是旧的类加载器,如此,加载不到新类也就能理解了。
在findClassNoInit函数中,假如从hash表中找不到对应的类记录,则会直接从dex文件中生成一个classObject,然后通过dvmAddClassToHash把class的信息添加到hash表中(添加的方法也是调用dvmHashTableLookup执行的)。
关键就在于pHashTable里面,这个是由虚拟机刚开始时创建好的, 初始长度为256,从上面的函数可知,长度不够会自动resize。1
2
3
4
5
6
7
8
9
10
11// dalvik/vm/oo/Class.cpp
bool dvmClassStartup()
{
......
gDvm.loadedClasses =
dvmHashTableCreate(256, (HashFreeFunc) dvmFreeClassInnards);
......
}
要解决此问题,在Dalvik虚拟机中,可以使用dlfcn把libdvm.so动态把gDvm拿出来,然后就可以对HashTable进行操作。当然,目前仅止于理论,还未实践,且ART模式下也未进行分析,很可能与dalvik是不一样的,不通用最为致命。1
2void *handle=dlopen("libdvm.so",0);
DvmGlobals *p_gDvm=(DvmGlobals*)dlsym(handle,"gDvm");