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

support multidex feature in plugin application for the ROM below LOLLIPOP #264

Merged
merged 11 commits into from
Aug 10, 2017

Conversation

wangfuda
Copy link
Contributor

@wangfuda wangfuda commented Jul 30, 2017

Summary

增加特性支持:
1.增加RP框架对运行在android 5.0以下版本的插件(含多dex)的multidex支持
2.demo1中增加multidex support特性测试用例,默认关闭

Description

1.在android 5.0以下,动态解压插件应用并加载除主dex以外的所有dex。解压后并未删除这些dex,空间与时间的权衡,不希望删除文件带来性能损耗和时间上的增加,虽然只是一点点(还请评估,如果希望避免空间浪费,可以增加删除解压后dex文件逻辑)
2.在android 5.0及5.0以上,不做处理。
3.已针对内置插件和外置插件在android 5.0以上及4.4.2设备上做过多次压力测试,测试用例pass。
4.对宿主lib库的gradle做了修改,增加便于发布lib库至mavenLocal的代码,便于本地调试。

Copy link
Contributor

@jiongxuan jiongxuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

非常感谢Osan兄的给力提交!

MultiDex的支持本身会比较繁琐,能写出这么多核心代码着实不易。这里有几个我认为要修改的点,可能辛苦兄弟多看下了。

@@ -53,4 +54,12 @@ dependencies {
provided 'com.android.support:support-v4:25.2.0'
}

