ClassLoader
關(guān)于ClassLoader的介紹裕寨,可以參考之前提到的:
Android動態(tài)加載基礎(chǔ) ClassLoader工作機制
另外文章會提到食呻,android中classloader都是采用了“雙親委派機制”翎冲,關(guān)于這一點可以參考:
Parents Delegation Model
簡單總結(jié)一下:
對于android中的classloader是按照以下的flow:loadClass方法在加載一個類的實例的時候:
會先查詢當(dāng)前ClassLoader實例是否加載過此類,有就返回;
如果沒有。查詢Parent是否已經(jīng)加載過此類蒙秒,如果已經(jīng)加載過,就直接返回Parent加載的類宵统;
如果繼承路線上的ClassLoader都沒有加載晕讲,才由Child執(zhí)行類的加載工作;
這樣做的好處:
首先是共享功能马澈,一些Framework層級的類一旦被頂層的ClassLoader加載過就緩存在內(nèi)存里面瓢省,以后任何地方用到都不需要重新加載。
除此之外還有隔離功能痊班,不同繼承路線上的ClassLoader加載的類肯定不是同一個類勤婚,這樣的限制避免了用戶自己的代碼冒充核心類庫的類訪問核心類庫包可見成員的情況。這也好理解涤伐,一些系統(tǒng)層級的類會在系統(tǒng)初始化的時候被加載馒胆,比如java.lang.String,如果在一個應(yīng)用里面能夠簡單地用自定義的String類把這個系統(tǒng)的String類給替換掉废亭,那將會有嚴(yán)重的安全問題国章。
DexClassLoader和PathClassLoader
關(guān)于他們兩個恩怨情仇,Android動態(tài)加載基礎(chǔ) ClassLoader工作機制 已經(jīng)有說明豆村。
直接說結(jié)論:
DexClassLoader可以加載jar/apk/dex,可以從SD卡中加載未安裝的apk骂删;
PathClassLoader只能加載系統(tǒng)中已經(jīng)安裝過的apk掌动;
通過源代碼可以知道,DexClassLoader和PathClassLoader都是繼承自BaseDexClassLoader
// DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
// 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);
}
}
這邊以DexClassLoader為例宁玫,trace一下整個ClassLoader的初始化過程粗恢。
其中有兩個問題比較重要:
- dex文件如何被load進來,產(chǎn)生了class文件
- dex文件與oat文件的轉(zhuǎn)化
DexClassLoader
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
這里的四個參數(shù)作用如下:
- dexPath:dex文件的路徑
- new File(optimizedDirectory):解析出來的dex文件存放的路徑
- libraryPath:native library的存放路徑
- parent:父classloader
BaseDexClassLoader
繼續(xù)看super類BaseDexClassLoader:
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
其中super(parnent)會call到ClassLoader的構(gòu)造函數(shù):
protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
if (parentLoader == null && !nullAllowed) {
throw new NullPointerException("parentLoader == null && !nullAllowed");
}
parent = parentLoader;
}
這邊只是簡單賦了一下parent成員變量欧瘪。
DexPathList
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
......
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
......
}
這邊的傳入?yún)?shù)可以看到眷射,會先把dexPath做切割繼續(xù)丟下去
DexPathList.makePathElements
在makePathElements中主要是構(gòu)造dexElements對象,具體的依據(jù)就是傳入的files list,即dexPath妖碉。
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
List<Element> elements = new ArrayList<>();
for (File file : files) {
......
dex = loadDexFile(file, optimizedDirectory);
......
elements.add(new Element(dir, false, zip, dex));
......
}
return elements.toArray(new Element[elements.size()]);
}
DexPathList.loadDexFile
觀察這個函數(shù)的入?yún)⒂客ィ琭ile對應(yīng)的是dex路徑,optimizedDirectory則是對應(yīng)的釋放dex文件地址欧宜。
簡單來說坐榆,src:file,dst:optimizedDirectory
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);
}
}
這邊遇到了一個optimizedPath冗茸,看看他是怎么來的(其實就是一個路徑轉(zhuǎn)換罷了):
private static String optimizedPathFor(File path,
File optimizedDirectory) {
......
File result = new File(optimizedDirectory, fileName);
return result.getPath();
}
DexFile.loadDex
靜態(tài)方法席镀,并沒有什么特別的東西,其中會new DexFile:
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
return new DexFile(sourcePathName, outputPathName, flags);
}
DexFile Construct
這里在剛開始的時候回做一個權(quán)限檢測夏漱,如果當(dāng)前的文件目錄所有者跟當(dāng)前進程的不一致豪诲,那就GG。
當(dāng)然了挂绰,這一句不是重點跛溉,重點仍舊在后面:openDexFile
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) {
...
}
} catch (ErrnoException ignored) {
// assume we'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close")
}
DexFile.openDexFile
private static Object 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);
}
code trace到這邊,基本上java層就已經(jīng)到底了扮授。因為openDexFileNative會直接call到j(luò)ni層了:
private static native Object openDexFileNative(String sourceName, String outputName, int flags);
再次來回顧一下這邊的三個參數(shù):
- String sourceName:dex文件的所在地址
- String outputName:釋放dex文件的目標(biāo)地址
- int flags:0
JNI Native
Register JNI Method
DexFile對應(yīng)的jni函數(shù)在:art/runtime/native/dalvik_system_DexFile.cc
grep大法好芳室!
static JNINativeMethod gMethods[] = {
......
NATIVE_METHOD(DexFile, openDexFileNative,
"(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/Object;"),
};
void register_dalvik_system_DexFile(JNIEnv* env) {
REGISTER_NATIVE_METHODS("dalvik/system/DexFile");
}
Macro的定義
可以看到,在這里有兩個Macro刹勃,他們究竟是什么呢堪侯?
廬山真面目在:art/runtime/jni_internal.h
#ifndef NATIVE_METHOD
#define NATIVE_METHOD(className, functionName, signature) \
{ #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) }
#endif
#define REGISTER_NATIVE_METHODS(jni_class_name) \
RegisterNativeMethods(env, jni_class_name, gMethods, arraysize(gMethods))
JNINativeMethod
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
Macro合體
NATIVE_METHOD(DexFile, openDexFileNative,
"(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/Object;"),
展開后
"openDexFileNative",
"(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/Object;",
reinterpret_cast<void*> DexFile_openDexFileNative,
很自然,就是對應(yīng)了JNINativeMethod中的name荔仁,signature以及fnPtr伍宦。
因此對應(yīng)native層的function : DexFile_openDexFileNative
DexFile_openDexFileNative
native層的代碼用到了很多C++ STL庫的相關(guān)知識,另外還有不少namespace的相關(guān)概念乏梁。
STL:C++:STL標(biāo)準(zhǔn)入門匯總次洼,三十分鐘掌握STL
namespace:詳解C++中命名空間的意義和用法
static jobject DexFile_openDexFileNative(
JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
......
ClassLinker* linker = Runtime::Current()->GetClassLinker();
......
dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);
......
}
ClassLinker是一個很重要的東西,它與art息息相關(guān)遇骑。簡單來說它的創(chuàng)建是伴隨著runtime init的時候起來的卖毁,可以認(rèn)為是每個art & 進程會維護一個對象(具體代碼還沒有分析,只是從網(wǎng)上看了一篇文章)
這里我們暫時放下ClassLinker的創(chuàng)建初始化職責(zé)權(quán)限落萎,先看看OpenDexFilesFromOat
ClassLinker::OpenDexFilesFromOat
這個函數(shù)非常大亥啦,其主要的幾個動作如下:
- 構(gòu)造OatFileAssistant對象:這個對象的主要作用是協(xié)助dex文件解析成oat文件
- 遍歷ClassLinker的成員變量:oat_files_,目的是查看當(dāng)前的dex文件是否已經(jīng)被解析成oat了
- 如果當(dāng)前dex已經(jīng)被解析成了oat文件练链,那么就跳過dex的解析翔脱,直接進入3)
- 反之,如果當(dāng)前dex文件并沒有被解析過媒鼓,那么就會走:oat_file_assistant.MakeUpToDate
- 填充dex_files對象并返回届吁,這里也有兩種情況(區(qū)分oat or dex):
- oat_file_assistant.LoadDexFiles
- DexFile::Open
其中我們需要關(guān)注的點其實只有兩點(上文劃出的重點)共兩個函數(shù):
- oat_file_assistant.MakeUpToDate
- oat_file_assistant.LoadDexFiles
std::vector<std::unique_ptr<const DexFile>> ClassLinker::OpenDexFilesFromOat(
const char* dex_location, const char* oat_location,
std::vector<std::string>* error_msgs) {
......
OatFileAssistant oat_file_assistant(dex_location, oat_location, kRuntimeISA,
!Runtime::Current()->IsAotCompiler());
......
const OatFile* source_oat_file = nullptr;
......
for (const OatFile* oat_file : oat_files_) {
CHECK(oat_file != nullptr);
if (oat_file_assistant.GivenOatFileIsUpToDate(*oat_file)) {
source_oat_file = oat_file;
break;
}
}
......
if (source_oat_file == nullptr) {
if (!oat_file_assistant.MakeUpToDate(&error_msg)) {
......
}
// Get the oat file on disk.
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
if (oat_file.get() != nullptr) {
......
if (!DexFile::MaybeDex(dex_location)) {
accept_oat_file = true;
......
}
}
if (accept_oat_file) {
source_oat_file = oat_file.release();
RegisterOatFile(source_oat_file);
}
}
}
std::vector<std::unique_ptr<const DexFile>> dex_files;
......
if (source_oat_file != nullptr) {
dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);
......
}
// Fall back to running out of the original dex file if we couldn't load any
// dex_files from the oat file.
if (dex_files.empty()) {
......
if (!DexFile::Open(dex_location, dex_location, &error_msg, &dex_files)) {
......
}
return dex_files;
}
下面的內(nèi)容會牽扯到很多oat file的解析错妖,這一塊的內(nèi)容有點龐大,我也理解的不是很透徹疚沐。
如果有興趣的話可以先閱讀一下《羅升陽:Android運行時ART加載OAT文件的過程分析》暂氯,其中對于oat file format會有進一步的了解
這一部分的知識對于理順classloader的flow來說障礙不大,但是對于了解整個art的flow來說至關(guān)重要濒旦。
OatFileAssistant::MakeUpToDate
一出4岔口的switch分支株旷,對于這邊我們關(guān)注的是kDex2OatNeeded(可以認(rèn)為是第一次加載,這樣就省略掉很多細(xì)節(jié))
另外千萬不要小看GetDexOptNeeded()尔邓,在它內(nèi)部會去讀取dex文件晾剖,檢查checksum
bool OatFileAssistant::MakeUpToDate(std::string* error_msg) {
switch (GetDexOptNeeded()) {
case kNoDexOptNeeded: return true;
case kDex2OatNeeded: return GenerateOatFile(error_msg);
case kPatchOatNeeded: return RelocateOatFile(OdexFileName(), error_msg);
case kSelfPatchOatNeeded: return RelocateOatFile(OatFileName(), error_msg);
}
UNREACHABLE();
}
由于我們這里是第一次load,所以就會進入到GenerateOatFile
OatFileAssistant::GenerateOatFile
可以看到這支函數(shù)的主要最用是call Dex2Oat梯嗽,在最終call之前會去檢查一下文件是否存在齿尽。
bool OatFileAssistant::GenerateOatFile(std::string* error_msg) {
......
std::vector<std::string> args;
args.push_back("--dex-file=" + std::string(dex_location_));
args.push_back("--oat-file=" + oat_file_name);
......
if (!OS::FileExists(dex_location_)) {
......
return false;
}
if (!Dex2Oat(args, error_msg)) {
......
}
......
return true;
}
OatFileAssistant::Dex2Oat
argv是此處的關(guān)鍵,不光會傳入dst灯节,src等基本信息循头,還會有諸如debugger,classpath等信息
bool OatFileAssistant::Dex2Oat(const std::vector<std::string>& args,
std::string* error_msg) {
......
std::vector<std::string> argv;
argv.push_back(runtime->GetCompilerExecutable());
argv.push_back("--runtime-arg");
......
return Exec(argv, error_msg);
}
至于文末的Exec炎疆,則會來到art/runtime/utils.cc
bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg)
Exec
這個函數(shù)的重點是在fork卡骂,而fork之后會在子進程執(zhí)行arg帶入的第一個參數(shù)指定的那個可執(zhí)行文件。
而在fork的父進程形入,則會繼續(xù)監(jiān)聽子進程的運行狀態(tài)并一直wait全跨,所以對于上層來說..這邊就是一個同步調(diào)用了。
bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) {
......
const char* program = arg_vector[0].c_str();
......
pid_t pid = fork();
if (pid == 0) {
......
execv(program, &args[0]);
......
} else {
......
pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
......
return true;
}
再回頭看一下argv的第一個參數(shù):argv.push_back(runtime->GetCompilerExecutable());
所以..如果非debug狀態(tài)下亿遂,我們用到可執(zhí)行文件就是:/bin/dex2oat
std::string Runtime::GetCompilerExecutable() const {
if (!compiler_executable_.empty()) {
return compiler_executable_;
}
std::string compiler_executable(GetAndroidRoot());
compiler_executable += (kIsDebugBuild ? "/bin/dex2oatd" : "/bin/dex2oat");
return compiler_executable;
}
所以到這邊浓若,我們大致就了解了dex文件是如何轉(zhuǎn)換到oat文件的,所以也就是回答了最早提出的問題2蛇数。
從Exec返回之后挪钓,我們還會繼續(xù)去檢查一下oat文件是否真的弄好了,如果一切正常耳舅,最后就會通過
RegisterOatFile(source_oat_file);
把對應(yīng)的oat file加入到ClassLinker的成員變量:oat_files_碌上,下次就不用繼續(xù)執(zhí)行dex2oat了。
OatFileAssistant::LoadDexFiles
對于這一段的flow我之前一直很不解挽放,為什么之前要把dex轉(zhuǎn)換成oat绍赛,這邊看起來又要通過oat去獲取dex?
其實簡單來想這里應(yīng)該是兩種情況:
- 首先是通過dex拿到oat文件
- 構(gòu)造dex structure給上層
所以光從字面意義來解釋這個函數(shù)辑畦,就會讓人進入一種誤區(qū),我們來看code:
std::vector<std::unique_ptr<const DexFile>> OatFileAssistant::LoadDexFiles(
const OatFile& oat_file, const char* dex_location) {
std::vector<std::unique_ptr<const DexFile>> dex_files;
// Load the primary dex file.
......
const OatFile::OatDexFile* oat_dex_file = oat_file.GetOatDexFile(
dex_location, nullptr, false);
......
std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);
......
dex_files.push_back(std::move(dex_file));
// Load secondary multidex files
for (size_t i = 1; ; i++) {
std::string secondary_dex_location = DexFile::GetMultiDexLocation(i, dex_location);
oat_dex_file = oat_file.GetOatDexFile(secondary_dex_location.c_str(), nullptr, false);
......
dex_file = oat_dex_file->OpenDexFile(&error_msg);
......
}
return dex_files;
}
這里的實現(xiàn)看起來比較復(fù)雜腿倚,但其實就是:
- 分別load primary dex和secondary multidex
- 再通過OpenDexFile方法獲得DexFile對象纯出。
因為對于oat文件來說,它可以是由多個dex產(chǎn)生的,詳細(xì)的情況可以參考老羅的文章暂筝,比如boot.oat箩言。
所以,我們需要看看這邊OpenDexFile到底做了什么:
std::unique_ptr<const DexFile> OatFile::OatDexFile::OpenDexFile(std::string* error_msg) const {
return DexFile::Open(dex_file_pointer_, FileSize(), dex_file_location_,
dex_file_location_checksum_, this, error_msg);
}
其中dex_file_pointer_是oat文件中dex數(shù)據(jù)的起始地址(DexFile Meta段)
DexFile::Open
上文提到的Open原型是:
static std::unique_ptr<const DexFile> Open(const uint8_t* base, size_t size,
const std::string& location,
uint32_t location_checksum,
const OatDexFile* oat_dex_file,
std::string* error_msg) {
return OpenMemory(base, size, location, location_checksum, nullptr, oat_dex_file, error_msg);
}
繼續(xù)trace一下:
std::unique_ptr<const DexFile> DexFile::OpenMemory(const uint8_t* base,
size_t size,
const std::string& location,
uint32_t location_checksum,
MemMap* mem_map,
const OatDexFile* oat_dex_file,
std::string* error_msg) {
CHECK_ALIGNED(base, 4); // various dex file structures must be word aligned
std::unique_ptr<DexFile> dex_file(
new DexFile(base, size, location, location_checksum, mem_map, oat_dex_file));
if (!dex_file->Init(error_msg)) {
dex_file.reset();
}
return std::unique_ptr<const DexFile>(dex_file.release());
}
最后看一下構(gòu)造函數(shù):
DexFile::DexFile(const uint8_t* base, size_t size,
const std::string& location,
uint32_t location_checksum,
MemMap* mem_map,
const OatDexFile* oat_dex_file)
: begin_(base),
size_(size),
location_(location),
location_checksum_(location_checksum),
mem_map_(mem_map),
header_(reinterpret_cast<const Header*>(base)),
string_ids_(reinterpret_cast<const StringId*>(base + header_->string_ids_off_)),
type_ids_(reinterpret_cast<const TypeId*>(base + header_->type_ids_off_)),
field_ids_(reinterpret_cast<const FieldId*>(base + header_->field_ids_off_)),
method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)),
proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)),
class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)),
find_class_def_misses_(0),
class_def_index_(nullptr),
oat_dex_file_(oat_dex_file) {
CHECK(begin_ != nullptr) << GetLocation();
CHECK_GT(size_, 0U) << GetLocation();
}
其實就是一些數(shù)據(jù)的填充焕襟,然后再打包傳回到上層(java端)陨收,丟該DexFile的mCookie對象。
:)