Skip to content

Commit 388e25f

Browse files
Em3rs0nEm3rs0n
Em3rs0n
authored and
Em3rs0n
committed
利用Trie结构将性能提升了10倍
借助新的并行计算工具构造了一个Trie结构,在保持APK Parser性能基本不变的情况下,将 自动适配表达式的性能提升了10倍。原先一个表达式求值需要耗费将近1秒,现在只需20ms不到。 旧的Benchmark记录 BM: Parse DexClasses takes 758 ms. BM: Search android.support.v4.app takes 747 ms. BM: Search com.tencent.mm.ui.contact takes 1239 ms. BM: Search com.tencent.mm.plugin.sns.ui takes 1393 ms. BM: Search com.tencent.mm takes 1635 ms. BM: Search com.tencent.mm.ui.conversation takes 761 ms. BM: Search com.tencent.mm.sdk.platformtools takes 860 ms. BM: Search com.tencent.mm.sdk.platformtools takes 669 ms. BM: Search com.tencent.mm.booter.notification takes 561 ms. BM: Search com.tencent.mm.storage takes 871 ms. BM: Search com.tencent.mm.booter.notification.queue takes 913 ms. BM: Search com.tencent.mm.ui takes 518 ms. BM: Search com.tencent.mm.modelsfs takes 626 ms. BM: Search com.tencent.mm.ui.chatting takes 470 ms. BM: Search over classes takes 4414 ms. 数据结构升级后的Benchmark记录 BM: Parse DexClasses takes 779 ms. BM: Search android.support.v4.app takes 42 ms. BM: Search com.tencent.mm takes 167 ms. BM: Search com.tencent.mm.sdk.platformtools takes 5 ms. BM: Search com.tencent.mm.booter.notification takes 1 ms. BM: Search com.tencent.mm.booter.notification.queue takes 0 ms. BM: Search com.tencent.mm.modelsfs takes 0 ms. BM: Search com.tencent.mm.plugin.sns.ui takes 66 ms. BM: Search com.tencent.mm.storage takes 1 ms. BM: Search com.tencent.mm.ui takes 2 ms. BM: Search com.tencent.mm.ui.chatting takes 24 ms. BM: Search com.tencent.mm.ui.contact takes 19 ms. BM: Search com.tencent.mm.ui.conversation takes 14 ms. BM: Search over classes takes 558 ms.
1 parent 7fcb0d7 commit 388e25f

File tree

5 files changed

+105
-36
lines changed

5 files changed

+105
-36
lines changed

