Android虚拟机的类加载机制
Hotspot虚拟机中由ClassLoader完成类的加载。而Android虚拟机不能加载.class字节码文件,.dex才是Android虚拟机能够识别并加载的文件。Android虚拟机使用PathClassLoader和DexClassLoader两种加载器。
PathClassLoader和DexClassLoader的区别
通常我们知道PathClassLoader只能加载已安装的应用,而DexClassLoader支持加载本地的apk、zip、jar、dex,下面从源码分析两者区别。
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);
}
}
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
...
}
由两者的构造方法可以看出,PathClassLoader相比DexClassLoader传给父类BaseDexClassLoader 的optimizedDirectory参数为NULL。
具体在DexPathList中有什么影响呢:
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
ArrayList suppressedExceptions = new ArrayList();
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
这里注意makeDexElements函数第一个参数splitDexPath(dexPath) , splitDexPath函数将dexPath字符串以":"分割为多个路径,也就是PathClassLoader和DexClassLoader都支持在构造方法中传入以":"分割多文件路径的参数。简而言之,支持多个文件的加载。
再细看makeDexElements函数:
private static Element[] makeDexElements(ArrayList files, File optimizedDirectory,
ArrayList suppressedExceptions) {
ArrayList elements = new ArrayList();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
File zip = null;
DexFile dex = null;
String name = file.getName();
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {
zip = file;
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
suppressedExceptions.add(suppressed);
}
} else if (file.isDirectory()) {
elements.add(new Element(file, true, null, null));
} else {
System.logW("Unknown file type for: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
再看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);
}
}
由PathClassLoader传入的optimizedDirectory为空,因此执行 new DexFile(file):
public DexFile(File file) throws IOException {
this(file.getPath());
}
public DexFile(String fileName) throws IOException {
mCookie = openDexFile(fileName, null, 0);
mFileName = fileName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie);
}
到此最后会执行openDexFile(fileName, null, 0)。
由于DexClassLoader通常传入一个开发者指定的optimizedDirectory,如果传入为null则跟PathClassLoader的构造无差别,因此看DexFile.loadDex(file.getPath(), optimizedPath, 0)函数:
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
/*
* TODO: we may want to cache previously-opened DexFile objects.
* The cache would be synchronized with close(). This would help
* us avoid mapping the same DEX more than once when an app
* decided to open it multiple times. In practice this may not
* be a real issue.
*/
return new DexFile(sourcePathName, outputPathName, flags);
}
private DexFile(String sourceName, String outputName, int flags) throws IOException {
if (outputName != null) {
try {
String parent = new File(outputName).getParent();
if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
throw new IllegalArgumentException("Optimized data directory " + parent
+ " is not owned by the current user. Shared storage cannot protect"
+ " your application from code injection attacks.");
}
} catch (ErrnoException ignored) {
// assume we'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie);
}
DexClassLoader最后也是执行openDexFile(sourceName, outputName, flags)。
再看openDexFile函数:
private static int openDexFile(String sourceName, String outputName,
int flags) throws IOException {
return openDexFileNative(new File(sourceName).getCanonicalPath(),
(outputName == null) ? null : new File(outputName).getCanonicalPath(),
flags);
}
native private static int openDexFileNative(String sourceName, String outputName,
int flags) throws IOException;
到这一步可以知道DexClassLoader和PathClassLoader的构造最后都会执行到openDexFileNative这个Native函数,所不同的是PathClassLoader传入outputName的必为NULL。
下面以http://androidxref.com/4.4_r1/xref/art/runtime/native/dalvik_system_DexFile.cc的源码继续分析:
static jint DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() == NULL) {
return 0;
}
std::string dex_location(sourceName.c_str());
NullableScopedUtfChars outputName(env, javaOutputName);
if (env->ExceptionCheck()) {
return 0;
}
ScopedObjectAccess soa(env);
uint32_t dex_location_checksum;
if (!DexFile::GetChecksum(dex_location, &dex_location_checksum)) {
LOG(WARNING) << "Failed to compute checksum: " << dex_location;
ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
soa.Self()->ThrowNewExceptionF(throw_location, "Ljava/io/IOException;",
"Unable to get checksum of dex file: %s", dex_location.c_str());
return 0;
}
ClassLinker* linker = Runtime::Current()->GetClassLinker();
const DexFile* dex_file;
if (outputName.c_str() == NULL) {
dex_file = linker->FindDexFileInOatFileFromDexLocation(dex_location, dex_location_checksum);
} else {
std::string oat_location(outputName.c_str());
dex_file = linker->FindOrCreateOatFileForDexLocation(dex_location, dex_location_checksum, oat_location);
}
if (dex_file == NULL) {
LOG(WARNING) << "Failed to open dex file: " << dex_location;
ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
soa.Self()->ThrowNewExceptionF(throw_location, "Ljava/io/IOException;",
"Unable to open dex file: %s", dex_location.c_str());
return 0;
}
return static_cast(reinterpret_cast(dex_file));
}
注意 if (outputName.c_str() == NULL)这个判断,我们知道PathClassLoader传入的javaOutputName一定为NULL。因此会执行FindDexFileInOatFileFromDexLocation函数,依然以Android4.4为例,函数定义在http://androidxref.com/4.4_r1/xref/art/runtime/class_linker.cc:
const DexFile* ClassLinker::FindDexFileInOatFileFromDexLocation(const std::string& dex_location,
uint32_t dex_location_checksum) {
WriterMutexLock mu(Thread::Current(), dex_lock_);
const OatFile* open_oat_file = FindOpenedOatFileFromDexLocation(dex_location,
dex_location_checksum);
if (open_oat_file != NULL) {
return open_oat_file->GetOatDexFile(dex_location, &dex_location_checksum)->OpenDexFile();
}
// Look for an existing file next to dex. for example, for
// /foo/bar/baz.jar, look for /foo/bar/baz.odex.
std::string odex_filename(OatFile::DexFilenameToOdexFilename(dex_location));
UniquePtr oat_file(FindOatFileFromOatLocationLocked(odex_filename));
if (oat_file.get() != NULL) {
uint32_t dex_location_checksum;
if (!DexFile::GetChecksum(dex_location, &dex_location_checksum)) {
// If no classes.dex found in dex_location, it has been stripped, assume oat is up-to-date.
// This is the common case in user builds for jar's and apk's in the /system directory.
const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, NULL);
CHECK(oat_dex_file != NULL) << odex_filename << " " << dex_location;
RegisterOatFileLocked(*oat_file);
return oat_dex_file->OpenDexFile();
}
const DexFile* dex_file = VerifyAndOpenDexFileFromOatFile(oat_file.release(),
dex_location,
dex_location_checksum);
if (dex_file != NULL) {
return dex_file;
}
}
// Look for an existing file in the dalvik-cache, validating the result if found
// not found in /foo/bar/baz.odex? try /data/dalvik-cache/foo@[email protected]@classes.dex
std::string cache_location(GetDalvikCacheFilenameOrDie(dex_location));
oat_file.reset(FindOatFileFromOatLocationLocked(cache_location));
if (oat_file.get() != NULL) {
uint32_t dex_location_checksum;
if (!DexFile::GetChecksum(dex_location, &dex_location_checksum)) {
LOG(WARNING) << "Failed to compute checksum: " << dex_location;
return NULL;
}
const DexFile* dex_file = VerifyAndOpenDexFileFromOatFile(oat_file.release(),
dex_location,
dex_location_checksum);
if (dex_file != NULL) {
return dex_file;
}
if (TEMP_FAILURE_RETRY(unlink(cache_location.c_str())) != 0) {
PLOG(FATAL) << "Failed to remove obsolete oat file from " << cache_location;
}
}
LOG(INFO) << "Failed to open oat file from " << odex_filename << " or " << cache_location << ".";
// Try to generate oat file if it wasn't found or was obsolete.
std::string oat_cache_filename(GetDalvikCacheFilenameOrDie(dex_location));
return FindOrCreateOatFileForDexLocationLocked(dex_location, dex_location_checksum, oat_cache_filename);
}
从函数的命名我们可以得知函数是用来寻找生成的.oat文件的,.oat文件在ART虚拟机下安装时就会生成,对于未安装的APK,是不会生成这个文件的。再看后续执行的FindOrCreateOatFileForDexLocation函数,由PathClassLoader传入的 oat_cache_filename对于未安装的APK是空指针:
const DexFile* ClassLinker::FindOrCreateOatFileForDexLocationLocked(const std::string& dex_location,
uint32_t dex_location_checksum,
const std::string& oat_location) {
// We play a locking game here so that if two different processes
// race to generate (or worse, one tries to open a partial generated
// file) we will be okay. This is actually common with apps that use
// DexClassLoader to work around the dex method reference limit and
// that have a background service running in a separate process.
ScopedFlock scoped_flock;
if (!scoped_flock.Init(oat_location)) {
LOG(ERROR) << "Failed to open locked oat file: " << oat_location;
return NULL;
}
// Check if we already have an up-to-date output file
const DexFile* dex_file = FindDexFileInOatLocation(dex_location,
dex_location_checksum,
oat_location);
if (dex_file != NULL) {
return dex_file;
}
// Generate the output oat file for the dex file
VLOG(class_linker) << "Generating oat file " << oat_location << " for " << dex_location;
if (!GenerateOatFile(dex_location, scoped_flock.GetFile().Fd(), oat_location)) {
LOG(ERROR) << "Failed to generate oat file: " << oat_location;
return NULL;
}
const OatFile* oat_file = OatFile::Open(oat_location, oat_location, NULL,
!Runtime::Current()->IsCompiler());
if (oat_file == NULL) {
LOG(ERROR) << "Failed to open generated oat file: " << oat_location;
return NULL;
}
RegisterOatFileLocked(*oat_file);
const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, &dex_location_checksum);
if (oat_dex_file == NULL) {
LOG(ERROR) << "Failed to find dex file " << dex_location
<< " (checksum " << dex_location_checksum
<< ") in generated oat file: " << oat_location;
return NULL;
}
const DexFile* result = oat_dex_file->OpenDexFile();
CHECK_EQ(dex_location_checksum, result->GetLocationChecksum())
<< "dex_location=" << dex_location << " oat_location=" << oat_location << std::hex
<< " dex_location_checksum=" << dex_location_checksum
<< " DexFile::GetLocationChecksum()=" << result->GetLocationChecksum();
return result;
}
因为oat_location未NULL,所以会返回NULL,在回到openDexFileNative:
if (dex_file == NULL) {
LOG(WARNING) << "Failed to open dex file: " << dex_location;
ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
soa.Self()->ThrowNewExceptionF(throw_location, "Ljava/io/IOException;",
"Unable to open dex file: %s", dex_location.c_str());
return 0;
}
最终会返回失败,并抛出异常。
而DexClassLoader因为指定的输出文件目录不为空,虚拟机可以生成优化的OAT文件。当然如果传入非法的目录一样是会失败的。
测试PathClassLoader与DexClassLoader
从上文我们知道PathClassLoader不能加载非已安装的apk文件,而Dex则可以动态加载类并输出到指定优化路径。
测试思路,将一个dex文件放在assets下,在apk启动后由两种类加载器加载dex。
首先创建一个module,并在其中定义类A:
package dev.mars.app2;
public class A {
public static void printName(){
Log.e("dev_mars",A.class.getName()+" printName()");
}
}
由AS build APK生成apk,从中取出classes.dex,放到主module下的assets文件夹下。注意这里我去除了supportv4、v7等不需要的包。
在主module下创建自定义Application:
package dev.mars.androidclassloadertest;
import android.app.Application;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
public class MyApplication extends Application{
private static final String LOG_TAG = "dev_mars";
private static void LOGE(String str){
Log.e(LOG_TAG,str);
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
String dexFilePath = getDexFilePath();
LOGE("dexFilePath : "+dexFilePath);
String optimziedFolderPath = getDir("optimized_dex",MODE_PRIVATE).getAbsolutePath();
LOGE("optimziedFolderPath : "+optimziedFolderPath);
if(dexFilePath!=null) {
loadDexFileByPathClassLoader(dexFilePath);
//loadDexFileByDexClassLoader(dexFilePath,optimziedFolderPath);
}
}
private void loadDexFileByDexClassLoader(String dexFilePath, String optimziedFolderPath) {
DexClassLoader dexClassLoader = new DexClassLoader(dexFilePath,optimziedFolderPath,null,getClassLoader());
try {
Class A = dexClassLoader.loadClass(" dev.mars.app2.A");
executeMethod(A);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private void executeMethod(Class A) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
Object a = A.newInstance();
Method printName = A.getDeclaredMethod("printName");
printName.invoke(a);
LOGE("loadDexFileByPathClassLoader finish ");
}
private void loadDexFileByPathClassLoader(String dexFilePath) {
LOGE("loadDexFileByPathClassLoader : "+dexFilePath);
PathClassLoader pathClassLoader = new PathClassLoader(dexFilePath,null,getClassLoader());
try {
Class A = pathClassLoader.loadClass(" dev.mars.app2.A");
executeMethod(A);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private String getDexFilePath(){
String fileName = "classes.dex";
String dexFilePath = null;
AssetManager assetManager = getAssets();
try {
InputStream is = assetManager.open(fileName);
File outputFolderFile = getDir("output",MODE_PRIVATE);
dexFilePath = outputFolderFile.getAbsolutePath()+"/"+fileName;
FileOutputStream fs = new FileOutputStream(dexFilePath);
byte[] buffer =new byte[2048];
int readSize =0;
while (readSize!=-1){
readSize = is.read(buffer);
if(readSize>0){
fs.write(buffer,0,readSize);
}
}
fs.flush();
fs.close();
is.close();
Log.e("dev_mars","dexFilePath : "+dexFilePath);
return dexFilePath;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
由loadDexFileByPathClassLoader函数的执行结果log来看:
05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest E/dev_mars: loadDexFileByPathClassLoader : /data/data/dev.mars.androidclassloadertest/app_output/classes.dex
05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest E/dalvikvm: Dex cache directory isn't writable: /data/dalvik-cache
05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest I/dalvikvm: Unable to open or create cache for /data/data/dev.mars.androidclassloadertest/app_output/classes.dex (/data/dalvik-cache/data@[email protected]@[email protected])
05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest I/dalvikvm: Zip is good, but no classes.dex inside, and no valid .odex file in the same directory
05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest E/System: Unable to load dex file: /data/data/dev.mars.androidclassloadertest/app_output/classes.dex
05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest E/System: java.io.IOException: unable to open DEX file
at dalvik.system.DexFile.openDexFileNative(Native Method)
at dalvik.system.DexFile.openDexFile(DexFile.java:296)
at dalvik.system.DexFile.(DexFile.java:80)
at dalvik.system.DexFile.(DexFile.java:59)
at dalvik.system.DexPathList.loadDexFile(DexPathList.java:263)
at dalvik.system.DexPathList.makeDexElements(DexPathList.java:221)
at dalvik.system.DexPathList.(DexPathList.java:112)
at dalvik.system.BaseDexClassLoader.(BaseDexClassLoader.java:48)
at dalvik.system.PathClassLoader.(PathClassLoader.java:65)
at dev.mars.androidclassloadertest.MyApplication.loadDexFileByPathClassLoader(MyApplication.java:42)
at dev.mars.androidclassloadertest.MyApplication.attachBaseContext(MyApplication.java:36)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.tools.fd.runtime.BootstrapApplication.attachBaseContext(BootstrapApplication.java:251)
at android.app.Application.attach(Application.java:181)
at android.app.Instrumentation.newApplication(Instrumentation.java:991)
at android.app.Instrumentation.newApplication(Instrumentation.java:975)
at android.app.LoadedApk.makeApplication(LoadedApk.java:511)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4317)
at android.app.ActivityThread.access$1500(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1256)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5017)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
at dalvik.system.NativeStart.main(Native Method)
其中:
Dex cache directory isn't writable: /data/dalvik-cache
Unable to open or create cache for /data/data/dev.mars.androidclassloadertest/app_output/classes.dex (/data/dalvik-cache/data@[email protected]@[email protected])
直接说明了PathClassLoader加载dex失败的原因,因为目标未被安装找不到缓存的的执行文件,/data/dalvik-cache又不能被写入,所以抛出异常。因此PathClassLoader只能用于加载已安装的APK。
再来看执行loadDexFileByDexClassLoader函数的log:
05-19 00:40:55.510 19851-19851/dev.mars.androidclassloadertest E/dev_mars: dexFilePath : /data/data/dev.mars.androidclassloadertest/app_output/classes.dex
05-19 00:40:55.510 19851-19851/dev.mars.androidclassloadertest E/dev_mars: dexFilePath : /data/data/dev.mars.androidclassloadertest/app_output/classes.dex
05-19 00:40:55.510 19851-19851/dev.mars.androidclassloadertest E/dev_mars: optimziedFolderPath : /data/data/dev.mars.androidclassloadertest/app_optimized_dex
05-19 00:40:55.510 19851-19851/dev.mars.androidclassloadertest I/dalvikvm: DexOpt: source file mod time mismatch (591e76a5 vs 591e7757)
05-19 00:40:55.510 19851-19851/dev.mars.androidclassloadertest D/dalvikvm: ODEX file is stale or bad; removing and retrying (/data/data/dev.mars.androidclassloadertest/app_optimized_dex/classes.dex)
05-19 00:40:55.520 19851-19851/dev.mars.androidclassloadertest D/dalvikvm: DexOpt: --- BEGIN 'classes.dex' (bootstrap=0) ---
05-19 00:40:55.580 19851-19851/dev.mars.androidclassloadertest D/dalvikvm: DexOpt: --- END 'classes.dex' (success) ---
05-19 00:40:55.580 19851-19851/dev.mars.androidclassloadertest D/dalvikvm: DEX prep '/data/data/dev.mars.androidclassloadertest/app_output/classes.dex': copy in 0ms, rewrite 53ms
05-19 00:40:55.580 19851-19851/dev.mars.androidclassloadertest E/dev_mars: dev.mars.app2.A printName()
05-19 00:40:55.580 19851-19851/dev.mars.androidclassloadertest E/dev_mars: loadDexFileByPathClassLoader finish
可以看到dex被成功加载并用反射技术成功创建了A的实例a,并调用printName方法打印了log。遍历下optimized文件夹看看多了什么:
05-19 00:43:08.390 21786-21786/dev.mars.androidclassloadertest E/dev_mars: /data/data/dev.mars.androidclassloadertest/app_optimized_dex/classes.dex
可以看到使用DexClassLoader加载dex生成了优化的dex,猜想等到第二次加载该dex就不需要再次生成优化的dex,直接加载优化的dex从而提高执行速度。
总结
- PathClassLoader和DexClassLoader都是Android虚拟机支持的类加载器,所不同的是PathClassLoader只能加载已安装的APK(在Native层如果发现加载的是非已安装的APK会抛出异常),并且作为APP默认的类加载器。
- DexClassLoader可以加载dex、apk、jar、zip等格式的插件,这些插件不需要已安装。
- 传入的dexPath既可以是单个dex文件的路径,也可以是多个dex文件路径以":"分割合并的字符串。