0x2-1 dex加载过程

创建一个DexClassLoader来加载dex

dexClassLoader = new DexClassLoader(apkPath, getFilesDir().getAbsolutePath(), null, getClassLoader());

DexClassLoader的构造方法:

 public class DexClassLoader extends BaseDexClassLoader {
                // dexPath:是加载apk/dex/jar的路径           // optimizedDirectory:是优化dex后得到的.odex文件的输出路径
                // libraryPath:是加载的时候需要用到的so库     // parent:给DexClassLoader指定父加载器
                public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
                    super(dexPath, new File(optimizedDirectory), libraryPath, parent);
                }
            }

可以看到它调用的是父类的构造函数,所以直接来看BaseDexClassLoader的构造函数

public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
                super(parent);
                this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
            }

创建了一个DexPathList实例,下面来看看DexPathList的构造函数

private final Element[] dexElements;
            public DexPathList(ClassLoader definingContext, String dexPath,
                    String libraryPath, File optimizedDirectory) {
                ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
                this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                                   suppressedExceptions);
            }

Element主要是用来保存dex或资源文件路径的类。 一些app方法超过65535个的时候,就需要使用多dex的方案进行处理
static class Element { private final DexFile dexFile;  }
spliteDexPath方法是传递进来的dex路径字符串分割转换成File对象数组,然后调用makeDexElements方法加载dex、jar、apk或zip结尾的文件

makeDexElements方法:

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();
            
                  // 如果是一个dex文件
                  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);
                  }
                  // 如果是一个apk或者jar或者zip文件
                  } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)|| name.endsWith(ZIP_SUFFIX)) {
                        zip = file;
                        try {
                              // 1、调用loadDexFile加载dex文件,得到一个DexFile对象
                              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);
                  }
                  
                  // 2、把DexFile对象封装到Element对象中,然后将Element对象加入Element数组
                  if ((zip != null) || (dex != null)) {
                        elements.add(new Element(file, false, zip, dex));
                  }
            }
            
            return elements.toArray(new Element[elements.size()]);
      }

重点看看loadDexFile加载dex文件:

private static DexFile loadDexFile(File file, File optimizedDirectory)
                    throws IOException {
                if (optimizedDirectory == null) {
                    // 没有设置保存优化dex目录
                    return new DexFile(file);
                } else {
                    String optimizedPath = optimizedPathFor(file, optimizedDirectory);
                    return DexFile.loadDex(file.getPath(), optimizedPath, 0);
                }
            }     

在DexFile.loadDex方法,其实调用的也是创建一个DexFile对象,所以我们需要关注的是DexFile对象的创建方法。

static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException {
                      return new DexFile(sourcePathName, outputPathName, flags);
        }

        public DexFile(String fileName) throws IOException {
                mCookie = openDexFile(fileName, null, 0);
                mFileName = fileName;
                guard.open("close");
        }

可以看到,它调用openDexFile来进行dex的加载。

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);
          }

整个过程如下:
在这里插入图片描述

然后调用到c++层:
下面进入从native层进行分析,openDexFileNative方法对应的native层方法就是dalvik_system_DexFile.cpp文件的Dalvik_dalvik_system_DexFile_openDexFileNative方法。

static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,
JValue* pResult){
StringObject* sourceNameObj = (StringObject*) args[0];
StringObject* outputNameObj = (StringObject*) args[1];
DexOrJar* pDexOrJar = NULL;
JarFile* pJarFile;
RawDexFile* pRawDexFile;
char* sourceName;
char* outputName;

sourceName = dvmCreateCstrFromString(sourceNameObj);
if (outputNameObj != NULL)
outputName = dvmCreateCstrFromString(outputNameObj);
else
outputName = NULL;

// 1、尝试把它当做一个后缀为.dex的DEX文件进行打开,得到RawDexFile结构数据
// 2、如果打开失败,则把它当做一个包含有classes.dex文件的Zip文件进行打开,得到JarFile结构数据
if (hasDexExtension(sourceName)
&& dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = true;
pDexOrJar->pRawDexFile = pRawDexFile;
pDexOrJar->pDexMemory = NULL;
} else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
ALOGV("Opening DEX file '%s' (Jar)", sourceName);

pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = false;
pDexOrJar->pJarFile = pJarFile;
pDexOrJar->pDexMemory = NULL;
}

