From 7f342ca076fd23a3b52da750e10641f50d0c95a6 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Sat, 29 Aug 2026 22:07:51 +0600 Subject: [PATCH] Use built-in DelegateLastClassLoader for extensions (#3874) Assisted-by: Claude:claude-opus-5 --- .../extension/util/ExtensionLoader.kt | 4 +- .../util/system/ChildFirstPathClassLoader.kt | 86 ------------------- .../dalvik/DelegateLastClassLoaderCompat.kt | 70 +++++++++++++++ 3 files changed, 72 insertions(+), 88 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/system/ChildFirstPathClassLoader.kt create mode 100644 app/src/main/java/mihon/data/dalvik/DelegateLastClassLoaderCompat.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt index 8c931cb87..c3faa7f7f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt @@ -13,13 +13,13 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.util.lang.Hash import eu.kanade.tachiyomi.util.storage.copyAndSetReadOnlyTo -import eu.kanade.tachiyomi.util.system.ChildFirstPathClassLoader import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking import logcat.LogPriority import mihon.app.di.appGraph +import mihon.data.dalvik.DelegateLastClassLoaderCompat import tachiyomi.core.common.util.system.logcat import java.io.File @@ -277,7 +277,7 @@ internal object ExtensionLoader { } val classLoader = try { - ChildFirstPathClassLoader(appInfo.sourceDir, null, context.classLoader) + DelegateLastClassLoaderCompat(appInfo.sourceDir, null, context.classLoader) } catch (e: Exception) { logcat(LogPriority.ERROR, e) { "Extension load error: $extName ($pkgName)" } return LoadResult.Error diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ChildFirstPathClassLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ChildFirstPathClassLoader.kt deleted file mode 100644 index 0823e765d..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ChildFirstPathClassLoader.kt +++ /dev/null @@ -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 { - 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 { - 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 - } - } -} diff --git a/app/src/main/java/mihon/data/dalvik/DelegateLastClassLoaderCompat.kt b/app/src/main/java/mihon/data/dalvik/DelegateLastClassLoaderCompat.kt new file mode 100644 index 000000000..fba6a085f --- /dev/null +++ b/app/src/main/java/mihon/data/dalvik/DelegateLastClassLoaderCompat.kt @@ -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 { + 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) + } +}