Use built-in DelegateLastClassLoader for extensions (#3874)

Assisted-by: Claude:claude-opus-5
This commit is contained in:
AntsyLich
2026-08-29 22:07:51 +06:00
committed by GitHub
parent b695940114
commit 7f342ca076
3 changed files with 72 additions and 88 deletions
@@ -13,13 +13,13 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.util.lang.Hash import eu.kanade.tachiyomi.util.lang.Hash
import eu.kanade.tachiyomi.util.storage.copyAndSetReadOnlyTo import eu.kanade.tachiyomi.util.storage.copyAndSetReadOnlyTo
import eu.kanade.tachiyomi.util.system.ChildFirstPathClassLoader
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import logcat.LogPriority import logcat.LogPriority
import mihon.app.di.appGraph import mihon.app.di.appGraph
import mihon.data.dalvik.DelegateLastClassLoaderCompat
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import java.io.File import java.io.File
@@ -277,7 +277,7 @@ internal object ExtensionLoader {
} }
val classLoader = try { val classLoader = try {
ChildFirstPathClassLoader(appInfo.sourceDir, null, context.classLoader) DelegateLastClassLoaderCompat(appInfo.sourceDir, null, context.classLoader)
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Extension load error: $extName ($pkgName)" } logcat(LogPriority.ERROR, e) { "Extension load error: $extName ($pkgName)" }
return LoadResult.Error return LoadResult.Error
@@ -1,86 +0,0 @@
package eu.kanade.tachiyomi.util.system
import dalvik.system.PathClassLoader
import java.io.IOException
import java.io.InputStream
import java.net.URL
import java.util.Enumeration
/**
* A parent-last class loader that will try in order:
* - the system class loader
* - the child class loader
* - the parent class loader.
*/
class ChildFirstPathClassLoader(
dexPath: String,
librarySearchPath: String?,
parent: ClassLoader,
) : PathClassLoader(dexPath, librarySearchPath, parent) {
private val systemClassLoader: ClassLoader? = getSystemClassLoader()
override fun loadClass(name: String?, resolve: Boolean): Class<*> {
var c = findLoadedClass(name)
if (c == null && systemClassLoader != null) {
try {
c = systemClassLoader.loadClass(name)
} catch (_: ClassNotFoundException) {}
}
if (c == null) {
c = try {
findClass(name)
} catch (_: ClassNotFoundException) {
super.loadClass(name, resolve)
}
}
if (resolve) {
resolveClass(c)
}
return c
}
override fun getResource(name: String?): URL? {
return systemClassLoader?.getResource(name)
?: findResource(name)
?: super.getResource(name)
}
override fun getResources(name: String?): Enumeration<URL> {
val systemUrls = systemClassLoader?.getResources(name)
val localUrls = findResources(name)
val parentUrls = parent?.getResources(name)
val urls = buildList {
while (systemUrls?.hasMoreElements() == true) {
add(systemUrls.nextElement())
}
while (localUrls?.hasMoreElements() == true) {
add(localUrls.nextElement())
}
while (parentUrls?.hasMoreElements() == true) {
add(parentUrls.nextElement())
}
}
return object : Enumeration<URL> {
val iterator = urls.iterator()
override fun hasMoreElements() = iterator.hasNext()
override fun nextElement() = iterator.next()
}
}
override fun getResourceAsStream(name: String?): InputStream? {
return try {
getResource(name)?.openStream()
} catch (_: IOException) {
return null
}
}
}
@@ -0,0 +1,70 @@
package mihon.data.dalvik
import android.os.Build
import dalvik.system.DelegateLastClassLoader
import dalvik.system.PathClassLoader
import java.net.URL
import java.util.Collections
import java.util.Enumeration
@Suppress("FunctionName")
fun DelegateLastClassLoaderCompat(
dexPath: String,
librarySearchPath: String?,
parent: ClassLoader,
): ClassLoader {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
DelegateLastClassLoader(dexPath, librarySearchPath, parent)
} else {
DelegateLastPathClassLoader(dexPath, librarySearchPath, parent)
}
}
/**
* Backport of [DelegateLastClassLoader], which was added in API 27.
*/
private class DelegateLastPathClassLoader(
dexPath: String,
librarySearchPath: String?,
parent: ClassLoader,
) : PathClassLoader(dexPath, librarySearchPath, parent) {
private val bootClassLoader: ClassLoader? = Any::class.java.classLoader
override fun loadClass(name: String?, resolve: Boolean): Class<*> {
findLoadedClass(name)?.let { return it }
if (bootClassLoader != null) {
try {
return bootClassLoader.loadClass(name)
} catch (_: ClassNotFoundException) {}
}
val fromSuper = try {
return findClass(name)
} catch (e: ClassNotFoundException) {
e
}
return try {
parent.loadClass(name)
} catch (_: ClassNotFoundException) {
throw fromSuper
}
}
override fun getResource(name: String?): URL? {
return bootClassLoader?.getResource(name)
?: findResource(name)
?: parent?.getResource(name)
}
override fun getResources(name: String?): Enumeration<URL> {
val resources = buildList {
bootClassLoader?.getResources(name)?.let { addAll(it.toList()) }
findResources(name)?.let { addAll(it.toList()) }
parent?.getResources(name)?.let { addAll(it.toList()) }
}
return Collections.enumeration(resources)
}
}