if (pDexOrJar != NULL) {
pDexOrJar->fileName = sourceName;
// 3、把pDexOrJar这个结构体中的内容加到gDvm中的userDexFile结构的hash表中,以便dalvik以后的查找
addToDexFileTable(pDexOrJar);
} else {
free(sourceName);
}

free(outputName);
RETURN_PTR(pDexOrJar);}

1、后缀为.dex的DEX文件处理,得到RawDexFile结构数据

/* See documentation comment in header. */int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,
RawDexFile** ppRawDexFile, bool isBootstrap){
DvmDex* pDvmDex = NULL;
char* cachedName = NULL;
int result = -1;
int dexFd = -1;
int optFd = -1;
u4 modTime = 0;
u4 adler32 = 0;
size_t fileSize = 0;
bool newFile = false;
bool locked = false;
// 1、打开.dex文件
dexFd = open(fileName, O_RDONLY);
if (dexFd < 0) goto bail;
// 2、验证dex版本信息,并且获取adler32值存放到adler32里面
if (verifyMagicAndGetAdler32(dexFd, &adler32) < 0) {
ALOGE("Error with header for %s", fileName);
goto bail;
}
// 3、得到dex文件的修改时间和文件大小,分别保存在变量modTime和filesize中
if (getModTimeAndSize(dexFd, &modTime, &fileSize) < 0) {
ALOGE("Error with stat for %s", fileName);
goto bail;
}

// 4、如果优化dex后的输出目录为空,则会生成一个目录,否则odexOutputName为输出目录
if (odexOutputName == NULL) {
cachedName = dexOptGenerateCacheFileName(fileName, NULL);
if (cachedName == NULL)
goto bail;
} else {
cachedName = strdup(odexOutputName);
}
// 5、调用函数dexOptCreateEmptyHeader,构造了一个DexOptHeader结构体,写入fd并返回
optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime,
adler32, isBootstrap, &newFile, /*createIfMissing=*/true);

locked = true;

/*
* If optFd points to a new file (because there was no cached
* version, or the cached version was stale), generate the
* optimized DEX. The file descriptor returned is still locked,
* and is positioned just past the optimization header.
*/
// 如果成功生成了opt头
//优化dex文件
if (newFile) {
u8 startWhen, copyWhen, endWhen;
bool result;
off_t dexOffset;

dexOffset = lseek(optFd, 0, SEEK_CUR);
result = (dexOffset > 0);

if (result) {
startWhen = dvmGetRelativeTimeUsec();
// 6、将dex文件中的内容拷贝到当前odex文件,从dexOffset开始
result = copyFileToFile(optFd, dexFd, fileSize) == 0;
copyWhen = dvmGetRelativeTimeUsec();
}

if (result) {
//7、对dex文件进行优化,并将优化数据写入odex文件
result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,
fileName, modTime, adler32, isBootstrap);
}


endWhen = dvmGetRelativeTimeUsec();
}

// 8、将odex文件数据转换为pDvmDex结构
if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) {
ALOGI("Unable to map cached %s", fileName);
goto bail;
}

if (locked) {
/* unlock the fd */
if (!dvmUnlockCachedDexFile(optFd)) {
/* uh oh -- this process needs to exit or we'll wedge the system */
ALOGE("Unable to unlock DEX file");
goto bail;
}
locked = false;
}

ALOGV("Successfully opened '%s'", fileName);
//9、分配内存,填充结构体 RawDexFile
*ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile));
(*ppRawDexFile)->cacheFileName = cachedName;
(*ppRawDexFile)->pDvmDex = pDvmDex;
cachedName = NULL; // don't free it below
result = 0;

