Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

插件卸载不干净导致无法重新安装插件 #691

Closed
weixiaokang opened this issue Nov 27, 2021 · 9 comments · Fixed by #772
Closed

插件卸载不干净导致无法重新安装插件 #691

weixiaokang opened this issue Nov 27, 2021 · 9 comments · Fixed by #772
Labels
bug Something isn't working

Comments

@weixiaokang
Copy link

复现条件:
使用deleteInstalledPlugin方法卸载插件,再重新安装会失败。

原因分析:
deleteInstalledPlugin方法只会卸载插件、runtime、loader三个apk,而安装插件时回去查找插件目录下的UNPACK_DONE_PRE_FIX + pluginUnpackDir.getName()文件进行判断,如果存在就是已经安装过不解压缩包。加载插件的loader.apk时会失败抛出找不到这个文件异常。

这里有个issue也提到了这个问题:
#621
这个issue解决方法感觉不够简洁,我这里简化了下,自测解决自己场景是没问题,但是还是希望官方能提供更优雅的解决方案。

    public boolean deleteInstalledPlugin(String uuid) {
        InstalledPlugin installedPlugin = mInstalledDao.getInstalledPluginByUUID(uuid);
        File installedFile = installedPlugin.pluginLoaderFile.pluginFile.getParentFile();
        if (installedFile == null || installedFile.getParentFile() == null) {
            return false;
        }
        boolean success = deleteDir(installedFile.getParentFile());
        if (installedPlugin.pluginLoaderFile.oDexDir != null) {
            success = success && deleteDir(installedPlugin.pluginLoaderFile.oDexDir);
        }
        if (installedPlugin.pluginLoaderFile.libraryDir != null) {
            success = success && deleteDir(installedPlugin.pluginLoaderFile.libraryDir);
        }
        return success && mInstalledDao.deleteByUUID(uuid) > 0;
    }

    private boolean deleteDir(File dir) {
        if (dir.isFile()) {
            return dir.delete();
        }
        boolean success = true;
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                success = success && deleteDir(file);
            }
        }
        return success && dir.delete();
    }
@shifujun
Copy link
Collaborator

我重新CR了一下这块的代码,确实是设计层面的问题。我先记录一下问题。

这里有一个UnpackedTag的设计,试图对解压zip的过程做一个缓存记录。当tag存在时,表示一次zip解压完整结束了,下次就可以不用重复解压了。

但是对于BasePluginManager(更具体到InstalledPluginDBHelper中的表结构)来说,已安装的插件是没有安装时来源的记录的,也就没有zip解压的概念了。所以删除插件时,BasePluginManager不可能知道该去清除UnpackedTag标记。

所以,在不谈重构UnpackManager设计的前提下,应该这样修复:

  1. 去掉UnpackedTag
  2. 改为从zip中先主动查找config.json,取出其中的uuid,然后查找已安装的插件是否有已安装的文件,跳过已安装的文件的解压。

@shifujun shifujun added the bug Something isn't working label Nov 29, 2021
@a6685234
Copy link

这个bug已经在最新版本解决了么

@shifujun
Copy link
Collaborator

没呢,解决的话会关联提交到这个issue的,也会关闭的。可以帮忙修复一下。我可能得晚点再看这个了。

@BruceLiuTao
Copy link

我在想是用uuid来处理这个bug还是用文件的MD5,其实我们的插件每次都没有去改uuid,不改的话有啥影响么?

@shifujun
Copy link
Collaborator

shifujun commented Jan 4, 2022

我在想是用uuid来处理这个bug还是用文件的MD5,其实我们的插件每次都没有去改uuid,不改的话有啥影响么?

uuid是用来标记一组apk可以协同工作的。这组apk包括runtime、loader和插件。如果每次发布都使用同样的uuid,显然会失去uuid的检查能力。各apk出现不兼容情况时,你就很难第一时间发现是因为它们版本不兼容导致的。

md5对于每个文件来说都不一样,不能用来替代uuid。即便可以,运行时计算md5也很慢。

@BruceLiuTao
Copy link