publishing {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我们经过试验,publishing组和bintray是有冲突的,建议先注释掉

Copy link
Contributor

@jiongxuan jiongxuan Jul 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议先回滚此改动(指publishing块)。我这边会提上去“可用的”玩法

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已回滚


try {
// get dexElements of main dex
Class<?> clz = Class.forName("dalvik.system.BaseDexClassLoader");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因为方法内涉及到Hook点了(DexPathList),所以从原则上可尽量去掉对此的引用,否则一旦DexPathList做了修改(例如4.4以下的个别ROM做了什么改动),则可能会导致加载失败的问题。

例如,能不能采用new出多个DexClassLoader,并加到List中,然后在使用时依次做findClass的处理呢?
因为DexPathList在findClass阶段,本质上也就是去轮询读取DexFile里的findClass,在性能上确实是OK的。

以下是“伪代码”,具体用法欢迎QQ私信联系

// 初始化
DexClassLoader[] dexes = ...

// 使用(以下为伪代码)
for (DexClassLoader d : dexes) {
    Class clz = cl.findClass();
    if (clz == null) {
        continue;
    }
    ...
}

Copy link
Contributor Author

@wangfuda wangfuda Aug 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

多个DexClassLoader依次loadclass,目前做过多种解决思路尝试,最终有会遇到2个问题:
1.主DexClassLoader和其他DexClassLoader无双亲委派关系时,会在跨dex加载2个类A和B,并这2个类关系为内部类接口实现,且A类和B类分别位于不同dex时,dalvik无法resloving interface关系。除了这个特殊情况,其他情况已验证均可正常加载,demo1 multidex时已测试pass,但是demo2 multidex测试时,发现这个特殊情况会失败。
2.主DexClassLoader和其他DexClassLoader有双亲委派关系时,会在跨dex加载其他dex的类时,遇到Class ref in pre-verified class resolved to unexpected implementation异常。
以上2个问题尝试过一些办法去解决,暂时未成。
目前在方法上加注了@deprecated,限定为仅5.0以下api,且可能废弃。
后续继续尝试是否有其他解决方案。

* @param optimizedDirectory
* @param parent
*/
private void installExtraDexes(String dexPath, String optimizedDirectory, ClassLoader parent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因为这个方法主要是针对4.4及以下,建议将方法名改为:installMultiDexesBeforeLollipop

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已更名

allElements.add(mainElements);

// get paths of dex
List<File> dexFiles = getDexFiles(dexPath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以先做下DexFile的判断,如果只有一个Dex,则和过去完全一样,否则才走后面的逻辑,这样尽可能减少影响面。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已判断

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因为还要考虑到和之前版本的兼容问题,故建议这么判断:

List<File> dexFiles = getDexFiles(dexPath);
if (dexFiles != null) {
    if (dexFiles.size() > 1) {
        // 不止一处Dex,证明适用于MultiDex的情况,做后面的MultiDex支持工作
        ...
        return true;
    } else if (dexFiles.size() == 1) {
        // 只有一个Dex,按原来逻辑处理
        ...
        return false;
    }
}
// 其它异常情况,抛出错误日志,然后直接返回
return false;

List<File> files = null;
try {
zipFile = new ZipFile(dexPath);
files = traverseZipFile(dexPath.substring(0, dexPath.lastIndexOf("/")), zipFile);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这块儿可能有个问题:目前所有的Dex都放在app_p_od下(可以看下手机中存的dex文件即可了解)。如果“直接释放”,则一旦有超过2个及以上的MultiDex插件,则可能会出现classes2.dex覆盖的问题。

建议创建目录,说白了就是dexPath的文件名后面加个_md后缀,这样可做有效区分。

同时还应注意,需要在PluginManagerServer和PluginInfo中,对这种情况做“兼容处理”,否则一旦插件卸载,则可能导致目录无法被清除。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已针对多dex多插件场景作了覆盖;已针对卸载升级等做了"兼容处理"

@jiongxuan
Copy link
Contributor

对了,我刚才突然想到一点,也建议改下,关于Sample上的。

目前Demo1上的东西有些多,而MultiDex本身相对独立,建议挪到Demo2中进行展示,这样避免“头重脚轻”,而且顺带着可以演示下从Demo1进入带MultiDex的Demo2的真实效果

@wangfuda
Copy link
Contributor Author

wangfuda commented Aug 6, 2017

另:MultiDex test case 已移至demo2

Copy link
Contributor

@jiongxuan jiongxuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢Osan!现在代码比以前更完善了。

还有几个细节,辛苦再看下哈。

files = traverseZipFile(dexPath.substring(0, dexPath.lastIndexOf("/")), zipFile);
} catch (IOException e) {

String installedFileName = dexPath.substring(dexPath.lastIndexOf(File.separator) + 1).replace(".jar", "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这块儿其实有个更好的方法能拿到APK的路径,那就是利用PluginInfo.getApkFile() 方法。
而PluginInfo的获取可以这么做:

String pn = RePlugin.fetchPluginNameByClassLoader(this);
PluginInfo pi = RePlugin.getPluginInfo(pn);
...

(上面只是最简单的流程代码,具体还得做下缓存、判空等处理)

PS:这块儿当时我在设计时,确实没想到需要用到PluginInfo(觉得用不到)。早知道当时就通过构造函数,直接让外面传进来得了 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

经过测试,这里通过自身的classloader获取不到插件名。
因此这里我重构了构造函数,增加了PluginInfo参数,不想用我之前那种妥协的轮询查插件信息方式。

*
* @return 优化前Dex所在目录的File对象
*/
public File getUnoptDexParentDir() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

目前该方法的内容为:无论是否支持MultiDex,其获取的都是添加了“_md”后的目录,然而,非MultiDex插件又不会用到这个方法。所以在方法名上,叫“getUnoptDex...”则会让人误以为,无论是否支持MultiDex都获取的是统一的目录。

建议将方法名改为:getMultiDexParentDir,或者直接将此方法放入PluginDexClassLoader中(毕竟只有一处在用)

Copy link
Contributor Author

@wangfuda wangfuda Aug 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里包括后面您提到的关于multidex,main dex,extra dex的revivew点,我做了一次整体的调整。

1.这里获取的dex更确切的说是extra dex,即除main dex以外的dex,因此叫getExtraDexParentDir更为确切,包括后续相关方法定义,均沿用此思路。
2.dex放置目录上,区别为_ed目录和_od目录,分别用于放置优化前的extra dex(因为main dex由父dexclassloader直接去释放到优化后的目录,不需要RP框架释放,因此定义为extra dex目录)和优化后的optimized dex(包括main odex 和 extra odex)。这里从定义的层面不再去特别关心是否为multidex场景,从而避免了在非multidex场景时目录名仍包含_"m"d的误解。
3.在获取extra dex列表时,如果不存在extra dex,则不会去创建extra dex的_ed目录。


File subDir = new File(dir + File.separator + makeInstalledFileName() + Constant.LOCAL_PLUGIN_MULTI_ODEX_SUB_DIR);
if (!subDir.exists()) {
subDir.mkdir();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

理由同getUnoptDexParentDir

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已重构


File subDir = new File(dir + File.separator + makeInstalledFileName() + Constant.LOCAL_PLUGIN_MULTI_DEX_SUB_DIR);
if (!subDir.exists()) {
subDir.mkdir();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

通常这类get方法不建议再做一次mkdir操作,容易和方法名所表示的含义冲突。一般叫“getXXX”方法都是从内存中获取记录(可以做简单运算),而从网络、文件中获取内容大部分用fetch。而会“创建目录”的情况下,叫“make”更合适。

建议将mkdir的操作放在方法外。

Copy link
Contributor Author

@wangfuda wangfuda Aug 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已重构,采用的类似context.getDir方式,单独重构了一个getDexDir方法出来,Retrieve dir, or creating if needed

allElements.add(mainElements);

// get paths of dex
List<File> dexFiles = getDexFiles(dexPath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因为还要考虑到和之前版本的兼容问题,故建议这么判断:

List<File> dexFiles = getDexFiles(dexPath);
if (dexFiles != null) {
    if (dexFiles.size() > 1) {
        // 不止一处Dex,证明适用于MultiDex的情况,做后面的MultiDex支持工作
        ...
        return true;
    } else if (dexFiles.size() == 1) {
        // 只有一个Dex,按原来逻辑处理
        ...
        return false;
    }
}
// 其它异常情况,抛出错误日志,然后直接返回
return false;

// 必须使用宿主的Context对象,防止出现“目录定位到插件内”的问题
Context context = RePluginInternal.getAppContext();
if (isPnPlugin()) {
return context.getDir(Constant.LOCAL_PLUGIN_ODEX_SUB_DIR, 0);
dir = context.getDir(Constant.LOCAL_PLUGIN_ODEX_SUB_DIR, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议可以联合上面的方法,再抽取一个共用方法出来(但为private,不能让外界访问)。只单纯的获取Dex目录即可(也即,和早期的getDexParentFile一样)。比如可以叫 getPureDexParentDir() 之类的(先临时这么叫,反正将来随时可以改)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已重构

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我看了下,这部分代码会将P-N(一种旧的卫士插件管理方案)的目录给修改了(原来是”app_plugins_v3_odex“,现在是”app_plugins_v3_odex_ed/od),可能会导致出现旧的P-n插件会重复释放odex。总体而言,我觉得这块儿的改动还是过大了。

我的想法是,尽可能减少对过去代码的入侵,要确保在 5.0以上,或者没有做MultiDex的插件,仍走过去一样的逻辑,不受影响。只有在判断是需要做MultiDex后,再创建相应的目录。

因为和这部分代码不是很搭,所以我把具体描述写到了外面的,可参考下。


if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {

File subDir = new File(dir + File.separator + makeInstalledFileName() + Constant.LOCAL_PLUGIN_MULTI_ODEX_SUB_DIR);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这块儿写的非常赞,先判断Android 5.0以上,然后再判断是否有带MultiDex字样的目录,有就返回新的,没有就 返回旧的👍

* @param zipFile
* @return the File list of the extra dexes
*/
private static List<File> traverseZipFile(String dir, ZipFile zipFile) {
Copy link
Contributor

@jiongxuan jiongxuan Aug 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如刚才私信沟通,建议改成“traverseExtraDex”,毕竟里面是对classes.dex做了排除的,怕会有误会

Copy link
Contributor Author

@wangfuda wangfuda Aug 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

所见略同:),已重构。

@jiongxuan jiongxuan changed the base branch from master to dev August 7, 2017 13:02
Copy link
Contributor

@jiongxuan jiongxuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

非常感谢,还有些细节调整

// 必须使用宿主的Context对象,防止出现“目录定位到插件内”的问题
Context context = RePluginInternal.getAppContext();
if (isPnPlugin()) {
return context.getDir(Constant.LOCAL_PLUGIN_ODEX_SUB_DIR, 0);
dir = context.getDir(Constant.LOCAL_PLUGIN_ODEX_SUB_DIR, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我看了下,这部分代码会将P-N(一种旧的卫士插件管理方案)的目录给修改了(原来是”app_plugins_v3_odex“,现在是”app_plugins_v3_odex_ed/od),可能会导致出现旧的P-n插件会重复释放odex。总体而言,我觉得这块儿的改动还是过大了。

我的想法是,尽可能减少对过去代码的入侵,要确保在 5.0以上,或者没有做MultiDex的插件,仍走过去一样的逻辑,不受影响。只有在判断是需要做MultiDex后,再创建相应的目录。

因为和这部分代码不是很搭,所以我把具体描述写到了外面的,可参考下。

@jiongxuan
Copy link
Contributor

jiongxuan commented Aug 8, 2017

我看了下,最新的提交会将P-N(一种旧的卫士插件管理方案)的目录给修改了(原来是”app_plugins_v3_odex“,现在是”app_plugins_v3_odex_ed/od),可能会导致出现旧的P-n插件会重复释放odex。总体而言,我觉得这块儿的改动还是过大了。

我的想法是,尽可能减少对过去代码的入侵,要确保在 5.0以上,或者没有做MultiDex的插件,仍走过去一样的逻辑,不受影响。只有在判断是需要做MultiDex后,再创建相应的目录。

再具体一些,如果这个插件“不需要支持”MultiDex,则其Dex的目录为:

  • 新插件:/data/data/xxx/app_p_od/1324130492.dex
  • P-n插件:/data/data/xxx/app_plugins_v3_odex/xxx-10-10-101.dex

等于说,和过去完全一样,保持不变

如果需要支持MultiDex,则为:

  • 新插件:/data/data/xxx/app_p_od/1324130492/classes.dex (and etc)
  • P-n插件:/data/data/xxx/app_plugins_v3_odex/xxx-10-10-101/classes.dex (and etc)

若老插件不支持MultiDex,但新插件又支持,则:

  • 先尝试删除/data/data/xxx/app_p_od/1324130492.dex和1324130492目录
  • 再释放新的插件,走上面的逻辑

反之亦然。

@jiongxuan
Copy link
Contributor

jiongxuan commented Aug 8, 2017

从架构设计角度来看,虽然项目迭代在所难免,但很多时候,越少的侵入性修改,也就意味着越能够减少不稳定因素。

关于这次MultiDex上,我觉得核心点是针对“Android 5.0以下,且该插件是MultiDex”的去做处理,所以我们应该专注于针对这块儿来做支持。

如咱俩私信沟通,建议:

关于PluginInfo:

  • getDexParentDir方法内容和过去完全一样
  • getDexFile和以前一样,返回非MultiDex的File对象(无论文件是否存在)
  • 新增 getMultiDexDir 方法,返回的是支持MultiDex的那个目录(同样的,无论目录是否存在)
  • 原来调用getDexFile的一些方法(如move、delete等),也顺便调用下getMultiDexDir

在PluginDexClassLoader里:

  • 若不满足MultiDex条件,则和过去一样,还是用当时传进去的optimizedDirectory来
  • 若满足MultiDex,则首先用getMultiDexDir来获取类似xxx_od这样的目录,然后释放到那,最后再将其Add到Elements里

注意原来的像isDexExtracted这类方法,原来只判断了getDexFile的情况,现在还应再判断下getMultiDexDir。当然这个MultiDexDir在注释里也写下“仅适用于4.4及以下”,避免未来有歧义

仅供参考~ 另外给个大大的👍 !

@wangfuda
Copy link
Contributor Author

wangfuda commented Aug 8, 2017

  1. 已优化为对原dex目录及dex主文件不做任何侵入代码重构
  2. _ed目录只做优化释放用,用后即焚
  3. _eod目录保持与主dex同步,原getDexFile()调用处已视实际场景针对_eod目录操作,做了覆盖。
  4. isDexExtracted我认为不要去判断除默认dex以外的dex是否释放。缘由:
    1)主dex释放成功时,如果存在多dex,那么extra dex也会同时被释放的。
    2)另外这里存在不确定性,如果增加判断是否有extra dex,如果没有,那有两种可能:a.插件的extra dex未被优化释放;b.插件只有single dex

另:附dex场景测试case结果


				***** multi dex 系列 *****

  • 场景一:前提条件:2个内置插件,每个插件均包含2个dex,5.0以下rom:4.4.4

1.v3_odex目录
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_plugins_v3_odex # ls
demo1-10-10-103.dex
demo1-10-10-103_eod
demo2-10-10-101.dex
demo2-10-10-101_eod
2._ed目录,放置未优化的extra dex
释放完优化后的extra dex,就会删除_ed目录
3._eod目录,放置优化后的extra dex
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_plugins_v3_odex/demo1-10-10-103_eod # ls
-rw-r--r-- u0_a78 u0_a78 2080176 2017-08-08 08:18 classes2.dex
升级或卸载时会删除


  • 场景二:前提条件:2个外置插件,每个插件均包含2个dex,5.0以下rom:4.4.4

1.p_od目录
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_od # ls
-138396329.dex
-138396329_eod
58119098.dex
58119098_eod
2._ed目录,放置未优化的extra dex
释放完优化后的extra dex,就会删除_ed目录
3._eod目录,放置优化后的extra dex
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_od/58119098_eod # ls
classes2.dex
升级或卸载时会删除


  • 场景三:前提条件:2个内置插件,每个插件均包含2个dex,5.0以上rom:7.0

1.v3_odex目录
IRO_A5_Q:/data/data/com.qihoo360.replugin.sample.host/app_plugins_v3_odex # ls
demo1-10-10-103.dex
demo2-10-10-101.dex
2._ed目录,放置未优化的extra dex
不会创建该子目录
3._eod目录,放置优化后的extra dex
不会创建该子目录


  • 场景四:前提条件:2个外置插件,每个插件均包含2个dex,5.0以上rom:7.0

1.p_od目录
IRO_A5_Q:/data/data/com.qihoo360.replugin.sample.host/app_p_od # ls
138396329.dex
58119098.dex
2._ed目录,放置未优化的extra dex
不会创建该子目录
3._eod目录,放置优化后的extra dex
不会创建该子目录


				***** single dex 系列 *****

  • 场景五:前提条件:2个内置插件,每个插件均只包含1个dex,5.0以下rom:4.4.4

1.v3_odex目录
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_plugins_v3_odex # ls
demo1-10-10-103.dex
demo2-10-10-100.dex
2._ed目录,放置未优化的extra dex
不会创建该子目录
3._eod目录,放置优化后的extra dex
不会创建该子目录


  • 场景六:前提条件:2个外置插件,每个插件均只包含1个dex,5.0以下rom:4.4.4

1.p_od目录
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_od # ls
-138397290.dex
58119098.dex
2._ed目录,放置未优化的extra dex
不会创建该子目录
3._eod目录,放置优化后的extra dex
不会创建该子目录


  • 场景七:前提条件:2个内置插件,每个插件均只包含1个dex,5.0以上rom:7.0

1.v3_odex目录
IRO_A5_Q:/data/data/com.qihoo360.replugin.sample.host/app_plugins_v3_odex # ls
demo1-10-10-103.dex demo2-10-10-100.dex
2._ed目录,放置未优化的extra dex
不会创建该子目录
3._eod目录,放置优化后的extra dex
不会创建该子目录


  • 场景八:前提条件:2个外置插件,每个插件均只包含1个dex,5.0以上rom:7.0

1.p_od目录
IRO_A5_Q:/data/data/com.qihoo360.replugin.sample.host/app_p_od # ls
-138396329.dex 58119098.dex
2._ed目录,放置未优化的extra dex
不会创建该子目录
3._eod目录,放置优化后的extra dex
不会创建该子目录

@jiongxuan
Copy link
Contributor

这么详细的测试场景描述,真的是太用心了。辛苦 @wangfuda

明天我再看下,OK后就Merge到Dev上。 👍

@wangfuda
Copy link
Contributor Author

wangfuda commented Aug 8, 2017

移除了move方法内对extra dex的move操作,缘由:

  • PluginManagerServer.move发生在同版本覆盖场景,而同版本覆盖调用move方法时,待更新的插件尚未释放dex,我认为此时做move操作是不必要的,还请指点。

另实测,在同版本升级时,因为此刻新插件尚未释放,导致PluginManagerServer.move方法内,有两处代码会报异常:

  • 在FileUtils.copyFile(newPi.getDexFile(), curPi.getDexFile())代码,提示文件找不到异常
  • FileUtils.copyFile(newPi.getNativeLibsDir(), curPi.getNativeLibsDir())提示文件找不到异常

@jiongxuan
Copy link
Contributor

  • “待更新的插件尚未释放dex”

如果“待更新插件”(无论是否为同版本覆盖)在做了install之后,用其“返回值”来作为RePlugin.preload的参数并被调用,那么这个新插件的Dex等会被释放出来,这时当进程重启后,其updateNow方法会将之前preload出来的dex等移动到正确的位置。

所以对Dex、MultiDex的Move我认为是有必要的。

  • “此刻新插件尚未释放,那么srcFile (/app_p_c/-138396329.dex') 还不存在,方法就会发生exception”

发现的很好,这确实是一个改进点,虽然对最终结果无影响(毕竟Dex不在的话,NativeLib也一定不会在),但老是出来个Error也挺讨厌的。辛苦改下~

@wangfuda
Copy link
Contributor Author

wangfuda commented Aug 9, 2017

  • 增加了同版本更新时,move方法中对extra dex 目录的copy
  • 修复了NativeLibsDir无法正确拷贝的问题
  • FileUtils工具类中,增加了对目录的拷贝方法,以便支持以上两种对目录的拷贝。该新增方法为通用文件目录拷贝操作方法,不是特殊补丁方法。

Copy link
Contributor

@jiongxuan jiongxuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

最后阶段了,还有几个小问题,辛苦修改下。

}

// get dexElements of extra dex (need to load dex first)
String optimizedDirectory = pi.getExtraOdexDir().getAbsolutePath();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里应该在循环外获取,毕竟每次获取的值是一样的。

String optimizedDirectory = pi.getExtraOdexDir().getAbsolutePath();
DexClassLoader dexClassLoader = new DexClassLoader(file.getAbsolutePath(), optimizedDirectory, optimizedDirectory, parent);
// delete extra dex, after optimized
FileUtils.forceDelete(pi.getExtraDexDir());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果有三个及以上的Dex,直接删除ExtraDir会不会导致第三个Dex无法被优化?

从流程上看:

  1. 第一个Dex在前面已经被释放了,不再赘述
  2. 第二个Dex在上面那行被释放了,然后在这里删除了整个目录,进入下一步
  3. 第三个Dex在new DexClassLoader时,发现由于之前已把目录全删掉了,所以无法再找到这个Dex,出现问题

@wangfuda
Copy link
Contributor Author

wangfuda commented Aug 9, 2017

已修改为全部dex优化释放后,再清理extra dex目录。

另:附dex场景测试case结果


				***** multidex目录测试 *****
				
			场景:外置插件:2个,插件dex数:均为3个,rom:4.4.4

  • 常规加载3个dex

1.p_od目录
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_od # ls
-138396329.dex
-138396329_eod
58119098.dex
58119098_eod
2._ed目录,放置未优化的extra dex
释放完优化后的extra dex,就会删除_ed目录
3._eod目录,放置优化后的extra dex
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_od/58119098_eod # ls
classes2.dex
classes3.dex
4.p_c目录和_eod目录,在同版本升级时的场景

  • 卸载:dex及目录会被删除

root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_od # ls

  • 同版本插件升级

测试项

p_c目录,p_n目录,p_od目录,_eod目录,在同版本覆盖升级前后的场景

背景条件

宿主host:做了测试适配,第一次进入时,尝试安装demo1,不preload;第二次进入时,尝试安装demo1,且preload;第三次进入时,尝试安装demo1,不preload;
插件demo1:3个dex:main dex 1个,extra dex 2个;so文件 5个;

测试步骤

1)进入宿主,install安装外置插件demo1,打开demo1,back键退出demo1
2)进入宿主,install安装外置插件demo1并在install success后preload demo1,打开demo1,back键退出demo1
3)手动删除demo1目录下的p_n目录中的5个so文件中的3个so文件,保留2个so文件
4)手动删除demo1目录下的p_od目录中_eod子目录中2个dex文件中的1个class2.dex文件,保留1个class3.dex文件
5)强行停止宿主应用
6)进入宿主,install安装外置插件demo1,打开demo1,back键退出demo1
7)查看宿主中的插件文件目录,结果:
so文件和多dex,均经由p_c目录正常的拷贝至或覆盖至(如原文件仍存在,则覆盖)p_n和p_od目录
且p_n目录的子目录结构正常及文件正确的全部复制或覆盖过来(5个so),以及P_od的子目录结构正常及文件正确的全部复制或覆盖过来(2个dex)

测试结果


				***** 升级前 *****

  • 升级前p_c目录:(包含大于等于2个so以上,大于等于2个extra dex的场景)

root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_c # ls
58119098
58119098.dex
58119098.jar
58119098_eod
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_c/58119098_eod # ls
classes2.dex
classes3.dex
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_c/58119098 # ls
libBaiduMapSDK_base_v4_3_2.so
libBaiduMapSDK_map_v4_3_2.so
libimagepipeline.so
liblocSDK7a.so
librealm-jni.so

  • 升级前p_n目录:故意手动删除三个so,保留两个so,测试拷贝和覆盖两种情况

root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_n/58119098 # ls <
libBaiduMapSDK_base_v4_3_2.so
libBaiduMapSDK_map_v4_3_2.so

  • 升级前p_od及_eod目录:故意手动删除一个classes2.dex,保留一个classes3.dex,测试拷贝和覆盖两种情况

root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_od # ls
58119098.dex
58119098_eod
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_od/58119098_eod # ls
classes3.dex


				***** 升级后 *****

  • 升级后p_c目录:整体被删除
  • 升级后p_n目录:

root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_n # ls
58119098
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_n/58119098 # ls
libBaiduMapSDK_base_v4_3_2.so
libBaiduMapSDK_map_v4_3_2.so
libimagepipeline.so
liblocSDK7a.so
librealm-jni.so

  • 升级后p_od及_eod目录:

root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_od # ls
58119098.dex
58119098_eod
root@vbox86p:/data/data/com.qihoo360.replugin.sample.host/app_p_od/58119098_eod # ls
classes2.dex
classes3.dex


@jiongxuan
Copy link
Contributor

这份PR可以作为范本。尤其是你写的测试场景的验证,非常的全面!辛苦 @wangfuda

我看了下,这次改完后应该没什么问题了,这就Merge。

@jiongxuan jiongxuan merged commit 926022f into Qihoo360:dev Aug 10, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants