2

我正在开发一个 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

知道是什么原因造成的吗?

4

1 回答 1

0

确保您没有删除 dex 文件。

于 2020-04-27T14:10:09.547 回答