我正在开发一个 android 应用程序,并且我正在尝试类似插件的功能,该功能将允许加载额外的 dex 文件以扩展应用程序功能。我已经想出了如何加载扩展 PathClassLoader 的附加 dex 文件,只需进行一些小的更改以允许其他模块进行通信。问题是,当应用程序运行时第一次将 dex 文件加载到应用程序中时,一切正常,然后如果我决定禁用此模块以便卸载类加载器,应用程序将继续正常工作几秒钟然后它抛出异常(仍然继续正常工作),然后在几秒/分钟后(有时甚至需要 5 分钟)再次抛出异常,应用程序因本机堆栈跟踪而崩溃。如果我决定再次加载我之前禁用的模块,它只会增加崩溃的机会。
这是卸载模块类加载器几秒钟后发生的情况:
12-27 01:57:10.839 E/System: Uncaught exception thrown by finalizer
12-27 01:57:10.840 E/System: java.lang.AssertionError: Failed to close dex file in finalizer.
at dalvik.system.DexFile.finalize(DexFile.java:336)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:250)
at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:237)
at java.lang.Daemons$Daemon.run(Daemons.java:103)
at java.lang.Thread.run(Thread.java:764)
12-27 01:57:10.840 E/System: Uncaught exception thrown by finalizer
12-27 01:57:10.840 E/System: java.lang.AssertionError: Failed to close dex file in finalizer.
at dalvik.system.DexFile.finalize(DexFile.java:336)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:250)
at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:237)
at java.lang.Daemons$Daemon.run(Daemons.java:103)
at java.lang.Thread.run(Thread.java:764)
12-27 01:57:10.841 E/System: Uncaught exception thrown by finalizer
12-27 01:57:10.841 E/System: java.lang.AssertionError: Failed to close dex file in finalizer.
at dalvik.system.DexFile.finalize(DexFile.java:336)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:250)
at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:237)
at java.lang.Daemons$Daemon.run(Daemons.java:103)
at java.lang.Thread.run(Thread.java:764)
然后几秒钟或几分钟后,会发生本机崩溃:
12-27 01:57:15.409 A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
12-27 01:57:15.409 A/DEBUG: Build fingerprint: 'Sony/G8341/G8341:8.0.0/47.1.A.8.49/3744219090:user/release-keys'
12-27 01:57:15.409 A/DEBUG: Revision: '0'
12-27 01:57:15.409 A/DEBUG: ABI: 'arm64'
12-27 01:57:15.409 A/DEBUG: pid: 17551, tid: 17697, name: Profile Saver >>> com.rowl.plugdj
12-27 01:57:15.409 A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x657a6983
12-27 01:57:15.409 A/DEBUG: x0 00000000657a6973 x1 0000007bb0739460 x2 0000007bb0600000 x3 0000000000000002
12-27 01:57:15.409 A/DEBUG: x4 0000000000000139 x5 0000007bb073947f x6 2f6d65747379732f x7 726f77656d617266
12-27 01:57:15.409 A/DEBUG: x8 0000000013954588 x9 aec629c3012f5dc2 x10 0000000000000139 x11 000000000000004c
12-27 01:57:15.409 A/DEBUG: x12 656d6172662f6b72 x13 72616a2e6b726f77 x14 000000000000000d x15 aaaaaaaaaaaaaaab
12-27 01:57:15.409 A/DEBUG: x16 0000007bca457cc8 x17 0000007bca3f5f60 x18 0000007bc9c07eb0 x19 0000007baecba270
12-27 01:57:15.409 A/DEBUG: x20 0000007ba6ab68c0 x21 0000007baecba588 x22 0000007ba6ab4298 x23 0000007baa692b18
12-27 01:57:15.409 A/DEBUG: x24 0000000000000000 x25 0000000000002710 x26 00000000977434b0 x27 0000000097746934
12-27 01:57:15.409 A/DEBUG: x28 0000007bc99b6b70 x29 0000007baecba1d0 x30 0000007bc96cf67c
12-27 01:57:15.409 A/DEBUG: sp 0000007baecba0c0 pc 0000007bc96cf634 pstate 0000000080000000
12-27 01:57:15.411 A/DEBUG: backtrace:
12-27 01:57:15.411 A/DEBUG: #00 pc 00000000002fe634 /system/lib64/libart.so (_ZN3art3jit12JitCodeCache18GetProfiledMethodsERKNSt3__13setINS2_12basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEENS2_4lessIS9_EENS7_IS9_EEEERNS2_6vectorINS_17ProfileMethodInfoENS7_ISH_EEEE+228)
12-27 01:57:15.411 A/DEBUG: #01 pc 000000000030ac08 /system/lib64/libart.so (_ZN3art12ProfileSaver20ProcessProfilingInfoEbPt+1452)
12-27 01:57:15.411 A/DEBUG: #02 pc 00000000003099ac /system/lib64/libart.so (_ZN3art12ProfileSaver3RunEv+704)
12-27 01:57:15.411 A/DEBUG: #03 pc 000000000030b7b8 /system/lib64/libart.so (_ZN3art12ProfileSaver21RunProfileSaverThreadEPv+88)
12-27 01:57:15.411 A/DEBUG: #04 pc 00000000000667d0 /system/lib64/libc.so (_ZL15__pthread_startPv+36)
12-27 01:57:15.411 A/DEBUG: #05 pc 000000000001f2a4 /system/lib64/libc.so (__start_thread+68)
这是我正在使用的类加载器
public class ExtensionClassLoader extends PathClassLoader {
private final Map<String, Class<?>> classes = new HashMap();
private final ExtensionManager extensionManager;
public ExtensionClassLoader(ExtensionManager extensionManager, String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, librarySearchPath, parent);
this.extensionManager = extensionManager;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (name.startsWith("com.rowl.") || name.startsWith("dj.plug.")) {
throw new ClassNotFoundException(name);
}
PlugDJ.d("Find Class: " + name);
Class clazz = classes.get(name);
if(clazz == null){
clazz = extensionManager.getClass(name);
if(clazz == null){
clazz = super.findClass(name);
if(clazz != null){
extensionManager.addClass(name, clazz);
}
}
if(clazz != null){
classes.put(name, clazz);
}
}
return clazz;
}
@Override
public String findLibrary(String name) {
return super.findLibrary(name);
}
public Set<String> getClasses(){
return classes.keySet();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
}
}
此外,这里是加载这些模块的类:
class ExtensionManager(plugDJInstance: PlugDJ) : Manager(plugDJInstance) {
private val classes = HashMap<String, Class<*>>()
private val extensionLoaders = HashMap<String, ExtensionClassLoader>()
private val loadedExtensions = ArrayList<PDJExtension>()
private val availableExtensions = ArrayList<PDJExtensionInfo>()
val plugAPI = PlugAPIImpl(pdjInstance as PlugDJAndroid)
private val context by lazy { (pdjInstance as PlugDJAndroid).context }
private val extensionSettings by lazy { context.getSharedPreferences("ext", Context.MODE_PRIVATE)}
private val whitelistedExtensions by lazy { extensionSettings.getStringSet("whitelistedExtensions", HashSet<String>()) }
fun addClass(name: String, clazz: Class<*>){
if(!classes.containsKey(name)){
classes.put(name, clazz)
}
}
fun removeClass(name: String){
classes.remove(name)
}
fun getClass(name: String): Class<*>?{
return classes[name]
}
private fun loadApk(dir: File, dexOutputDir: File, parent: ClassLoader): ExtensionClassLoader{
val files = StringBuilder()
dir.listFiles().filter { it.extension == "apk" }.forEach {
files.append(it.absolutePath).append(":")
}
files.deleteCharAt(files.length - 1)
return ExtensionClassLoader(this, files.toString(), dexOutputDir.absolutePath.toString(), parent)
}
fun isExtensionWhitelisted(extInfo: PDJExtensionInfo): Boolean{
return whitelistedExtensions.contains(extInfo.appPackage)
}
@SuppressLint("ApplySharedPref")
fun addExtensionToWhitelist(extInfo: PDJExtensionInfo){
whitelistedExtensions.add(extInfo.appPackage)
extensionSettings.edit().putStringSet("whitelistedExtensions", whitelistedExtensions).commit()
d("Added " + extInfo.appPackage + " to whitelist")
}
@SuppressLint("ApplySharedPref")
fun removeExtensionFromWhitelist(extInfo: PDJExtensionInfo){
whitelistedExtensions.remove(extInfo.appPackage)
extensionSettings.edit().putStringSet("whitelistedExtensions", if(whitelistedExtensions.isEmpty()) null else whitelistedExtensions).commit()
d("Checking... " + whitelistedExtensions)
d("Removed " + extInfo.appPackage + " from whitelist")
}
fun discoverExtensions() {
try{
whitelistedExtensions.forEach {
d("Whitelisted extension: $it")
}
val installedApps = context.packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
for(app in installedApps){
if(app.sourceDir.contains("system")) continue
if(app.metaData != null && app.metaData.containsKey("pdjExtensionName")){
val extName = app.metaData.getString("pdjExtensionName")
val extMainClass = app.metaData.getString("pdjExtensionMainClass")
val sourceDir = File(app.sourceDir).parentFile
PlugDJ.i("Found extension: " + extName)
var extInfo = getAvailableExtensions().find { it.appPackage == app.packageName }
d(extInfo?.sourceDirectory.toString() )
d(sourceDir.toString() )
if(extInfo != null){
PlugDJ.d("Removing " + app.packageName)
availableExtensions.remove(extInfo)
}
extInfo = PDJExtensionInfoImpl(extName, sourceDir, app.packageName, extMainClass, "", "", null, null)
availableExtensions.add(extInfo)
}
}
}catch (e: Exception){
e.printStackTrace()
}
}
fun getAvailableExtensions(): List<PDJExtensionInfo>{
return Collections.unmodifiableList(ArrayList(availableExtensions))
}
fun getLoadedExtensions(): List<PDJExtension>{
return Collections.unmodifiableList(ArrayList(loadedExtensions))
}
fun loadExtension(extInfo: PDJExtensionInfo): PDJExtension{
var cl = context.classLoader
val dexOutputDir = context.getDir("dex", Context.MODE_PRIVATE)
val extcl = loadApk(extInfo.sourceDirectory, dexOutputDir, cl)
extensionLoaders[extInfo.name] = extcl
val mainClass = extcl.loadClass(extInfo.mainClass)
val apiField = mainClass.superclass.getDeclaredField("api")
val clField = mainClass.superclass.getDeclaredField("classLoader")
val extField = mainClass.superclass.getDeclaredField("extensionInfo")
val extension = mainClass.newInstance() as PDJExtension
apiField.apply {
isAccessible = true
set(extension, plugAPI)
isAccessible = false
}
clField.apply {
isAccessible = true
set(extension, mainClass.classLoader)
isAccessible = false
}
extField.apply {
isAccessible = true
set(extension, extInfo)
isAccessible = false
}
loadedExtensions.add(extension)
return extension
}
fun getLoadedExtension(appPackage: String): PDJExtension? {
return getLoadedExtensions().find { it.extensionInfo.appPackage == appPackage }
}
fun enableExtension(ext: PDJExtension){
ext.onEnable()
ext::class.java.superclass.getDeclaredField("isEnabled").apply {
isAccessible = true
set(ext, true)
isAccessible = false
}
}
fun disableExtension(ext: PDJExtension){
try{
plugAPI.removeEventListeners(ext)
ext.onDisable()
}catch (e: Exception){
e.printStackTrace()
}
ext::class.java.superclass.getDeclaredField("isEnabled").apply {
isAccessible = true
set(ext, false)
isAccessible = false
}
}
fun unloadExtension(ext: PDJExtension){
d("Unloading extension...")
ext::class.java.superclass.getDeclaredField("api").apply {
isAccessible = true
set(ext, null)
isAccessible = false
}
val cl = ext.classLoader as ExtensionClassLoader
loadedExtensions.remove(ext)
for(className in cl.classes){
removeClass(className)
}
extensionLoaders.remove(ext.extensionInfo.name)
d("Extension unloaded.")
System.runFinalization()
System.gc()
}
}
此外,这里是完整的日志https://pastebin.com/Jhpk4Wpc
知道是什么原因造成的吗?