InMemoryDexClassLoader探究

ClassLoader对于写Java的同学来说再熟悉不过了,在AndroidO中新增了一种ClassLoader名叫InMemoryDexClassLoader的ClassLoader,从名字上看像是从内存中直接加载class的意思,为了探究其原理,这几天在空余时间翻了翻它的源码,写成文章记录一下。

源码分析

首先我们在来看一下这个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的子类,从构造函数中可以看出,它确实是用来加载内存中的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);

}

接下去看它的父类的构造函数,其中直接用ByteBuffer数组构造了一个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的构造函数中,调用了makeInMemoryDexElements方法去创建Element数组,这里可以对比看一下DexClassLoader的构造方式。

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;

}

很简单,直接构造了DexFile,而在DexFile的构造函数中,调用了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转成了native层的一个MemoryMap,并且通过CreateSingleDexFileCookie去创建对应的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);

}

这个方法里调用了CreateDexFile去创建一个DexFile,并且调用ConvertDexFilesToJavaArray将其转换成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();

}

在该方法中,调用了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方法里调用了OpenCommon方法,最终会调用DexFile的构造函数。

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的文件格式对其进行内存地址的赋值(对Dex文件格式不熟悉的同学可以去看我的这篇文章),这里有一点值得注意,oat_dex_file这个值,在Open中传的就是kNoOatDexFile,也就是空。回到上面的CreateSingleDexFileCookie方法,最终调用ConvertDexFilesToJavaArray时,oat_file里传的也是null。

对比DexClassLoader

那么我们对比一下原来的DexClassLoader,DexClassLoader在Native层调用的函数是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去创建DexFie,而在OpenDexFilesFromOat方法中,会通过oat_file_assistant类的dex2oat的方式创建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);

}

类加载

当我们需要用到某个类的时候,native层是通过class_linker的defineClass去解决的,而在defineClass中,通过Dex文件结构去获取其class data区的数据,需要一个索引,我们可以在oat_file这个类中找到获取索引的方法:

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;

}

这里可以清楚的看到,如果一个DexFile中有oat_file,则通过oat_file去查,反之直接利用dex的文件结构去查。

后记

Google为什么要推出这么一个ClassLoader呢?我的猜想是由于插件化,热修复等技术的兴起,大家都在动态的往ClassLoader里做文章,而这难免会设计到DexFile的操作和dex2oat,这些东西Google都是不希望大家去直接使用的,于是就推出了这个直接在内存中操作dex,避免dex2oat的ClassLoader。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容