Skip to content

Commit

Permalink
perf(core.loader): 插件ClassLoader白名单采用前缀树进行匹配
Browse files Browse the repository at this point in the history
改善规则较多时的字符串匹配性能。
  • Loading branch information
shifujun committed Oct 12, 2021
1 parent a335b19 commit cc8f6d0
Showing 1 changed file with 77 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.tencent.shadow.core.loader.classloaders

import android.os.Build
import dalvik.system.BaseDexClassLoader
import org.jetbrains.annotations.TestOnly
import java.io.File


Expand Down Expand Up @@ -48,18 +49,22 @@ class PluginClassLoader(
* 宿主的白名单包名
* 在白名单包里面的宿主类,插件才可以访问
*/
private val allHostWhiteList: Array<String>
private val allHostWhiteTrie = PackageNameTrie()

private val loaderClassLoader = PluginClassLoader::class.java.classLoader!!

init {
val defaultWhiteList = arrayOf(
"org.apache.commons.logging"//org.apache.commons.logging是非常特殊的的包,由系统放到App的PathClassLoader中.
)
if (hostWhiteList != null) {
allHostWhiteList = defaultWhiteList.plus(hostWhiteList)
}else {
allHostWhiteList = defaultWhiteList
hostWhiteList?.forEach {
allHostWhiteTrie.insert(it)
}

//org.apache.commons.logging是非常特殊的的包,由系统放到App的PathClassLoader中.
allHostWhiteTrie.insert("org.apache.commons.logging")

//Android 9.0以下的系统里面带有http包,走系统的不走本地的
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
allHostWhiteTrie.insert("org.apache.http")
allHostWhiteTrie.insert("org.apache.http.**")
}
}

Expand All @@ -79,11 +84,7 @@ class PluginClassLoader(
}

//包名在白名单中的类按双亲委派逻辑,从宿主中加载
if (className.inPackage(allHostWhiteList)
//Android 9.0以下的系统里面带有http包,走系统的不走本地的
|| (Build.VERSION.SDK_INT < Build.VERSION_CODES.P
&& className.startsWith("org.apache.http"))
) {
if (className.inPackage(allHostWhiteTrie)) {
return super.loadClass(className, resolve)
}

Expand Down Expand Up @@ -114,33 +115,75 @@ class PluginClassLoader(

private fun String.subStringBeforeDot() = substringBeforeLast('.', "")

@Deprecated("use PackageNameTrie instead.")
@TestOnly
internal fun String.inPackage(packageNames: Array<String>): Boolean {
val trie = PackageNameTrie()
packageNames.forEach {
trie.insert(it)
}
return inPackage(trie)
}

private fun String.inPackage(packageNames: PackageNameTrie): Boolean {
val packageName = subStringBeforeDot()
return packageNames.isMatch(packageName)
}

return packageNames.any {
return@any when {
it == "" -> false
it == ".*" -> false
it == ".**" -> false
it.endsWith(".*") -> {//只允许一级子包
val sub = packageName.subStringBeforeDot()
if (sub.isEmpty()) {
false
} else {
sub == it.subStringBeforeDot()
}
/**
* 基于Trie算法对包名进行前缀匹配
*/
private class PackageNameTrie {
private class Node {
val subNodes = mutableMapOf<String, Node>()
var isLastPackageOfARule = false
}

private val root = Node()

fun insert(packageNameRule: String) {
var node = root
packageNameRule.split('.').forEach {
if (it.isEmpty()) return //"",".*",".**"这种无包名情况不允许设置

var subNode = node.subNodes[it]
if (subNode == null) {
subNode = Node()
node.subNodes[it] = subNode
}
node = subNode
}
node.isLastPackageOfARule = true
}

fun isMatch(packageName: String): Boolean {
var node = root

val split = packageName.split('.')
val lastIndex = split.size - 1
for ((index, name) in split.withIndex()) {
// 只要下级包名规则中有**,就完成了匹配
val twoStars = node.subNodes["**"]
if (twoStars != null) {
return true
}
it.endsWith(".**") -> {//允许所有子包
val sub = packageName.subStringBeforeDot()
if (sub.isEmpty()) {
false
} else {
"$sub.".startsWith(it.subStringBeforeDot() + '.')

// 剩最后一级包名时,如果规则是*则完成比配
if (index == lastIndex) {
val oneStar = node.subNodes["*"]
if (oneStar != null) {
return true
}
}
else -> packageName == it

// 找不到下级包名时即匹配失败
val subNode = node.subNodes[name]
if (subNode == null) {
return false
} else {
node = subNode
}
}
return node.isLastPackageOfARule
}
}


0 comments on commit cc8f6d0

Please sign in to comment.