更改UnpackManager文件的unpackPlugin方法,这样改可以把?那个文件我没有删除,用来存储对应的uuid,到时候就不是判断文件是否存在了,加了个条件,判断是否一致
/**
* 解包一个下载好的插件
* @param zipHash 插件包的hash
* @param target 插件包
*/
public PluginConfig unpackPlugin(String zipHash, File target) throws IOException, JSONException {
if (zipHash == null) {
zipHash = MinFileUtils.md5File(target);
}
File pluginUnpackDir = getPluginUnpackDir(zipHash, target);

    pluginUnpackDir.mkdirs();
    File tag = getUnpackedTag(pluginUnpackDir);
    PluginConfig pluginConfig = null;
    try {
        pluginConfig = getDownloadedPluginInfoFromPluginUnpackedDir(pluginUnpackDir, zipHash);
    } catch (Exception e) {
        if (!tag.delete()) {
            throw new IOException("解析版本信息失败,且无法删除标记:" + tag.getAbsolutePath());
        }
    }
    if (isDirUnpacked(pluginUnpackDir) && pluginConfig.UUID.equals(MinFileUtils.getFileContent(tag))) {
        Log.i("liutao", "当前包已存在,不解压" + zipHash);
        return pluginConfig;
    }
    Log.i("liutao", "当前包不存在,解压重置相关数据" + zipHash);
    MinFileUtils.cleanDirectory(pluginUnpackDir);

    ZipFile zipFile = null;
    try {
        zipFile = new SafeZipFile(target);
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            if (!entry.isDirectory()) {
                MinFileUtils.writeOutZipEntry(zipFile, entry, pluginUnpackDir, entry.getName());
            }
        }

// PluginConfig pluginConfig = getDownloadedPluginInfoFromPluginUnpackedDir(pluginUnpackDir, zipHash);

        // 外边创建完成标记

        tag.createNewFile();
        MinFileUtils.writeFileContent(tag, pluginConfig.UUID); //写入文件

        return pluginConfig;
    } finally {
        try {
            if (zipFile != null) {
                zipFile.close();
            }
        } catch (IOException e) {
            mLogger.warn("zip关闭时出错忽略", e);
        }
    }
}

@weixiaokang
Copy link
Author

@BruceLiuTao 你们的场景是怎样的?我理解删除插件,就应该要把该插件相关的内容都清理干净

shifujun added a commit to shifujun/Shadow that referenced this issue Jan 11, 2022
单独提取zip中的config.json后先生成PluginConfig。
PluginConfig添加的isUnpacked方法根据各目标文件是否均已存在来判断是否已解压。
如果文件缺失,则会重新解压文件。

fix Tencent#691
shifujun added a commit that referenced this issue Jan 11, 2022
单独提取zip中的config.json后先生成PluginConfig。
PluginConfig添加的isUnpacked方法根据各目标文件是否均已存在来判断是否已解压。
如果文件缺失,则会重新解压文件。

fix #691
@jychenX
Copy link

jychenX commented Jan 20, 2022

大神好,我可以这样理解吗:

卸载插件,其实并不是真正从内存里面移除掉这些插件的类,而是仅仅删除了文件以及插件对应关系的存储,让外部调用不到这个classloader而已;

实际上这些插件已经加载过的类,依然是存于内存中,对吗?

@shifujun
Copy link
Collaborator

大神好,我可以这样理解吗:

卸载插件,其实并不是真正从内存里面移除掉这些插件的类,而是仅仅删除了文件以及插件对应关系的存储,让外部调用不到这个classloader而已;

实际上这些插件已经加载过的类,依然是存于内存中,对吗?

这个issue讨论的是installuninstall过程,是core.manager对于插件管理的文件和数据库操作,并不涉及内存。

你理解的内存相关的加载时core.loader中的loadPlugin操作,是真正加载插件到ClassLoader调用相应方法到过程。这一过程是无法反向操作的。不讨论DexClassLoader的反加载能力,单说so库也是无法撤销load操作的。所以从内存中卸载插件和正常安装的应用一样,需要终止进程才可以。也可以从插件代码是宿主代码的一部分来理解。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants