您的位置:首页 > 移动开发 > Android开发

在Android 4.4/5.0/8.0 测试动态加载jar/apk

2018-02-06 16:46 2571 查看
不同于java虚拟机JVM加载jar中的.class文件,Android的Dalvik/ART虚拟机加载的是jar/apk/zip中的.dex文件,因此两种环境下的ClassLoader使用也是不同的。

java虚拟机环境下一般使用java.net.URLClassLoader;

Android虚拟机环境下一般使用dalvik.system.DexClassLoader和dalvik.system.PathClassLoader;

DexClassLoader和PathClassLoader这两个ClassLoader都继承自dalvik.system.BaseDexClassLoader,在8.0之前主要区别在于DexClassLoader会提供optimizedDirectory参数,但从8.0开始optimizedDirectory已经弃用。

两者从代码加载流程上来看的区别见后边。

关于optimizedDirectory的要求:This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) (从API level 21起,官方变成了推荐使用Context.getCodeCacheDir())
to create such a directory. Do not cache optimized classes on external storage. External storage does not provide access controls necessary to protect your application from code injection attacks.(https://developer.android.google.cn/reference/dalvik/system/DexClassLoader.html)
this.getDir("abc", Context.MODE_PRIVATE)
就会在/data/data/pkg-name/下产生app_abc目录,即会有个app_前缀。

网上很多人都说如下结论:

DexClassLoader:能够加载未安装的jar/apk/dex
PathClassLoader:只能加载系统中已经安装过的apk


但实际上我的测试并非如此:

在Android 8.0 和5.0模拟器上测试发现,PathClassLoader一样可以加载未安装的jar/apk,而在4.4上才会加载失败。

从 Android 5.0 开始,ART 运行时取代 Dalvik 成为平台默认设置。

也就是说,上述结论适用于Dalvik,但在ART上PathClassLoader也可以加载非安装的jar/apk。

在4.4模拟器上测试情况如下:

使用PathClassLoader加载/sdcard/MyTools.apk失败(加载/data/data/com.wtz.testo/files/jar/MyTools.apk也报一样的错):
02-06 06:22:17.878 3019-3019/com.wtz.testo D/MainActivity: button3.onClick
02-06 06:22:17.878 3019-3019/com.wtz.testo E/dalvikvm: Dex cache directory isn't writable: /data/dalvik-cache
02-06 06:22:17.878 3019-3019/com.wtz.testo I/dalvikvm: Unable to open or create cache for /storage/sdcard/MyTools.apk (/data/dalvik-cache/storage@sdcard@MyTools.apk@classes.dex)
02-06 06:22:17.878 3019-3019/com.wtz.testo W/System.err: java.lang.ClassNotFoundException: Didn't find class "com.wtz.tools.DateTimeUtil" on path: DexPathList[[zip file "/sdcard/MyTools.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
02-06 06:22:17.888 3019-3019/com.wtz.testo W/System.err:     at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
02-06 06:22:17.888 3019-3019/com.wtz.testo W/System.err:     at java.lang.ClassLoader.loadClass(ClassLoader.java:497)
02-06 06:22:17.888 3019-3019/com.wtz.testo W/System.err:     at java.lang.ClassLoader.loadClass(ClassLoader.java:457)
02-06 06:22:17.888 3019-3019/com.wtz.testo W/System.err:     at com.wtz.testo.utils.DexTools.loadDex(DexTools.java:27)
......
02-06 06:22:17.888 3019-3019/com.wtz.testo W/System.err: 	Suppressed: java.io.IOException: unable to open DEX file
02-06 06:22:17.898 3019-3019/com.wtz.testo W/System.err:     at dalvik.system.DexFile.openDexFileNative(Native Method)
02-06 06:22:17.898 3019-3019/com.wtz.testo W/System.err:     at dalvik.system.DexFile.openDexFile(DexFile.java:296)
02-06 06:22:17.898 3019-3019/com.wtz.testo W/System.err:     at dalvik.system.DexFile.<init>(DexFile.java:80)
02-06 06:22:17.898 3019-3019/com.wtz.testo W/System.err:     at dalvik.system.DexFile.<init>(DexFile.java:59)
02-06 06:22:17.898 3019-3019/com.wtz.testo W/System.err:     at dalvik.system.DexPathList.loadDexFile(DexPathList.java:263)
02-06 06:22:17.898 3019-3019/com.wtz.testo W/System.err:     at dalvik.system.DexPathList.makeDexElements(DexPathList.java:230)
02-06 06:22:17.898 3019-3019/com.wtz.testo W/System.err:     at dalvik.system.DexPathList.<init>(DexPathList.java:112)
02-06 06:22:17.898 3019-3019/com.wtz.testo W/System.err:     at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48)
02-06 06:22:17.898 3019-3019/com.wtz.testo W/System.err:     at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:65)
02-06 06:22:17.898 3019-3019/com.wtz.testo W/System.err:     at com.wtz.testo.utils.DexTools.loadDex(DexTools.java:23)
02-06 06:22:17.898 3019-3019/com.wtz.testo W/System.err: 		... 15 more