src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/WechatGlobal.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.gh0u1l5.wechatmagician.spellbook.SpellBook.getApplicationVersion
66
import com.gh0u1l5.wechatmagician.spellbook.base.Version
77
import com.gh0u1l5.wechatmagician.spellbook.base.WaitChannel
88
import com.gh0u1l5.wechatmagician.spellbook.parser.ApkFile
9+
import com.gh0u1l5.wechatmagician.spellbook.parser.ClassTrie
910
import com.gh0u1l5.wechatmagician.spellbook.util.BasicUtil.tryAsynchronously
1011
import de.robv.android.xposed.callbacks.XC_LoadPackage
1112
import java.lang.ref.WeakReference
@@ -75,7 +76,7 @@ object WechatGlobal {
7576
* 这些类名使用的是 JVM 标准中规定的类名格式, 例如 String 的类名会被表示为 "Ljava/lang/String;"
7677
* Refer: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3
7778
*/
78-
@Volatile var wxClasses: Array<String>? = null
79+
@Volatile var wxClasses: ClassTrie? = null
7980
get() {
8081
if (!wxUnitTestMode) {
8182
initChannel.wait(INIT_TIMEOUT)

src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/parser/ApkFile.kt

+23-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.gh0u1l5.wechatmagician.spellbook.parser
22

3+
import com.gh0u1l5.wechatmagician.spellbook.util.ParallelUtil.parallelForEach
4+
35
import java.io.Closeable
46
import java.io.File
57
import java.nio.ByteBuffer
@@ -23,13 +25,27 @@ class ApkFile(apkFile: File) : Closeable {
2325
override fun close() =
2426
zipFile.close()
2527

26-
val classTypes: Array<String> by lazy {
27-
var ret = emptyArray<String>()
28-
for (i in 1 until 1000) {
29-
val path = if (i == 1) DEX_FILE else String.format(DEX_ADDITIONAL, i)
30-
val entry = zipFile.getEntry(path) ?: break
31-
val buffer = ByteBuffer.wrap(readEntry(entry))
32-
ret += DexParser(buffer).parseClassTypes()
28+
private fun getDexFilePath(idx: Int) =
29+
if (idx == 1) DEX_FILE else String.format(DEX_ADDITIONAL, idx)
30+
31+
private fun isDexFileExist(idx: Int): Boolean {
32+
val path = getDexFilePath(idx)
33+
return zipFile.getEntry(path) != null
34+
}
35+
36+
val classTypes: ClassTrie by lazy {
37+
var last = 2
38+
while (isDexFileExist(last)) last++
39+
40+
val ret = ClassTrie()
41+
(1..last).parallelForEach { idx ->
42+
val path = getDexFilePath(idx)
43+
val entry = zipFile.getEntry(path)
44+
val data = readEntry(entry)
45+
val buffer = ByteBuffer.wrap(data)
46+
DexParser(buffer).parseClassTypes().forEach { type ->
47+
ret += type
48+
}
3349
}
3450
return@lazy ret
3551
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.gh0u1l5.wechatmagician.spellbook.parser
2+
3+
import com.gh0u1l5.wechatmagician.spellbook.util.ParallelUtil.parallelForEach
4+
import java.util.concurrent.ConcurrentHashMap
5+
6+
class ClassTrie {
7+
companion object {
8+
private fun convertJVMTypeToClassName(type: String) =
9+
type.substring(1, type.length - 1).replace('/', '.')
10+
}
11+
12+
private val head: TrieNode = TrieNode()
13+
14+
operator fun plusAssign(type: String) {
15+
head.add(convertJVMTypeToClassName(type))
16+
}
17+
18+
operator fun plusAssign(types: Array<String>) {
19+
types.asList().parallelForEach { this += it }
20+
}
21+
22+
fun search(packageName: String, depth: Int): List<String> {
23+
return head.search(packageName, depth)
24+
}
25+
26+
private class TrieNode {
27+
val classes: MutableList<String> = ArrayList(50)
28+
29+
val children: MutableMap<String, TrieNode> = ConcurrentHashMap()
30+
31+
fun add(className: String) {
32+
add(className, 0)
33+
}
34+
35+
private fun add(className: String, pos: Int) {
36+
val delimiterAt = className.indexOf('.', pos)
37+
if (delimiterAt == -1) {
38+
synchronized(this) {
39+
classes.add(className)
40+
}
41+
return
42+
}
43+
val pkg = className.substring(pos, delimiterAt)
44+
if (pkg !in children) {
45+
children[pkg] = TrieNode()
46+
}
47+
children[pkg]!!.add(className, delimiterAt + 1)
48+
}
49+
50+
fun get(depth: Int = 0): List<String> {
51+
if (depth == 0) {
52+
return classes
53+
}
54+
return children.flatMap { it.value.get(depth - 1) }
55+
}
56+
57+
fun search(packageName: String, depth: Int): List<String> {
58+
return search(packageName, depth, 0)
59+
}
60+
61+
private fun search(packageName: String, depth: Int, pos: Int): List<String> {
62+
val delimiterAt = packageName.indexOf('.', pos)
63+
if (delimiterAt == -1) {
64+
val pkg = packageName.substring(pos)
65+
return children[pkg]?.get(depth) ?: emptyList()
66+
}
67+
val pkg = packageName.substring(pos, delimiterAt)
68+
return children[pkg]?.search(packageName, depth, delimiterAt + 1) ?: emptyList()
69+
}
70+
}
71+
}

src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/util/MirrorUtil.kt

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.gh0u1l5.wechatmagician.spellbook.util
22

33
import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal
4-
import com.gh0u1l5.wechatmagician.spellbook.util.ParallelUtil.parallelMap
54

65
/**
76
* 封装了一批用于检查“自动适配表达式”的函数
@@ -57,7 +56,7 @@ object MirrorUtil {
5756
* WARN: 仅供单元测试使用
5857
*/
5958
fun generateReportWithForceEval(instances: List<Any>): List<Pair<String, String>> {
60-
return instances.parallelMap { instance ->
59+
return instances.map { instance ->
6160
collectFields(instance).map {
6261
val value = it.second
6362
if (value is Lazy<*>) {
@@ -67,6 +66,6 @@ object MirrorUtil {
6766
}
6867
"${instance::class.java.canonicalName}.${it.first}" to it.second.toString()
6968
}
70-
}.flatten()
69+
}.flatten() // 为了 Benchmark 的准确性, 不对结果进行排序
7170
}
7271
}

src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/util/ReflectionUtil.kt

+7-25
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.gh0u1l5.wechatmagician.spellbook.util
33
import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal
44
import com.gh0u1l5.wechatmagician.spellbook.base.Classes
55
import com.gh0u1l5.wechatmagician.spellbook.parser.ApkFile
6+
import com.gh0u1l5.wechatmagician.spellbook.parser.ClassTrie
67
import de.robv.android.xposed.XC_MethodHook
78
import de.robv.android.xposed.XposedBridge.hookMethod
89
import java.lang.reflect.Field
@@ -66,40 +67,21 @@ object ReflectionUtil {
6667
* 里面其他包拥有的类.
6768
*
6869
* @param loader 用于取出 [Class] 对象的加载器
69-
* @param classes 所有已知的类名, 由于 Java 的 [ClassLoader] 对象不支持读取所有类名, 我们必须先通过其他手段
70-
* 获取一个类名列表, 详情请参见 [ApkFile] 和 [WechatGlobal]
70+
* @param trie 整个 APK 的包结构, 由于 Java 的 [ClassLoader] 对象不支持读取所有类名, 我们必须先通过其他手段
71+
* 解析 APK 结构, 然后才能检索某个包内的所有类, 详情请参见 [ApkFile] 和 [WechatGlobal]
7172
* @param packageName 包名
7273
* @param depth 深度
7374
*/
74-
@JvmStatic fun findClassesFromPackage(loader: ClassLoader, classes: Array<String>, packageName: String, depth: Int = 0): Classes {
75+
@JvmStatic fun findClassesFromPackage(loader: ClassLoader, trie: ClassTrie, packageName: String, depth: Int = 0): Classes {
7576
val key = "$depth-$packageName"
7677
val cached = classCache[key]
7778
if (cached != null) {
7879
return cached
7980
}
80-
81-
val packageLength = packageName.count { it == '.' } + 1
82-
val packageDescriptor = "L${packageName.replace('.', '/')}"
83-
val result = Classes(classes.filter { clazz ->
84-
val currentPackageLength = clazz.count { it == '/' }
85-
if (currentPackageLength < packageLength) {
86-
return@filter false
87-
}
88-
// Check depth
89-
val currentDepth = currentPackageLength - packageLength
90-
if (depth != -1 && depth != currentDepth) {
91-
return@filter false
92-
}
93-
// Check prefix
94-
if (!clazz.startsWith(packageDescriptor)) {
95-
return@filter false
96-
}
97-
return@filter true
98-
}.mapNotNull {
99-
findClassIfExists(it.substring(1, it.length - 1).replace('/', '.'), loader)
81+
val classes = Classes(trie.search(packageName, depth).mapNotNull { name ->
82+
findClassIfExists(name, loader)
10083
})
101-
102-
return result.also { classCache[key] = result }
84+
return classes.also { classCache[key] = classes }
10385
}
10486

10587
/**

0 commit comments

Comments
 (0)