Skip to content

Commit 5bf7804

Browse files
Em3rs0nEm3rs0n
Em3rs0n
authored and
Em3rs0n
committed
将APK解析时间优化到原先的三分之一
重写apk parser,移除了大量不必要的计算,将~70M的APK解析时间从1500~1700ms降低到大约500ms。
1 parent 6613c8e commit 5bf7804

File tree

7 files changed

+306
-19
lines changed

7 files changed

+306
-19
lines changed

build.gradle

-6
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,6 @@ apply plugin: 'com.android.library'
1919
apply plugin: 'kotlin-android'
2020
apply plugin: 'org.jetbrains.dokka-android'
2121

22-
kotlin {
23-
experimental {
24-
coroutines "enable"
25-
}
26-
}
27-
2822
android {
2923
compileSdkVersion 28
3024
defaultConfig {

src/androidTest/kotlin/com/gh0u1l5/wechatmagician/spellbook/MirrorUnitTest.kt

+27-9
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@ import com.gh0u1l5.wechatmagician.spellbook.base.Version
88
import com.gh0u1l5.wechatmagician.spellbook.mirror.MirrorClasses
99
import com.gh0u1l5.wechatmagician.spellbook.mirror.MirrorFields
1010
import com.gh0u1l5.wechatmagician.spellbook.mirror.MirrorMethods
11+
import com.gh0u1l5.wechatmagician.spellbook.parser.ApkFile
1112
import com.gh0u1l5.wechatmagician.spellbook.util.FileUtil
1213
import com.gh0u1l5.wechatmagician.spellbook.util.MirrorUtil
1314
import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil
1415
import dalvik.system.PathClassLoader
15-
import net.dongliu.apk.parser.ApkFile
1616
import org.junit.Assert.*
1717
import org.junit.Before
1818
import org.junit.Test
1919
import org.junit.runner.RunWith
2020
import java.io.File
2121
import java.lang.ClassLoader.getSystemClassLoader
22+
import kotlin.system.measureTimeMillis
2223

24+
@ExperimentalUnsignedTypes
2325
@RunWith(AndroidJUnit4::class)
2426
class MirrorUnitTest {
2527
companion object {
@@ -34,8 +36,13 @@ class MirrorUnitTest {
3436
}
3537

3638
private fun verifyPackage(apkPath: String) {
37-
val cacheDir = context!!.cacheDir
39+
// Parse the version of the apk
40+
val regex = Regex("wechat-v(.*)\\.apk")
41+
val match = regex.find(apkPath) ?: throw Exception("Unexpected path format")
42+
val version = match.groupValues[1]
3843

44+
// Store APK file to cache directory.
45+
val cacheDir = context!!.cacheDir
3946
val apkFile = File(cacheDir, apkPath)
4047
try {
4148
javaClass.classLoader!!.getResourceAsStream(apkPath).use {
@@ -46,25 +53,36 @@ class MirrorUnitTest {
4653
return // ignore if the apk isn't accessible
4754
}
4855

56+
// Ensure the apk is presented, and start the test
4957
assertTrue(apkFile.exists())
5058
ApkFile(apkFile).use {
59+
// Benchmark the APK parser
60+
val timeParseDex = measureTimeMillis { it.classTypes }
61+
Log.d("MirrorUnitTest", "Benchmark: Parse DexClasses takes $timeParseDex ms.")
62+
63+
// Initialize WechatGlobal
5164
WechatGlobal.wxUnitTestMode = true
52-
WechatGlobal.wxVersion = Version(it.apkMeta.versionName)
53-
WechatGlobal.wxPackageName = it.apkMeta.packageName
65+
WechatGlobal.wxVersion = Version(version)
66+
WechatGlobal.wxPackageName = "com.tencent.mm"
5467
WechatGlobal.wxLoader = PathClassLoader(apkFile.absolutePath, getSystemClassLoader())
55-
WechatGlobal.wxClasses = it.dexClasses.map { clazz ->
56-
ReflectionUtil.ClassName(clazz.classType)
57-
}
68+
WechatGlobal.wxClasses = it.classTypes
5869

70+
// Clear cached lazy evaluations
5971
val objects = MirrorClasses + MirrorMethods + MirrorFields
6072
ReflectionUtil.clearClassCache()
6173
ReflectionUtil.clearMethodCache()
6274
objects.forEach { instance ->
6375
MirrorUtil.clearUnitTestLazyFields(instance)
6476
}
6577

66-
MirrorUtil.generateReportWithForceEval(objects).forEach {
67-
Log.d("MirrorUnitTest", "Verified ${it.first} -> ${it.second}")
78+
// Test each lazy evaluation and generate result.
79+
var result: List<Pair<String, String>>? = null
80+
val timeSearch = measureTimeMillis {
81+
result = MirrorUtil.generateReportWithForceEval(objects)
82+
}
83+
Log.d("MirrorUnitTest", "Benchmark: Search over classes takes $timeSearch ms.")
84+
result?.forEach { entry ->
85+
Log.d("MirrorUnitTest", "Verified: ${entry.first} -> ${entry.second}")
6886
}
6987
}
7088

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import android.widget.BaseAdapter
55
import com.gh0u1l5.wechatmagician.spellbook.SpellBook.getApplicationVersion
66
import com.gh0u1l5.wechatmagician.spellbook.base.Version
77
import com.gh0u1l5.wechatmagician.spellbook.base.WaitChannel
8+
import com.gh0u1l5.wechatmagician.spellbook.parser.ApkFile
89
import com.gh0u1l5.wechatmagician.spellbook.util.BasicUtil.tryAsynchronously
910
import de.robv.android.xposed.callbacks.XC_LoadPackage
10-
import net.dongliu.apk.parser.ApkFile
1111
import java.lang.ref.WeakReference
1212

1313
/**
@@ -38,7 +38,7 @@ object WechatGlobal {
3838
*
3939
* Example: "Ljava/lang/String;"
4040
*/
41-
@Volatile var wxClasses: List<String>? = null
41+
@Volatile var wxClasses: Array<String>? = null
4242

4343
/**
4444
* A flag indicating whether the codes are running under unit test mode.
@@ -114,7 +114,7 @@ object WechatGlobal {
114114
wxLoader = lpparam.classLoader
115115

116116
ApkFile(lpparam.appInfo.sourceDir).use {
117-
wxClasses = it.dexClasses.map { it.classType }
117+
wxClasses = it.classTypes
118118
}
119119
} finally {
120120
initializeChannel.done()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.gh0u1l5.wechatmagician.spellbook.parser
2+
3+
import java.io.Closeable
4+
import java.io.File
5+
import java.nio.ByteBuffer
6+
import java.util.zip.ZipEntry
7+
import java.util.zip.ZipFile
8+
9+
@ExperimentalUnsignedTypes
10+
class ApkFile(apkFile: File) : Closeable {
11+
companion object {
12+
const val DEX_FILE = "classes.dex"
13+
const val DEX_ADDITIONAL = "classes%d.dex"
14+
}
15+
16+
constructor(path: String) : this(File(path))
17+
18+
private val zipFile: ZipFile = ZipFile(apkFile)
19+
20+
private fun readEntry(entry: ZipEntry): ByteArray =
21+
zipFile.getInputStream(entry).use { it.readBytes() }
22+
23+
override fun close() =
24+
zipFile.close()
25+
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()
33+
}
34+
return@lazy ret
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.gh0u1l5.wechatmagician.spellbook.parser
2+
3+
@ExperimentalUnsignedTypes
4+
class DexHeader {
5+
var version: Int = 0
6+
7+
var checksum: UInt = 0u
8+
9+
var signature: ByteArray = ByteArray(kSHA1DigestLen)
10+
11+
var fileSize: UInt = 0u
12+
13+
var headerSize: UInt = 0u
14+
15+
var endianTag: UInt = 0u
16+
17+
var linkSize: UInt = 0u
18+
19+
var linkOff: UInt = 0u
20+
21+
var mapOff: UInt = 0u
22+
23+
var stringIdsSize: Int = 0
24+
25+
var stringIdsOff: UInt = 0u
26+
27+
var typeIdsSize: Int = 0
28+
29+
var typeIdsOff: UInt = 0u
30+
31+
var protoIdsSize: Int = 0
32+
33+
var protoIdsOff: UInt = 0u
34+
35+
var fieldIdsSize: Int = 0
36+
37+
var fieldIdsOff: UInt = 0u
38+
39+
var methodIdsSize: Int = 0
40+
41+
var methodIdsOff: UInt = 0u
42+
43+
var classDefsSize: Int = 0
44+
45+
var classDefsOff: UInt = 0u
46+
47+
var dataSize: Int = 0
48+
49+
var dataOff: UInt = 0u
50+
51+
companion object {
52+
const val kSHA1DigestLen = 20
53+
const val kSHA1DigestOutputLen = kSHA1DigestLen * 2 + 1
54+
}
55+
}

0 commit comments

Comments
 (0)