使用DexClassLoader加载/sdcard/MyTools.apk成功:
02-06 06:30:50.318 10700-10700/com.wtz.testo D/MainActivity: button3.onClick
02-06 06:30:50.368 10700-10700/com.wtz.testo D/dalvikvm: DexOpt: --- BEGIN 'MyTools.apk' (bootstrap=0) ---
02-06 06:30:50.508 10700-10700/com.wtz.testo D/dalvikvm: DexOpt: --- END 'MyTools.apk' (success) ---
02-06 06:30:50.508 10700-10700/com.wtz.testo D/dalvikvm: DEX prep '/storage/sdcard/MyTools.apk': unzip in 16ms, rewrite 145ms
02-06 06:30:50.508 10700-10700/com.wtz.testo D/MainActivity: testLoadClassJar2...DateTimeUtil: 2018-02-06_06:30:50
02-06 06:31:06.848 10700-10700/com.wtz.testo D/MainActivity: button3.onClick
02-06 06:31:06.848 10700-10700/com.wtz.testo D/MainActivity: testLoadClassJar2...DateTimeUtil: 2018-02-06_06:31:06

查看优化目录“abc”:
root@generic_x86:/data/data/com.wtz.testo/app_abc # ls
MyTools.dex


在5.0模拟器上测试情况如下:

使用PathClassLoader加载/sdcard/MyTools.apk成功:
02-06 06:50:45.354 2931-2931/com.wtz.testo D/MainActivity: button3.onClick
02-06 06:50:45.437 2931-2931/com.wtz.testo D/MainActivity: testLoadClassJar2...DateTimeUtil: 2018-02-06_06:50:45

使用DexClassLoader加载/sdcard/MyTools.apk成功:
02-06 06:54:55.503 6689-6689/com.wtz.testo D/MainActivity: button3.onClick
02-06 06:54:56.647 6689-6689/com.wtz.testo D/MainActivity: testLoadClassJar2...DateTimeUtil: 2018-02-06_06:54:56

查看优化目录“abc”:
root@generic_x86_64:/data/data/com.wtz.testo/app_abc # ls
MyTools.dex


在8.0模拟器上测试情况如下:

在/data/data/pkg-name/下如预期DexClassLoader能正常加载jar,入参optimizedDirectory并没有产生对应的优化目录,但加载后jar同目录下生成了几个目录和文件如下:
/data/data/com.wtz.testo/
└─files
└─jar
│-test_class_jar.jar
│-test_class_jar.jar.prof
└─oat
└─x86_64
test_class_jar.odex
test_class_jar.vdex


也可以在/sdcard目录下正常加载jar,只是别忘了在AndroidManifest.xml和代码动态申请权限两个地方申请对应的外部存储读权限。否则会在dalvik.system.DexFile.openDexFileNative(Native Method)报错,对应到上层会报“ClassNotFoundException”:
02-01 17:38:08.930 27999-27999/com.wtz.testo W/System.err: java.lang.ClassNotFoundException: Didn't find class "com.test.java2.Student" on path: DexPathList[[zip file "/sdcard/test_class_jar.jar"],nativeLibraryDirectories=[/system/lib64, /vendor/lib64]]
02-01 17:38:08.930 27999-27999/com.wtz.testo W/System.err:     at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:93)
......
02-01 17:38:08.932 27999-27999/com.wtz.testo W/System.err: 	Suppressed: java.io.IOException: No original dex files found for dex location /sdcard/test_class_jar.jar
02-01 17:38:08.932 27999-27999/com.wtz.testo W/System.err:     at dalvik.system.DexFile.openDexFileNative(Native Method)

