Android虛擬機(jī)的類加載機(jī)制
Hotspot虛擬機(jī)中由ClassLoader完成類的加載。而Android虛擬機(jī)不能加載.class字節(jié)碼文件臊岸,.dex才是Android虛擬機(jī)能夠識(shí)別并加載的文件伞芹。Android虛擬機(jī)使用PathClassLoader和DexClassLoader兩種加載器凳谦。
PathClassLoader和DexClassLoader的區(qū)別
通常我們知道PathClassLoader只能加載已安裝的應(yīng)用,而DexClassLoader支持加載本地的apk挎扰、zip囚霸、jar腰根、dex,下面從源碼分析兩者區(qū)別邮辽。
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);
}
...
}
由兩者的構(gòu)造方法可以看出唠雕,PathClassLoader相比DexClassLoader傳給父類BaseDexClassLoader 的optimizedDirectory參數(shù)為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<IOException> suppressedExceptions = new ArrayList<IOException>();
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函數(shù)第一個(gè)參數(shù)splitDexPath(dexPath) , splitDexPath函數(shù)將dexPath字符串以":"分割為多個(gè)路徑吨述,也就是PathClassLoader和DexClassLoader都支持在構(gòu)造方法中傳入以":"分割多文件路徑的參數(shù)岩睁。簡(jiǎn)而言之,支持多個(gè)文件的加載揣云。
再細(xì)看makeDexElements函數(shù):
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();
/*
* 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函數(shù):
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為空捕儒,因此執(zhí)行 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);
}
到此最后會(huì)執(zhí)行openDexFile(fileName, null, 0)。
由于DexClassLoader通常傳入一個(gè)開發(fā)者指定的optimizedDirectory,如果傳入為null則跟PathClassLoader的構(gòu)造無(wú)差別,因此看DexFile.loadDex(file.getPath(), optimizedPath, 0)函數(shù):
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最后也是執(zhí)行openDexFile(sourceName, outputName, flags)邓夕。
再看openDexFile函數(shù):
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的構(gòu)造最后都會(huì)執(zhí)行到openDexFileNative這個(gè)Native函數(shù)刘莹,所不同的是PathClassLoader傳入outputName的必為NULL。
下面以http://androidxref.com/4.4_r1/xref/art/runtime/native/dalvik_system_DexFile.cc的源碼繼續(xù)分析:
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<jint>(reinterpret_cast<uintptr_t>(dex_file));
}
注意 if (outputName.c_str() == NULL)這個(gè)判斷焚刚,我們知道PathClassLoader傳入的javaOutputName一定為NULL点弯。因此會(huì)執(zhí)行FindDexFileInOatFileFromDexLocation函數(shù),依然以Android4.4為例,函數(shù)定義在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<const OatFile> 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@bar@baz.jar@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);
}
從函數(shù)的命名我們可以得知函數(shù)是用來(lái)尋找生成的.oat文件的矿咕,.oat文件在ART虛擬機(jī)下安裝時(shí)就會(huì)生成抢肛,對(duì)于未安裝的APK,是不會(huì)生成這個(gè)文件的碳柱。再看后續(xù)執(zhí)行的FindOrCreateOatFileForDexLocation函數(shù),由PathClassLoader傳入的 oat_cache_filename對(duì)于未安裝的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;
}
因?yàn)閛at_location未NULL捡絮,所以會(huì)返回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;
}
最終會(huì)返回失敗莲镣,并拋出異常福稳。
而DexClassLoader因?yàn)橹付ǖ妮敵鑫募夸洸粸榭眨摂M機(jī)可以生成優(yōu)化的OAT文件瑞侮。當(dāng)然如果傳入非法的目錄一樣是會(huì)失敗的的圆。
測(cè)試PathClassLoader與DexClassLoader
從上文我們知道PathClassLoader不能加載非已安裝的apk文件鼓拧,而Dex則可以動(dòng)態(tài)加載類并輸出到指定優(yōu)化路徑。
測(cè)試思路略板,將一個(gè)dex文件放在assets下毁枯,在apk啟動(dòng)后由兩種類加載器加載dex。
首先創(chuàng)建一個(gè)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下創(chuàng)建自定義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函數(shù)的執(zhí)行結(jié)果log來(lái)看:
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@data@dev.mars.androidclassloadertest@app_output@classes.dex)
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.<init>(DexFile.java:80)
at dalvik.system.DexFile.<init>(DexFile.java:59)
at dalvik.system.DexPathList.loadDexFile(DexPathList.java:263)
at dalvik.system.DexPathList.makeDexElements(DexPathList.java:221)
at dalvik.system.DexPathList.<init>(DexPathList.java:112)
at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48)
at dalvik.system.PathClassLoader.<init>(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@data@dev.mars.androidclassloadertest@app_output@classes.dex)
直接說(shuō)明了PathClassLoader加載dex失敗的原因,因?yàn)槟繕?biāo)未被安裝找不到緩存的的執(zhí)行文件娱节,/data/dalvik-cache又不能被寫入挠蛉,所以拋出異常。因此PathClassLoader只能用于加載已安裝的APK肄满。
再來(lái)看執(zhí)行l(wèi)oadDexFileByDexClassLoader函數(shù)的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被成功加載并用反射技術(shù)成功創(chuàng)建了A的實(shí)例a谴古,并調(diào)用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生成了優(yōu)化的dex稠歉,猜想等到第二次加載該dex就不需要再次生成優(yōu)化的dex掰担,直接加載優(yōu)化的dex從而提高執(zhí)行速度。
總結(jié)
- PathClassLoader和DexClassLoader都是Android虛擬機(jī)支持的類加載器怒炸,所不同的是PathClassLoader只能加載已安裝的APK(在Native層如果發(fā)現(xiàn)加載的是非已安裝的APK會(huì)拋出異常)带饱,并且作為APP默認(rèn)的類加載器。
- DexClassLoader可以加載dex阅羹、apk勺疼、jar、zip等格式的插件捏鱼,這些插件不需要已安裝执庐。
- 傳入的dexPath既可以是單個(gè)dex文件的路徑,也可以是多個(gè)dex文件路徑以":"分割合并的字符串导梆。