目录
整体区分
Dailk和ART
安卓中的类加载器
类加载机制采用双亲委托机制
整体区分
JVM是基于栈的虚拟机;而安卓是基于寄存器的虚拟机(ART和davik)。
1、寄存器
上边这个a+b在寄存器中的运行方式是:从物理地址为100的位置拿到第一个数放到寄存器AX中,物理地址是104的地方拿到第二个数存到BX中,,然后再ALU里边完成算数相加,将结果存到寄存器CX中,最后将结果地址存在108的位置。寄存器就是一个晶体管。安卓的虚拟机是为了模拟真实的寄存器的操作流程。
基于寄存器的虚拟机中没有操作数栈,但是有很多虚拟寄存器。其实和操作数栈相同,这些寄存器也存放 在运行时栈中,本质上就是一个数组。与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法 调用的活动记录以帧为单位保存在调用栈上。
相比于JVM。每次都需要将要操作的数据经过操作数栈,安卓这种基于寄存器的方式指令数量明显会减少很多。
同样一个1+2的操作。在JVM里边会是这样的:
安卓上边只有4条指令,而JVM则有多达8条指令。
基于寄存器的虚拟机开发难度相比基于栈的更高。
Dailk和ART
DVM也是实现了JVM规范的一个虚拟器,默认使用CMS垃圾回收器,但是与JVM运行Class字节码不同,DVM执行Dex(Dalvik Executable Format)** ——专为 Dalvik 设计的一种压缩格式。Dex 文件是很多 .class 文件处理压 缩后的产物,最终可以在 Android 运行时环境执行。 而ART(Android Runtime)是在 Android 4.4 中引入的一个开发者选项,也是 Android 5.0 及更高版本的默认 Android 运行时。ART 和 Dalvik 都是运行 Dex 字节码的兼容运行时,因此针对 Dalvik 开发的应用也能在 ART 环 境中运作。dexopt与dexaotdexopt在Dalvik中虚拟机在加载一个dex文件时,对 dex 文件 进行 验证 和 优化的操作,其对 dex 文件的优化结果变成了 odex(Optimized dex) 文件,这个文件和 dex 文件很像,只是使用了一些优化操作码。dex2oatART 预先编译机制,在安装时对 dex 文件执行AOT 提前编译操作,编译为OAT(实际上是ELF文件)可执行 文件(机器码) Dalvik虚拟机:执行的是dex字节码,解释执行。从Android 2.2版本开始,支持JIT及时编译(Just in Time)在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化。 ART(Android Runtime) 是在 Android 4.4 中引入的一个开发者选项,也是 Android 5.0 及更高版本的默认 Android 运行时。ART虚拟机执行的是本地机器码。Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者将自己的应用直接编译成目标机器码,APK仍然是一个包含dex字节码的文件。那么,ART虚拟机执行的本地机器码是从哪里来?
Dalvik下应用在安装的过程,会执行一次优化,将dex字节码进行优化生成odex文件。而Art下将应用的dex字 节码翻译成本地机器码的最恰当AOT(Ahead Of Time)时机也就发生在应用安装的时候。ART 引入了 ,在安装时,ART 使用设备自带的 dex2oat 工具来编译应用,dex中的字节码将被编译成本地机器码。AndroidN的运行方式ART使用预先(AOT)编译,并且从AndroidN混合使用AOT编译,解释和JIT。 1、最初安装应用时不进行任何AOT编译(安装又快了),运行过程中解释执行,对经常执行的方法进行JIT(其实就是记录的一些类的信息),经过JIT(JIT编译的代码只有本次有效)编译的方法将会记录到Profile配置文件中。 2、当设备闲置和充电时,编译守护进程会运行,根据Profile文件对常用代码进行AOT编译。待下次运行时直接使用。 Dalvik的dex就是dex文件,而ART的odex,已经是机器码了。
安卓中的类加载器
类加载是一个IO操作,所以这里必须要用缓存。加载类还需要解析出来,将类的信息保存到方法区。
类加载机制采用双亲委托机制
有两个好处:一个避免多次加载;而是安全,防止核心类被篡改(如果改掉了系统的核心代码,这可能导致后续出现了不可预测的错误)。
例如我们自定义String类:
所有用到了equals方法的地方都会崩溃!甚至连系统流程都可能被影响。
Android类加载流程
安卓中主要有三个类加载器:BootClassLoader,DexClassLoader, PathClassLoader,第一个用来加载Framework中的类,后两个用来加载自己写的类和谷歌的拓展库,例如AppCompatActivity等,需要注意的是,在Android8之后,DexClassLoader, PathClassLoader已经没有区分了,我们在加载类的时候,用的还是PathClassLoader,之所以还保留DexClassLoader,谷歌可能是考虑的拓展原因。
安卓类加载的流程:
// dalvik.system.BaseDexClassLoader#findClass @Overrideprotected 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>();// 从这里去加载真正的ClassClass 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#findClasspublic 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.cppstatic 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文件