InMemoryDexClassLoader探究

ClassLoader對于寫Java的同學(xué)來說再熟悉不過了,在AndroidO中新增了一種ClassLoader名叫InMemoryDexClassLoader的ClassLoader,從名字上看像是從內(nèi)存中直接加載class的意思,為了探究其原理,這幾天在空余時(shí)間翻了翻它的源碼,寫成文章記錄一下。

源碼分析

首先我們在來看一下這個(gè)InMemoryDexClassLoader的源碼:

```

public final class InMemoryDexClassLoader extends BaseDexClassLoader {

?? /**

? ? * Create an in-memory DEX class loader with the given dex buffers.

? ? *

? ? * @param dexBuffers array of buffers containing DEX files between

? ? * ? ? ? ? ? ? ? ? ? ? ? <tt>buffer.position()</tt> and <tt>buffer.limit()</tt>.

? ? * @param parent the parent class loader for delegation.

? ? * @hide

? ? */

?? public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) {

? ? ?? super(dexBuffers, parent);

?? }

```

?? /**

? ? * Creates a new in-memory DEX class loader.

? ? *

? ? * @param dexBuffer buffer containing DEX file contents between

? ? * ? ? ? ? ? ? ? ? ? ? ? <tt>buffer.position()</tt> and <tt>buffer.limit()</tt>.

? ? * @param parent the parent class loader for delegation.

? ? */

?? public InMemoryDexClassLoader(ByteBuffer dexBuffer, ClassLoader parent) {

? ? ?? this(new ByteBuffer[] { dexBuffer }, parent);

?? }

}