bail:
free(cachedName);
if (dexFd >= 0) {
close(dexFd);
}
if (optFd >= 0) {
if (locked)
(void) dvmUnlockCachedDexFile(optFd);
close(optFd);
}
return result;}

这种方式的处理思路如下图所示:
在这里插入图片描述
2、包含有classes.dex文件的Zip文件打开方式,得到JarFile结构数据

int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
JarFile** ppJarFile, bool isBootstrap){
ZipArchive archive;
DvmDex* pDvmDex = NULL;
char* cachedName = NULL;
bool archiveOpen = false;
bool locked = false;
int fd = -1;
int result = -1;

// 1、打开zip文件,存放在archive中
if (dexZipOpenArchive(fileName, &archive) != 0)
goto bail;
archiveOpen = true;

dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));

fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
if (fd >= 0) {
ALOGV("Using alternate file (odex) for %s ...", fileName);
if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
ALOGE("%s odex has stale dependencies", fileName);
free(cachedName);
cachedName = NULL;
close(fd);
fd = -1;
goto tryArchive;
} else {
ALOGV("%s odex has good dependencies", fileName);
}
} else {
ZipEntry entry;

tryArchive:
//2、从压缩包里找到Dex文件,然后打开这个文件
entry = dexZipFindEntry(&archive, kDexInJarName);
if (entry != NULL) {
bool newFile = false;

// 如果优化dex后的输出目录为空,则会生成一个目录,否则odexOutputName为输出目录
if (odexOutputName == NULL) {
cachedName = dexOptGenerateCacheFileName(fileName,
kDexInJarName);
if (cachedName == NULL)
goto bail;
} else {
cachedName = strdup(odexOutputName);
}
// 3、调用函数dexOptCreateEmptyHeader,构造了一个DexOptHeader结构体,写入fd并返回
fd = dvmOpenCachedDexFile(fileName, cachedName,
dexGetZipEntryModTime(&archive, entry),
dexGetZipEntryCrc32(&archive, entry),
isBootstrap, &newFile, /*createIfMissing=*/true);
locked = true;

// 如果成功生成了opt头
if (newFile) {
u8 startWhen, extractWhen, endWhen;
bool result;
off_t dexOffset;

dexOffset = lseek(fd, 0, SEEK_CUR);
result = (dexOffset > 0);

if (result) {
startWhen = dvmGetRelativeTimeUsec();
// 4、将dex文件中的内容拷贝到当前odex文件
result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;
extractWhen = dvmGetRelativeTimeUsec();
}
if (result) {
//5、对dex文件进行优化,并将优化数据写入odex文件
result = dvmOptimizeDexFile(fd, dexOffset,
dexGetZipEntryUncompLen(&archive, entry),
fileName,
dexGetZipEntryModTime(&archive, entry),
dexGetZipEntryCrc32(&archive, entry),
isBootstrap);
}

endWhen = dvmGetRelativeTimeUsec();
}
} else {
goto bail;
}
}

// 6、将odex文件数据转换为pDvmDex结构
if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {
ALOGI("Unable to map %s in %s", kDexInJarName, fileName);
goto bail;
}

if (locked) {
/* unlock the fd */
if (!dvmUnlockCachedDexFile(fd)) {
/* uh oh -- this process needs to exit or we'll wedge the system */
ALOGE("Unable to unlock DEX file");
goto bail;
}
locked = false;
}

ALOGV("Successfully opened '%s' in '%s'", kDexInJarName, fileName);
// 7、分配内存,填充结构体 RawDexFile
*ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));
(*ppJarFile)->archive = archive;
(*ppJarFile)->cacheFileName = cachedName;
(*ppJarFile)->pDvmDex = pDvmDex;
cachedName = NULL; // don't free it below
result = 0;

bail:
if (archiveOpen && result != 0)
dexZipCloseArchive(&archive);
free(cachedName);
if (fd >= 0) {
if (locked)
(void) dvmUnlockCachedDexFile(fd);
close(fd);
}
return result;}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值