但还是有一个问题,在连续操作几次加载/sdcard中的jar后,系统报错“vdex
wasn't explicitly flushed before destruction”并且崩溃了:
01 20:41:55.314 30045-30045/com.wtz.testo D/MainActivity: button3.onClick
02-01 20:41:55.332 30045-30045/com.wtz.testo I/System.out: a = test loadJar
02-01 20:41:56.902 30045-30045/com.wtz.testo D/MainActivity: button3.onClick
02-01 20:41:56.913 30045-30045/com.wtz.testo I/System.out: a = test loadJar
02-01 20:41:58.284 30045-30045/com.wtz.testo D/MainActivity: button3.onClick
02-01 20:41:58.288 30045-30045/com.wtz.testo E/zygote64: File /storage/emulated/0/oat/x86_64/test_class_jar.vdex wasn't explicitly flushed before destruction.
02-01 20:41:58.288 30045-30045/com.wtz.testo E/zygote64: File /storage/emulated/0/oat/x86_64/test_class_jar.vdex wasn't explicitly closed before destruction.
02-01 20:41:58.288 30045-30045/com.wtz.testo A/zygote64: fd_file.cc:72] Check failed: guard_state_ >= GuardState::kClosed (guard_state_=Base, GuardState::kClosed=Closed)
02-01 20:41:58.454 30045-30052/com.wtz.testo W/zygote64: Suspending all threads took: 158.280ms
02-01 20:41:58.505 30045-30045/com.wtz.testo A/zygote64: runtime.cc:492] Runtime aborting...
02-01 20:41:58.505 30045-30045/com.wtz.testo A/zygote64: runtime.cc:492] Aborting thread:
02-01 20:41:58.505 30045-30045/com.wtz.testo A/zygote64: runtime.cc:492] "main" prio=10 tid=1 Native
02-01 20:41:58.505 30045-30045/com.wtz.testo A/zygote64: runtime.cc:492]   | group="" sCount=0 dsCount=0 flags=0 obj=0x72818670 self=0x7fb3978bea00
02-01 20:41:58.505 30045-30045/com.wtz.testo A/zygote64: runtime.cc:492]   | sysTid=30045 nice=-10 cgrp=default sched=0/0 handle=0x7fb39c7faa08
02-01 20:41:58.505 30045-30045/com.wtz.testo A/zygote64: runtime.cc:492]   | state=R schedstat=( 165171692 506149647 230 ) utm=14 stm=2 core=0 HZ=100
02-01 20:41:58.505 30045-30045/com.wtz.testo A/zygote64: runtime.cc:492]   | stack=0x7ffeb3de1000-0x7ffeb3de3000 stackSize=8MB
02-01 20:41:58.505 30045-30045/com.wtz.testo A/zygote64: runtime.cc:492]   | held mutexes= "abort lock"
02-01 20:41:58.505 30045-30045/com.wtz.testo A/zygote64: runtime.cc:492]   native: #00 pc 0000000000435cc5  /system/lib64/libart.so (_ZN3art15DumpNativeStackERNSt3__113basic_ostreamIcNS0_11char_traitsIcEEEEiP12BacktraceMapPKcPNS_9ArtMethodEPv+213)


这个目前没查到原因,但在/data/data/com.wtz.testo/files下加载jar共30多次和关闭程序重新打开后加载都不会出错,上述错说明在/sdcard上加载jar还是存在问题的,实际使用也不应该直接从/sdcard上加载jar。

使用PathClassLoader在/data/data/pkg-name/下和/sdcard/下加载也都能成功。

===========================================

DexClassLoader和PathClassLoader代码加载流程区别:

(以下以5.0源码为例)

/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}

/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}


两者都继承BaseDexClassLoader,只是入参optimizedDirectory是否为空而已,从8.0开始,在DexPathList入口参数optimizedDirectory直接传入了null:

/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}

/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
在DexPathList构造方法中会执行:
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);

对于jar/apk/zip会调用:
dex = loadDexFile(file, optimizedDirectory);

方法loadDexFile定义如下:
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}


对于有optimizedDirectory参数而调用DexFile.loadDex会最终调用到如下:

/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException {
return new DexFile(sourcePathName, outputPathName, flags);
}


无论是new DexFile(file)还是new DexFile(sourcePathName, outputPathName, flags),最终都会调用到同样的方法,只是参数outputName是否为空而已:

/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
private static long openDexFile(String sourceName, String outputName, int flags) throws IOException {
// Use absolute paths to enable the use of relative paths when testing on host.
return openDexFileNative(
new File(sourceName).getAbsolutePath(),(outputName == null) ? null :  new File(outputName).getAbsolutePath(), flags);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ClassLoader