最近Android社区里关于hotpatch的好文章非常多,看了其中的几篇,发现它们都提到了一个问题,就是[现有的hotpatch方案在ART虚拟机上会出现内存移位],随着Android5.0以上机器的不断普及,这个问题也会被越来越多的遇到,所以这篇文章就带大家看看和这个问题相关的Android中的几个知识,希望大家看完会有收获~
说到上面说的那个问题,其实可以细分到Android hotpatch中的class文件patch这一块,而市面上现有的hotpatch方案中,使用[动态插入Element方式]的框架都会遇到这个问题,所以我们就从这种方式开始谈起。
熟悉hotpatch的同学都知道,通过这种方式完成需求的大志步骤就是调用DexPathList的makeDexElements方法将patch的dex动态的插入到DexPathList的成员变量dexElements,也就是Element数组的最前面,从而达到在类加载的时候[先加载patch的dex中的class,如果没有,再加载app的dex中的class]的效果。那么要搞清楚上面抛出的那个问题,我们就要从这里入手。
Android运行时ART加载OAT文件的过程分析
Android运行时ART加载类和方法的过程分析
Android运行时ART执行类方法的过程分析
首先为什么要讲这个呢,因为我们在接下去的源码分析中会用到。我们知道在dalvik虚拟机时代,存在一个odex文件,就是优化过的dex文件,虚拟机会自动的帮我们优化每一个dex文件以便提升效率,所以在dalvik虚拟机上存在一个[优化]的过程,从而就导出了hotpatch中最“著名”的一个问题——类校验,这个具体的我在这里不细讲了,因为这不是这篇文章的重点,有兴趣的同学可以自行google。但是呢dalvik虚拟机还是不足以确保Android系统的流畅,因为整个Android的应用都是基于虚拟机的,正因如此,所以就算Android将java文件优化成了dex文件,进而又优化成了odex并辅助以JIT,还是避免不了[解释]这一层面,[解释]通俗的讲就是将java代码解释成机器指令的过程。所以出现了ART,Android RunTime。在ART的时代,每一个Android应用在安装的时候都会存在一个aot操作(Ahead of time),用于生成oat文件,这个oat文件呢,既包含转化前的dex文件,又包含机器指令,所以我们的应用在运行的时候可以免去[解释]这一层而直接加载机器指令。最后说一点,其实ART中还是需要解释器的,因为我们可以手动开启ART的解释模式。
前面说过,我们是通过DexPathList的makeDexElements去创建一个patch的Element的,所以我们先看这个方法。
private static Element[] makeDexElements(ArrayList<File> files,
File optimizedDirectory) {
ArrayList<Element> elements = new ArrayList<Element>();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
ZipFile 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)) {
try {
zip = new ZipFile(file);
} catch (IOException ex) {
/*
* Note: ZipException (a subclass of IOException)
* might get thrown by the ZipFile constructor
* (e.g. if the file isn't actually a zip/jar
* file).
*/
System.logE("Unable to open zip file: " + file, ex);
}
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ignored) {
/*
* IOException might get thrown "legitimately" by
* the DexFile constructor if the zip file turns
* out to be resource-only (that is, no
* classes.dex file in it). Safe to just ignore
* the exception here, and let dex == null.
*/
}
} else {
System.logW("Unknown file type for: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
这个方法比较长,但是逻辑很好理解,就是区分我们传进来的参数是一个dex文件还是一个apk或者zip这样的压缩文件,在hotpatch的场景中我们一般传的都是后者,于是就通过loadDexFile方法去生成了一个DexFile,并且生成一个新的Element添加到成员变量elements中。下面让我们看loadDexFile方法。
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);
}
}
如果我们传的optimizedDirectory不为空,就获取一个optimizedPath并且调用DexFile.loadDex方法,否则直接new出一个DexFile,而DexFile.loadDex方法其实在内部也是直接new出一个DexFile的。所以我们直接看DexFile的构造函数。
可以看到其中调用了openDexFile,并且返回了一个cookie值,这个值大家可以看做索引,之后我们查找一个类的时候就需要用到它,所以,我们接下去要看的就是openDexFile这个方法,而这个方法是一个native方法。
友情提示,接下去的代码全都是C++的,请大家喝口水,缓解下焦躁的心情,慢慢看。
static jlong DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() == NULL) {
return 0;
}
NullableScopedUtfChars outputName(env, javaOutputName);
if (env->ExceptionCheck()) {
return 0;
}
ClassLinker* linker = Runtime::Current()->GetClassLinker();
std::unique_ptr<std::vector<const DexFile*>> dex_files(new std::vector<const DexFile*>());
std::vector<std::string> error_msgs;
bool success = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs,
dex_files.get());
if (success || !dex_files->empty()) {
// In the case of non-success, we have not found or could not generate the oat file.
// But we may still have found a dex file that we can use.
return static_cast<jlong>(reinterpret_cast<uintptr_t>(dex_files.release()));
} else {
// The vector should be empty after a failed loading attempt.
DCHECK_EQ(0U, dex_files->size());
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 0;
}
}
其中最重要的逻辑就是创建了一个dex_files的vector,并且调用class_linker的OpenDexFilesFromOat去从oat文件真正的打开一个Dex文件。从本文的第一小节我们可以知道,Android应用在ART虚拟机上,当它安装的时候都会生成一个oat文件,所以这里就是从这个oat文件中寻找对应的dex文件。下面让我们来看看OpenDexFilesFromOat函数。
bool ClassLinker::OpenDexFilesFromOat(const char* dex_location, const char* oat_location,std::vector<std::string>* error_msgs,std::vector<const DexFile*>* dex_files) {
// 1) Check whether we have an open oat file.
// This requires a dex checksum, use the "primary" one.
uint32_t dex_location_checksum;
uint32_t* dex_location_checksum_pointer = &dex_location_checksum;
bool have_checksum = true;
std::string checksum_error_msg;
if (!DexFile::GetChecksum(dex_location, dex_location_checksum_pointer, &checksum_error_msg)) {
// This happens for pre-opted files since the corresponding dex files are no longer on disk.
dex_location_checksum_pointer = nullptr;
have_checksum = false;
}
bool needs_registering = false;
const OatFile::OatDexFile* oat_dex_file = FindOpenedOatDexFile(oat_location, dex_location,
dex_location_checksum_pointer);
std::unique_ptr<const OatFile> open_oat_file(
oat_dex_file != nullptr ? oat_dex_file->GetOatFile() : nullptr);
// 2) If we do not have an open one, maybe there's one on disk already.
// In case the oat file is not open, we play a locking game here so
// that if two different processes race to load and register or 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 (open_oat_file.get() == nullptr) {
if (oat_location != nullptr) {
// Can only do this if we have a checksum, else error.
if (!have_checksum) {
error_msgs->push_back(checksum_error_msg);
return false;
}
std::string error_msg;
// We are loading or creating one in the future. Time to set up the file lock.
if (!scoped_flock.Init(oat_location, &error_msg)) {
error_msgs->push_back(error_msg);
return false;
}
// TODO Caller specifically asks for this oat_location. We should honor it. Probably?
open_oat_file.reset(FindOatFileInOatLocationForDexFile(dex_location, dex_location_checksum,oat_location, &error_msg));
if (open_oat_file.get() == nullptr) {
std::string compound_msg = StringPrintf("Failed to find dex file '%s' in oat location '%s': %s",dex_location, oat_location, error_msg.c_str());
VLOG(class_linker) << compound_msg;
error_msgs->push_back(compound_msg);
}
} else {
// TODO: What to lock here?
bool obsolete_file_cleanup_failed;
open_oat_file.reset(FindOatFileContainingDexFileFromDexLocation(dex_location,
dex_location_checksum_pointer,kRuntimeISA,error_msgs,&obsolete_file_cleanup_failed));
// There's no point in going forward and eventually try to regenerate the
// file if we couldn't remove the obsolete one. Mostly likely we will fail
// with the same error when trying to write the new file.
// TODO: should we maybe do this only when we get permission issues? (i.e. EACCESS).
if (obsolete_file_cleanup_failed) {
return false;
}
}
needs_registering = true;
}
// 3) If we have an oat file, check all contained multidex files for our dex_location.
// Note: LoadMultiDexFilesFromOatFile will check for nullptr in the first argument.
bool success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location,
dex_location_checksum_pointer,false, error_msgs, dex_files);
if (success) {
const OatFile* oat_file = open_oat_file.release(); // Avoid deleting it.
if (needs_registering) {
// We opened the oat file, so we must register it.
RegisterOatFile(oat_file);
}
// If the file isn't executable we failed patchoat but did manage to get the dex files.
return oat_file->IsExecutable();
} else {
if (needs_registering) {
// We opened it, delete it.
open_oat_file.reset();
} else {
open_oat_file.release(); // Do not delete open oat files.
}
}
// 4) If it's not the case (either no oat file or mismatches), regenerate and load.
// Need a checksum, fail else.
if (!have_checksum) {
error_msgs->push_back(checksum_error_msg);
return false;
}
// Look in cache location if no oat_location is given.
std::string cache_location;
if (oat_location == nullptr) {
// Use the dalvik cache.
const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA)));
cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str());
oat_location = cache_location.c_str();
}
bool has_flock = true;
// Definitely need to lock now.
if (!scoped_flock.HasFile()) {
std::string error_msg;
if (!scoped_flock.Init(oat_location, &error_msg)) {
error_msgs->push_back(error_msg);
has_flock = false;
}
}
if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) {
// Create the oat file.
open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(),
oat_location, error_msgs));
}
// Failed, bail.
if (open_oat_file.get() == nullptr) {
std::string error_msg;
// dex2oat was disabled or crashed. Add the dex file in the list of dex_files to make progress.
DexFile::Open(dex_location, dex_location, &error_msg, dex_files);
error_msgs->push_back(error_msg);
return false;
}
// Try to load again, but stronger checks.
success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location,
dex_location_checksum_pointer,true, error_msgs, dex_files);
if (success) {
RegisterOatFile(open_oat_file.release());
return true;
} else {
return false;
}
}
代码非常的长,但是我们惊喜的发现,贴心的google工程师已经帮我写好了step,所以我们就按照他们给的顺序进行源码分析。
step1:从已经在内存中的oat文件中查询时候有我们需要的dex文件。在这一步骤中,会生成一个dex文件的checksum值,这个值大家可以看做一个校验值,用来校验dex文件的。在FindOpenedOatDexFile方法中,会根据我们传入的oat文件路径(oat_location,也就是之前的optimizedPath)去查找在内存中是否已经有相同的oat文件,之后通过OatFile的GetOatDexFile去根据dex文件的路径(dex_location)获取一个OatDexFile,这个类可以获取对应的OatFile。
const OatFile::OatDexFile* ClassLinker::FindOpenedOatDexFile(const char* oat_location,const char* dex_location,const uint32_t* dex_location_checksum) {
ReaderMutexLock mu(Thread::Current(), dex_lock_);
for (const OatFile* oat_file : oat_files_) {
DCHECK(oat_file != nullptr);
if (oat_location != nullptr) {
if (oat_file->GetLocation() != oat_location) {
continue;
}
}
const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location,dex_location_checksum,false);
if (oat_dex_file != nullptr) {
return oat_dex_file;
}
}
return nullptr;
}
GetOatDexFile这个函数我们就不跟进去看了,具体的操作就是从oat_dex files 这个vector中找到对应的OatDexFile。每次生成一个oat文件之后,都会将对应的OatDexFile加入到oat_dex_files这个vector中。
我们这里的oat文件路径和dex文件路径都是patch的,所以是找不到的,于是就进入了step2。
step2:从磁盘中查找。时间原因,具体的代码我们就不分析了,这里当然也是找不到的。如果step1,step2命中,就会进入step3,调用LoadMultiDexFilesFromOatFile方法。
step3:调用LoadMultiDexFilesFromOatFile方法加载我们所需要的dex文件。由于我们step1和step2都没有命中,所以这一步也可以不看。
step4:最后一步,注释写的十分明确,[regenerate and load]重新生成一个oat文件并且加载到内存中。
在这一个步骤中,我们只需要了解,class_linker会调用CreateOatFileForDexLocation方法,借助dex2oat工具去生成一个oat文件,值得一提的是,如果我们没有传oat_location,也就是说之前的optimizedPath为空,那么系统就用自己的路径,/data/dalvik-cache,所以我们app的oat文件就在这个路径下。在CreateOatFileForDexLocation方法中,等生成了对应的OatFile之后会调用它的open函数,在这个函数中,会去判断oat文件是dl类型的还是elf类型的,不过最后都会调用setup函数,在这个函数中,OatFile生成了一个OatDexFile并将其加入到oat_dex files 这个vector中,这也证实了我之前说的[每次生成一个oat文件之后,都会将对应的OatDexFile加入到oat_dex_files这个vector中]。之后,class_linker会重新调用LoadMultiDexFilesFromOatFile方法,最后调用RegisterOatFile方法将oat文件加入到oat_files这个vector中。
LoadMultiDexFilesFromOatFile方法,看名字就知道和MultiDex有关。我们知道,MultiDex在Android5.0以上的版本是不需要手动操作的,因为ART虚拟机内部就支持了,原因就在这儿。至于为什么不是4.4就开始。。可能因为4.4的ART虚拟机bug比较多吧~
这个函数在最后会调用OpenFromZip函数,而在这个函数内部会去做一个while循环:
while (i < 100) {
std::string name = StringPrintf("classes%zu.dex", i);
std::string fake_location = location + kMultiDexSeparator + name;
std::unique_ptr<const DexFile> next_dex_file(Open(zip_archive, name.c_str(), fake_location,error_msg, &error_code));
if (next_dex_file.get() == nullptr) {
if (error_code != ZipOpenErrorCode::kEntryNotFound) {
LOG(WARNING) << error_msg;
}
break;
} else {
dex_files->push_back(next_dex_file.release());
}
i++;
}
将所有的classesx.dex(classes1,classes2…..)加入到dex_files这个vector中,最后和对应的oat文件关联,也就是说,ART虚拟机支持多个dex文件和一个oat文件关联。BTW,从这个循环就可以看出,最多分成100个dex,不过。。。也没有那个app会分这么多dex吧~
好了,至此,我们就讲完了关于Android中的class加载的部分,让我们来做个总结。
首先,DexPathList会调用makeDexElements去生成一个对应dex文件的Element。在这个函数中,会主动去生成一个新的DexFile。在DexFile的构造函数中,调用openDexFile函数去在oat文件中获取对应的dex文件并且返回一个cookie值给DexFile。openDexFile是一个native函数,其中调用了class_linker的OpenDexFilesFromOat函数。这个函数的逻辑分4部,上面都已经讲了,最后获取或者生成了一个oat文件,其中的LoadMultiDexFilesFromOatFile方法说明了ART内部支持MultiDex。
结合我们的hopatch,我们知道,在patch生效后,文件系统和内存中会多出一个oat文件,其中包含patch的dex和对应的机器指令。
我们都知道,在Android中,一个类是通过ClassLoader去加载和查找的,所以我们就先看ClassLoader的BaseDexClassLoader的findClass方法。
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
这个方法很简单,直接调用了DexPathList的findClass方法。
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
这个方法会去遍历成员变量dexElements(这也是hotpatch能起作用的原因),并且调用Elememt中的DexFile对象的loadClassBinaryName方法。
在DexFile的loadClassBinaryName方法中,会去获取对应的cookie,并且调用一个native方法defineClass去加载类。
static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader,
jlong cookie) {
std::vector<const DexFile*>* dex_files = toDexFiles(cookie, env);
if (dex_files == NULL) {
VLOG(class_linker) << "Failed to find dex_file";
return NULL;
}
ScopedUtfChars class_name(env, javaName);
if (class_name.c_str() == NULL) {
VLOG(class_linker) << "Failed to find class_name";
return NULL;
}
const std::string descriptor(DotToDescriptor(class_name.c_str()));
const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str()));
for (const DexFile* dex_file : *dex_files) {
const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str(), hash);
if (dex_class_def != nullptr) {
ScopedObjectAccess soa(env);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
class_linker->RegisterDexFile(*dex_file);
StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader(
hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
mirror::Class* result = class_linker->DefineClass(soa.Self(), descriptor.c_str(), hash,
class_loader, *dex_file, *dex_class_def);
if (result != nullptr) {
VLOG(class_linker) << "DexFile_defineClassNative returning " << result;
return soa.AddLocalReference<jclass>(result);
}
}
}
VLOG(class_linker) << "Failed to find dex_class_def";
return nullptr;
}
首先,这个方法调用了toDexFile,将cookie传进去,通过这个索引获取对应的dexFile。然后,调用class_linker的defineClass去获取一个类。值得一提的是,在defineClass中,这里存在一个缓存机制,会有一个DexCache用来缓存已经加载过的Dex信息,作用是提高效率和懒加载。
最后,class_linker会调用loadClass方法。
void ClassLinker::LoadClass(const DexFile& dex_file,const DexFile::ClassDef& dex_class_def,Handle<mirror::Class> klass,mirror::ClassLoader* class_loader) {
CHECK(klass.Get() != nullptr);
CHECK(klass->GetDexCache() != nullptr);
CHECK_EQ(mirror::Class::kStatusNotReady, klass->GetStatus());
const char* descriptor = dex_file.GetClassDescriptor(dex_class_def);
CHECK(descriptor != nullptr);
klass->SetClass(GetClassRoot(kJavaLangClass));
if (kUseBakerOrBrooksReadBarrier) {
klass->AssertReadBarrierPointer();
}
uint32_t access_flags = dex_class_def.GetJavaAccessFlags();
CHECK_EQ(access_flags & ~kAccJavaFlagsMask, 0U);
klass->SetAccessFlags(access_flags);
klass->SetClassLoader(class_loader);
DCHECK_EQ(klass->GetPrimitiveType(), Primitive::kPrimNot);
klass->SetStatus(mirror::Class::kStatusIdx, nullptr);
klass->SetDexClassDefIndex(dex_file.GetIndexForClassDef(dex_class_def));
klass->SetDexTypeIndex(dex_class_def.class_idx_);
CHECK(klass->GetDexCacheStrings() != nullptr);
const byte* class_data = dex_file.GetClassData(dex_class_def);
if (class_data == nullptr) {
return; // no fields or methods - for example a marker interface
}
OatFile::OatClass oat_class;
if (Runtime::Current()->IsStarted()
&& !Runtime::Current()->UseCompileTimeClassPath()
&& FindOatClass(dex_file, klass->GetDexClassDefIndex(), &oat_class)) {
LoadClassMembers(dex_file, class_data, klass, class_loader, &oat_class);
} else {
LoadClassMembers(dex_file, class_data, klass, class_loader, nullptr);
}
}
这个方法中的klass对象就代表着我们需要的类,其中主要做的操作就是给其设置一些属性,其中比较重要的就是SetDexClassDefIndex这个方法,设置这个class在dex中的索引值,之后我们需要通过这个值去获取oat文件中的OatClass。具体就是调用FindOatClass去获取对应的OatClass。FindOatClass最终调用了FindOpenedOatDexFile方法。这个方法在之前已经讲过,具体的逻辑就是这个函数就很简单了,遍历oat files 中的每一个oat文件,找到路径匹配的然后通过GetOatDexFile获取一个OatDexFile。
回到我们的LoadClass的最后,调用LoadClassMembers去加载这个类的相关信息。在这个方法中,会先去加载该类的field,然后去加载该类的method。我们以method为例,其中会调用linkCode方法,在这个方法中,我们会传入一个method_index作为方法的索引值,通过这个索引,我们从OatClass中获取了对应的OatMethod。既然如此,让我们看看OatMethod的构造函数。
OatFile::OatMethod::OatMethod(const byte* base,const uint32_t code_offset)
: begin_(base),
code_offset_(code_offset) {
}
其中begin_表示内存的启示值,code_offset表示偏移量,通过这两个值,我们就可以找到对应方法的机器指令在内存中的位置了。
通过上面的源码分析,我们知道,在ART虚拟机的环境下,类和方法遵循[dex class]->(class index)[oat class]->(method index)[oat method]->(begin,code offset )[native code]这样的逻辑。其中,dex文件和oat文件的class顺序必须要一致,因为是通过index去关联的。
下面这些都是我个人的思考,如果存在不对的地方,请大家指正,谢谢!(但是这种hotpatch方式在ART虚拟机上存在问题是不争的事实)。
一开始,我单纯的以为由于ART虚拟机上的aot操作,将内存地址在一开始就全部写死,这样动态的添加一个oat文件就会造成内存移位,但是后来在实际业务场景中发现,这个crash并不是必现的,甚至可以说出现的概率比较低,所以应该不可能是这种情况造成的,后来在查阅资料后也发现,只有系统的oat文件是固定地址的,在image文件后面,这也侧面证实了我的说法。
之后,我又开始到底怎么样才会出现这个crash,网上有很多同学说只要在patch类中调用非patch类的方法就会复现,原因是非patch类的方法找不到具体地址,oat文件上打印的地址有问题,于是我反汇编了一个patch的oat文件。
首先,假设我现在有两个类,MainActivity和Test2,我在MainActivity中调用了Test2的test方法,让我们先看看MainActivity在patch中而Test2不在patch中的oat文件:
我们可以看到,在图片中的最后一行,直接写的是#70,也就是Test2.test的method index。
再看看MainActivity和Test2都在patch中的情况:
由于MainActivity和Test2都在patch中,所以没有出现上面的那种现象。
所以有人会说,是这个问题,由于我对汇编代码实在一窍不通,所以也不好肯定的说这是错误的,但是如果你写过这样的demo,你会发现其实交叉引用[patch引用非patch]也是不一定能复现出crash的,而且如果你看过oat文件的全文,你就会发现其实像#70这样的引用是很常见的,所以,我觉得这就是一种类似[符号引用]的东西,不会存在什么问题。
最后,我又翻了翻class_linker的代码,发现在它的FindClass方法中,会先调用LookupClass方法,在这个方法内部,有一个逻辑叫LookupClassFromImage,这个逻辑里,它会先从dex_cache中查找,如果找不到,才会调用defineClass。
对于dex_cache,这里我多说两句,class_linker中,一个dex对应一个dex_cache,而在dex_cache中有一个数组resolved_methods,用来存储所有dex文件中的方法,而在初始化dex_cache的时候,resolved_methods数组里的方法会初始化成trampoline,之后,在每一个方法第一次调用的时候会被填充到resolved_methods数组中,起到缓存的作用。
在 Android N混合编译与对热补丁影响解析 一文中,文章的作者详细的说明了在AndroidN上patch失败的原因,其中原理部分和我上面提到的第三种情况比较相似,只是我在Android5.1的源码中没有看到base.art和AppImage,只有一个boot.art,用来缓存系统的类,但是,在我看来,这种情况导致patch失败的可能性是最大的。
以上就是这篇文章的所有内容了,对于最后一点hotpatch失败的猜想,欢迎大家一起讨论,当然如果有大神明确的知道其中的原理的话,能告诉我那就最好了~