目录
整体区分
JVM是基于栈的虚拟机;而安卓是基于寄存器的虚拟机(ART和davik)。
1、寄存器
上边这个a+b在寄存器中的运行方式是:从物理地址为100的位置拿到第一个数放到寄存器AX中,物理地址是104的地方拿到第二个数存到BX中,,然后再ALU里边完成算数相加,将结果存到寄存器CX中,最后将结果地址存在108的位置。寄存器就是一个晶体管。安卓的虚拟机是为了模拟真实的寄存器的操作流程。
相比于JVM。每次都需要将要操作的数据经过操作数栈,安卓这种基于寄存器的方式指令数量明显会减少很多。
同样一个1+2的操作。在JVM里边会是这样的:
安卓上边只有4条指令,而JVM则有多达8条指令。
基于寄存器的虚拟机开发难度相比基于栈的更高。
Dailk和ART
那么,ART虚拟机执行的本地机器码是从哪里来?
安卓中的类加载器
类加载是一个IO操作,所以这里必须要用缓存。加载类还需要解析出来,将类的信息保存到方法区。
类加载机制采用双亲委托机制
有两个好处:一个避免多次加载;而是安全,防止核心类被篡改(如果改掉了系统的核心代码,这可能导致后续出现了不可预测的错误)。
例如我们自定义String类:
所有用到了equals方法的地方都会崩溃!甚至连系统流程都可能被影响。
Android类加载流程
安卓中主要有三个类加载器:BootClassLoader,DexClassLoader, PathClassLoader,第一个用来加载Framework中的类,后两个用来加载自己写的类和谷歌的拓展库,例如AppCompatActivity等,需要注意的是,在Android8之后,DexClassLoader, PathClassLoader已经没有区分了,我们在加载类的时候,用的还是PathClassLoader,之所以还保留DexClassLoader,谷歌可能是考虑的拓展原因。
安卓类加载的流程:
// dalvik.system.BaseDexClassLoader#findClass
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// First, check whether the class is present in our shared libraries.
if (sharedLibraryLoaders != null) {
for (ClassLoader loader : sharedLibraryLoaders) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException ignored) {
}
}
}
// Check whether the class in question is present in the dexPath that
// this classloader operates on.
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
// 从这里去加载真正的Class
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
上边的一个关键变量:pathList,其实是DexPathList。DexPathList实在BaseClassLoader的构造方法中进行的初始化,并且将dex文件的路径传递到DexPathList中,这是为了后边的解析。
public BaseDexClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
boolean isTrusted) {
super(parent);
// Setup shared libraries before creating the path list. ART relies on the class loader
// hierarchy being finalized before loading dex files.
this.sharedLibraryLoaders = sharedLibraryLoaders == null
? null
: Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
reportClassLoaderChain();
}
来看看DexPathList的构造方法:
public DexPathList(ClassLoader definingContext, String librarySearchPath) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
this.definingContext = definingContext;
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
// splitDexPath,分组,虽然一般一个apk只有一个dex文件,但是我们是能够支持多个dex文件的同时解析的。传入的格式:/a/a/dex:/a/b.dex。两个dex文件中间用冒号隔开。
this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
}
// 一个dex生成一个element,多个dex就有多个element,所以这里使用数组。
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}
进入DexPathList的查找类的流程:
// dalvik.system.DexPathList#findClass
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
接着会走到这里:
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
接着走到这里:
// DexFile里边:
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
DexFile dexFile)
throws ClassNotFoundException, NoClassDefFoundError;
最终进入C++层:
// dalvik_system_DexFile.cpp
static jclass DexFile_defineClassNative(JNIEnv* env,
jclass,
jstring javaName,
jobject javaLoader,
jobject cookie,
jobject dexFile) {
std::vector<const DexFile*> dex_files;
const OatFile* oat_file;
if (!ConvertJavaArrayToDexFiles(env, cookie, /*out*/ dex_files, /*out*/ oat_file)) {
VLOG(class_linker) << "Failed to find dex_file";
DCHECK(env->ExceptionCheck());
return nullptr;
}
ScopedUtfChars class_name(env, javaName);
if (class_name.c_str() == nullptr) {
VLOG(class_linker) << "Failed to find class_name";
return nullptr;
}
const std::string descriptor(DotToDescriptor(class_name.c_str()));
const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str()));
for (auto& dex_file : dex_files) {
const dex::ClassDef* dex_class_def =
OatDexFile::FindClassDef(*dex_file, descriptor.c_str(), hash);
if (dex_class_def != nullptr) {
ScopedObjectAccess soa(env);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader(
hs.NewHandle(soa.Decode<mirror::ClassLoader>(javaLoader)));
ObjPtr<mirror::DexCache> dex_cache =
class_linker->RegisterDexFile(*dex_file, class_loader.Get());
if (dex_cache == nullptr) {
// OOME or InternalError (dexFile already registered with a different class loader).
soa.Self()->AssertPendingException();
return nullptr;
}
ObjPtr<mirror::Class> result = class_linker->DefineClass(soa.Self(),
descriptor.c_str(),
hash,
class_loader,
*dex_file,
*dex_class_def);
// Add the used dex file. This only required for the DexFile.loadClass API since normal
// class loaders already keep their dex files live.
class_linker->InsertDexFileInToClassLoader(soa.Decode<mirror::Object>(dexFile),
class_loader.Get());
if (result != nullptr) {
VLOG(class_linker) << "DexFile_defineClassNative returning " << result
<< " for " << class_name.c_str();
return soa.AddLocalReference<jclass>(result);
}
}
}
VLOG(class_linker) << "Failed to find dex_class_def " << class_name.c_str();
return nullptr;
}
整体流程:
直观图:
一定要注意,有几个dex文件,Element数组里边的元素就有几个。
热修复原理
(1)获取当前应用的PathClassLoader
(2)反射获取到DexPathList属性对象pathList;
(3)反射修改pathList的dexElemets
1、把补丁包的path.dex转化为Element[];(patch)
2、获得pathList的dexElements属性;(old)
3、合并patch+old,并反射赋值给pathList的dexElements;patch一定要放在前边
在安卓N之后,由于使用了ART, 常用的都会被编译成机器码,区分Dalvik,不会再去找dex文件,采用了AOT方式,PathClassLoader在创建之初就会将类加载进去,导致使用上边这种方式会失效。此时可以采用的方式是自定义ClassLoader反射替换掉PathClassLoader,修改其中的加载流程。Tinker就采用了这种方式。
参考文章
Android dex、odex、oat、vdex、art区别_慢慢的燃烧的博客-CSDN博客_odex vdex
Android[art]-Android dex,odex,oat,vdex,art文件结构学习总结_TaylorPotter的博客-CSDN博客_vdex文件