```

非常的簡短,它是BaseDexClassLoader的子類,從構(gòu)造函數(shù)中可以看出,它確實(shí)是用來加載內(nèi)存中的dex文件的。

public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {

?? // TODO We should support giving this a library search path maybe.

?? super(parent);

?? this.pathList = new DexPathList(this, dexFiles);

}

接下去看它的父類的構(gòu)造函數(shù),其中直接用ByteBuffer數(shù)組構(gòu)造了一個(gè)DexPathList。

public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) {

?? if (definingContext == null) {

? ? ?? throw new NullPointerException("definingContext == null");

?? }

?? if (dexFiles == null) {

? ? ?? throw new NullPointerException("dexFiles == null");

?? }

?? if (Arrays.stream(dexFiles).anyMatch(v -> v == null)) {

? ? ?? throw new NullPointerException("dexFiles contains a null Buffer!");

?? }

?

?? this.definingContext = definingContext;

?? // TODO It might be useful to let in-memory dex-paths have native libraries.

?? this.nativeLibraryDirectories = Collections.emptyList();

?? this.systemNativeLibraryDirectories =

? ? ? ? ?? splitPaths(System.getProperty("java.library.path"), true);

?? this.nativeLibraryPathElements = makePathElements(this.systemNativeLibraryDirectories);

?

?? ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();

?? this.dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions);

?? if (suppressedExceptions.size() > 0) {

? ? ?? this.dexElementsSuppressedExceptions =

? ? ? ? ? ? ?? suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);

?? } else {

? ? ?? dexElementsSuppressedExceptions = null;

?? }

}

DexPathList的構(gòu)造函數(shù)中,調(diào)用了makeInMemoryDexElements方法去創(chuàng)建Element數(shù)組,這里可以對比看一下DexClassLoader的構(gòu)造方式。

private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? List<IOException> suppressedExceptions) {

?? Element[] elements = new Element[dexFiles.length];

?? int elementPos = 0;

?? for (ByteBuffer buf : dexFiles) {

? ? ?? try {

? ? ? ? ?? DexFile dex = new DexFile(buf);

? ? ? ? ?? elements[elementPos++] = new Element(dex);

? ? ?? } catch (IOException suppressed) {

? ? ? ? ?? System.logE("Unable to load dex file: " + buf, suppressed);

? ? ? ? ?? suppressedExceptions.add(suppressed);

? ? ?? }

?? }

?? if (elementPos != elements.length) {

? ? ?? elements = Arrays.copyOf(elements, elementPos);

?? }

?? return elements;

}

很簡單,直接構(gòu)造了DexFile,而在DexFile的構(gòu)造函數(shù)中,調(diào)用了createCookieWithArray方法,走到了native層。

static jobject DexFile_createCookieWithArray(JNIEnv* env,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jclass,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jbyteArray buffer,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jint start,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jint end) {

?? std::unique_ptr<MemMap> dex_mem_map(AllocateDexMemoryMap(env, start, end));

?? if (dex_mem_map == nullptr) {

? ? ?? DCHECK(Thread::Current()->IsExceptionPending());

? ? ?? return 0;

?? }

?

?? auto destination = reinterpret_cast<jbyte*>(dex_mem_map.get()->Begin());

?? env->GetByteArrayRegion(buffer, start, end - start, destination);

?? return CreateSingleDexFileCookie(env, std::move(dex_mem_map));

}

在該方法中,將java的buffer轉(zhuǎn)成了native層的一個(gè)MemoryMap,并且通過CreateSingleDexFileCookie去創(chuàng)建對應(yīng)的cookie。

static jobject CreateSingleDexFileCookie(JNIEnv* env, std::unique_ptr<MemMap> data) {

?? std::unique_ptr<const DexFile> dex_file(CreateDexFile(env, std::move(data)));

?? if (dex_file.get() == nullptr) {

? ? ?? DCHECK(env->ExceptionCheck());

? ? ?? return nullptr;

?? }

?? std::vector<std::unique_ptr<const DexFile>> dex_files;

?? dex_files.push_back(std::move(dex_file));

?? return ConvertDexFilesToJavaArray(env, nullptr, dex_files);

}

這個(gè)方法里調(diào)用了CreateDexFile去創(chuàng)建一個(gè)DexFile,并且調(diào)用ConvertDexFilesToJavaArray將其轉(zhuǎn)換成java層需要的cookie。

static const DexFile* CreateDexFile(JNIEnv* env, std::unique_ptr<MemMap> dex_mem_map) {

? ? ?? std::string location = StringPrintf("Anonymous-DexFile@%p-%p",

? ? ?? dex_mem_map->Begin(),

? ? ?? dex_mem_map->End());

? ? ?? std::string error_message;

? ? ?? std::unique_ptr<const DexFile> dex_file(DexFile::Open(location,0,std::move(dex_mem_map),

? ? ? ? ?? /* verify */ true,

? ? ? ? ?? /* verify_location */ true,

? ? ? ? ?? &error_message));

? ? ?? if (dex_file == nullptr) {

? ? ? ? ?? ScopedObjectAccess soa(env);

? ? ? ? ?? ThrowWrappedIOException("%s", error_message.c_str());

? ? ? ? ?? return nullptr;

? ? ?? }

?

? ? ?? if (!dex_file->DisableWrite()) {

? ? ? ? ?? ScopedObjectAccess soa(env);

? ? ? ? ?? ThrowWrappedIOException("Failed to make dex file read-only");

? ? ? ? ?? return nullptr;

? ? ?? }

?

? ? ?? return dex_file.release();

}

在該方法中,調(diào)用了DexFile的Open方法,并且傳入了MemoryMap。

std::unique_ptr<const DexFile> DexFile::Open(const std::string& location,

? ? ?? uint32_t location_checksum,

? ? ?? std::unique_ptr<MemMap> map,

? ? ?? bool verify,

? ? ?? bool verify_checksum,

? ? ?? std::string* error_msg) {

? ? ?? ScopedTrace trace(std::string("Open dex file from mapped-memory ") + location);

? ? ?? CHECK(map.get() != nullptr);

?

? ? ?? if (map->Size() < sizeof(DexFile::Header)) {

? ? ? ? ?? *error_msg = StringPrintf(

? ? ? ? ?? "DexFile: failed to open dex file '%s' that is too short to have a header",

? ? ? ? ?? location.c_str());

? ? ?? return nullptr;

? ? ?? }

?

? ? ?? std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),

? ? ?? map->Size(),

? ? ?? location,

? ? ?? location_checksum,

? ? ?? kNoOatDexFile,

? ? ?? verify,

? ? ?? verify_checksum,

? ? ?? error_msg);

? ? ?? if (dex_file != nullptr) {

? ? ? ? ?? dex_file->mem_map_.reset(map.release());

? ? ?? }

? ? ?? return dex_file;

}

Open方法里調(diào)用了OpenCommon方法,最終會調(diào)用DexFile的構(gòu)造函數(shù)。

DexFile::DexFile(const uint8_t* base,

? ? ?? size_t size,

? ? ?? const std::string& location,

? ? ?? uint32_t location_checksum,

? ? ?? const OatDexFile* oat_dex_file)

? ? ?? : begin_(base),

? ? ?? size_(size),

? ? ?? location_(location),

? ? ?? location_checksum_(location_checksum),

? ? ?? 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_)),

? ? ?? method_handles_(nullptr),

? ? ?? num_method_handles_(0),

? ? ?? call_site_ids_(nullptr),

? ? ?? num_call_site_ids_(0),

? ? ?? oat_dex_file_(oat_dex_file) {

? ? ?? CHECK(begin_ != nullptr) << GetLocation();

? ? ?? CHECK_GT(size_, 0U) << GetLocation();

? ? ?? // Check base (=header) alignment.

? ? ?? // Must be 4-byte aligned to avoid undefined behavior when accessing

? ? ?? // any of the sections via a pointer.

? ? ?? CHECK_ALIGNED(begin_, alignof(Header));

?

? ? ?? InitializeSectionsFromMapList();

}

這里可以非常清晰看到,就是通過Dex的文件格式對其進(jìn)行內(nèi)存地址的賦值(對Dex文件格式不熟悉的同學(xué)可以去看我的這篇文章),這里有一點(diǎn)值得注意,oat_dex_file這個(gè)值,在Open中傳的就是kNoOatDexFile,也就是空。回到上面的CreateSingleDexFileCookie方法,最終調(diào)用ConvertDexFilesToJavaArray時(shí),oat_file里傳的也是null。

對比DexClassLoader

那么我們對比一下原來的DexClassLoader,DexClassLoader在Native層調(diào)用的函數(shù)是openDexFileNative。

static jobject DexFile_openDexFileNative(JNIEnv* env,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jclass,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jstring javaSourceName,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jstring javaOutputName ATTRIBUTE_UNUSED,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jint flags ATTRIBUTE_UNUSED,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jobject class_loader,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jobjectArray dex_elements) {

? ?? ScopedUtfChars sourceName(env, javaSourceName);

? ?? if (sourceName.c_str() == nullptr) {

? ? ? ?? return 0;

? ?? }

?

? ?? Runtime* const runtime = Runtime::Current();

? ?? ClassLinker* linker = runtime->GetClassLinker();

? ?? std::vector<std::unique_ptr<const DexFile>> dex_files;

? ?? std::vector<std::string> error_msgs;

const OatFile* oat_file = nullptr;

?

? ?? dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),

? ? ? ? ? ?? class_loader,

? ? ? ? ? ?? dex_elements,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /*out*/ &oat_file,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /*out*/ &error_msgs);

?

? ?? if (!dex_files.empty()) {

? ? ? ?? jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);

? ? ? ?? if (array == nullptr) {

? ? ? ? ? ?? ScopedObjectAccess soa(env);

? ? ? ? ? ?? for (auto& dex_file : dex_files) {

? ? ? ? ? ? ? ?? if (linker->IsDexFileRegistered(soa.Self(), *dex_file)) {

? ? ? ? ? ? ? ? ? ?? dex_file.release();

? ? ? ? ? ? ? ?? }

? ? ? ? ? ?? }

? ? ? ?? }

? ? ? ?? return array;

? ?? } else {

? ? ? ?? ScopedObjectAccess soa(env);

? ? ? ?? CHECK(!error_msgs.empty());

? ? ? ?? // The most important message is at the end. So set up nesting by going forward, which will

? ? ? ?? // wrap the existing exception as a cause for the following one.

? ? ? ?? auto it = error_msgs.begin();

? ? ? ?? auto itEnd = error_msgs.end();

? ? ? ?? for ( ; it != itEnd; ++it) {

? ? ? ? ? ?? ThrowWrappedIOException("%s", it->c_str());

? ? ? ?? }

?

? ? ? ?? return nullptr;

? ?? }

}

其中通過OatFileManager的OpenDexFilesFromOat去創(chuàng)建DexFie,而在OpenDexFilesFromOat方法中,會通過oat_file_assistant類的dex2oat的方式創(chuàng)建oat文件。

bool OatFileAssistant::Dex2Oat(const std::vector<std::string>& args,

?? std::string* error_msg) {

?? Runtime* runtime = Runtime::Current();

?? std::string image_location = ImageLocation();

?? if (image_location.empty()) {

? ? ?? *error_msg = "No image location found for Dex2Oat.";

? ? ?? return false;

?? }

?

?? std::vector<std::string> argv;

?? argv.push_back(runtime->GetCompilerExecutable());

?? argv.push_back("--runtime-arg");

?? argv.push_back("-classpath");

?? argv.push_back("--runtime-arg");

?? std::string class_path = runtime->GetClassPathString();

?? if (class_path == "") {

? ? ?? class_path = OatFile::kSpecialSharedLibrary;

?? }

?? argv.push_back(class_path);

?? if (runtime->IsJavaDebuggable()) {

? ? ?? argv.push_back("--debuggable");

?? }

?? runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv);

?

?? if (!runtime->IsVerificationEnabled()) {

? ? ?? argv.push_back("--compiler-filter=verify-none");

?? }

?

?? if (runtime->MustRelocateIfPossible()) {

? ? ?? argv.push_back("--runtime-arg");

? ? ?? argv.push_back("-Xrelocate");

?? } else {

? ? ?? argv.push_back("--runtime-arg");

? ? ?? argv.push_back("-Xnorelocate");

?? }

?

?? if (!kIsTargetBuild) {

? ? ?? argv.push_back("--host");

?? }

?

?? argv.push_back("--boot-image=" + image_location);

?

?? std::vector<std::string> compiler_options = runtime->GetCompilerOptions();

?? argv.insert(argv.end(), compiler_options.begin(), compiler_options.end());

?

?? argv.insert(argv.end(), args.begin(), args.end());

?

?? std::string command_line(android::base::Join(argv, ' '));

?? return Exec(argv, error_msg);

}

類加載

當(dāng)我們需要用到某個(gè)類的時(shí)候,native層是通過class_linker的defineClass去解決的,而在defineClass中,通過Dex文件結(jié)構(gòu)去獲取其class data區(qū)的數(shù)據(jù),需要一個(gè)索引,我們可以在oat_file這個(gè)類中找到獲取索引的方法:

const DexFile::ClassDef* OatFile::OatDexFile::FindClassDef(const DexFile& dex_file,

? ? ?? const char* descriptor,

? ? ?? size_t hash) {

? ? ?? const OatFile::OatDexFile* oat_dex_file = dex_file.GetOatDexFile();

? ? ?? DCHECK_EQ(ComputeModifiedUtf8Hash(descriptor), hash);

? ? ?? if (LIKELY((oat_dex_file != nullptr) && (oat_dex_file->GetTypeLookupTable() != nullptr))) {

? ? ? ? ?? const uint32_t class_def_idx = oat_dex_file->GetTypeLookupTable()->Lookup(descriptor, hash);

? ? ? ? ?? return (class_def_idx != DexFile::kDexNoIndex) ? &dex_file.GetClassDef(class_def_idx) : nullptr;

? ? ?? }

? ? ?? // Fast path for rare no class defs case.

? ? ?? const uint32_t num_class_defs = dex_file.NumClassDefs();

? ? ?? if (num_class_defs == 0) {

? ? ? ? ?? return nullptr;

? ? ?? }

? ? ?? const DexFile::TypeId* type_id = dex_file.FindTypeId(descriptor);

? ? ?? if (type_id != nullptr) {

? ? ? ? ?? dex::TypeIndex type_idx = dex_file.GetIndexForTypeId(*type_id);

? ? ? ? ?? return dex_file.FindClassDef(type_idx);

? ? ?? }

? ? ?? return nullptr;

}

這里可以清楚的看到,如果一個(gè)DexFile中有oat_file,則通過oat_file去查,反之直接利用dex的文件結(jié)構(gòu)去查。

后記

Google為什么要推出這么一個(gè)ClassLoader呢?我的猜想是由于插件化,熱修復(fù)等技術(shù)的興起,大家都在動(dòng)態(tài)的往ClassLoader里做文章,而這難免會設(shè)計(jì)到DexFile的操作和dex2oat,這些東西Google都是不希望大家去直接使用的,于是就推出了這個(gè)直接在內(nèi)存中操作dex,避免dex2oat的ClassLoader。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容