c语言sscanf函数的用法是什么
270
2022-09-21
Anroid插件化/模块化/热修复知识梳理
今天跟着大神们的blog来梳理一下Android 关于插件化 热修复等的知识。
关于模块化、插件化、组件化
单工程模式 就是我们单纯的直接开发项目时,就二话不说的开new Project->分包 然后开怼。写的就很爽。这种模式不涉及到花里胡哨的东西,上手快,开发快(当然如果你的demo其实很小,那也没有必有用到别的模式)。一般项目刚起步都比较小,一些附加业务都没有绑定到App上面。
模块化 AS出来之后,多了一个概念,Project Module模块,Module中包含两种格式:application、library。也就是说一个Module是一个小的项目,也是AS概念中的模块。因此我们开始设计common等模块,对于包来说,模块更加灵活,耦合度更低,随意插拔,就跟引入第三方框架一样,想用哪一个就用哪一个。模块化就是将一个项目根据可以共享的部分抽取出来形成一个module,这就是模块化。
组件化 组件化和模块化有着比较小的差别,其核心是 模块角色的可转化性。 比如说在打包的时候是library,在调试时就是application。 Module分为application和library。library就是引用库,如我抽取的common。application就是一个apk,是一个完整的项目。 当我们在调试自己的代码的时候就相当于调试一个小的App。就相当一个上百M的大项目分成了多个只有10几M的小项目,这个时候只需要关注自己的代码调试,不用在意别的代码怎么样就行了。
插件化 插件化严格意义也是模块化的进阶概念。将一个完整的工程,按业务分成不同的插件,这是分治的一种体现。也是相当于将一个大项目分成许多小项目,化整为零。那这样又和之前讲到的组件化有什么区别呢?
其实就是字面上的意思: 组件化:就是将应用里面的内容分成一块一块的组件。 插件化:就是将应用作为一个插件,去和别的插件进行组装。
插件化和热修复的关系
从技术角度来说,他们都是从系统加载器的角度出发,无论是采用hook方式,亦或者是代理方式或者是其它底层实现。(之前说过,热修复就是插件化思想的一种实现) 都是通过“欺骗”Android系统的方式让宿主正常加载和运行插件(补丁)中的内容。 接下来了解一下称谓约定:
宿主:就是当前运行的app插件:相对于插件化技术来说,就是要加载运行的apk文件补丁:对于热修复来说,就是要加载运行的 .patch,.dex,*.apk等一系列包含dex修复内容的文件。
关于动态加载,早期的一些换肤功能就是通过下载皮肤的apk文件、还有特定节日的促销活动,逃避审核机制的动态广告加载都是Android插件技术可以考虑实现的。
类加载原理
热修复原理和类加载原理相关,和java类似,Android中类的加载也是通过classload来完成,具体来说就是PathClassLoader和DexClassLoader两个加载器,他们的区别是:
PathClassLoader:只能加载到已经安装到Android系统的apk文件,是android使用的默认的类加载器。DexClassLoader:可以加载任意目录下的dex/jar/apk/zip文件,也就是补丁啦。
这两个类都是继承自BaseDexClassLoader,我们可以看一下BaseDexClassLoader的构造函数
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); }
它做的事就是对传入进来的参数初始化了一个DexPathList对象。DexPathList的构造函数就是将传递进来的程序文件(就是补丁文件)封装成一个Element对象,并将这些对象添加到Element的数组集合dexElement中去。
classloader的加载机制是双亲委派机制,这种机制下一个Class只会被加载一次。 这里对于一个ClassLoader来说,一个Class加载到内存中其实是有虚拟机完成的,对于开发者来说,我们的关注点应该是如何去找到这个需要加载的类。
假设我们现在要找一个名为name的class,那么DexClassLoader将会走下面几个步骤:
在DexClassLoader的findClass中通过DexPathList的findClass()方法来获取一个class。在DexPathListh的findClass方法里面去遍历之前说的集合dexElement,如果找到类名和name相同,则直接返回这个class,否则返回null。
总的来说通过DexClassLoader查找一个类最终就是在dexElement中查找特定类的操作。
综上所述我们可以描述出一个热修复的方案:当我们发现我们代码中某个类或者某几个类是有bug的,那我们在修复完这个类后,打包成一个补丁文件,然后通过这个补丁文件打包封装成一个Elemt对象,并将这个Element对象插入到原有dexElements数组的最前端。这样当DexClassLoader去类时,优先会从我们这个Element中找到对应的类,然后加载了一个没有bug的类,虽然原有的bug类还存在于数组的后面,但是由于双亲委派机制,这个类已经没有机会再加载了,这样一个bug就在没有重新安装应用的情况下修复了。
现在市面上有许多热修复框架,按照公司团队可以分为以下几种:
修复框架很多,但是核心修复技术只有三类:代码修复、资源修复和动态链接库修复。 而每个修复框架也有自己的特点,我们根据需要去使用修复框架。
代码修复
代码修复主要有三个方案,分别是底层替换方案、类加载方案和Instant Run方法。
类加载方案 类加载方案基于Dex分包方案,什么是Dex分包方案呢?得先从65536和LinearAlloc限制说起。
65536限制:就是指一个应用中应用的方法数超过了65536个,因为DVM Bytecode的限制,DVM指令集的方法调用指令invoke-kind索引为16bits,最多只能引用65535个方法。LinearAlloc限制:在安装时会提示INSTALL_FAILED_DEXOPT。产生的原因就是DVM中LinearAlloc是一个缓存区,当方法数过多超出了缓存区的大小就会报错。
为了解决上述两个问题,从而产生了Dex分包方案。方案主要是在打包的过程中将代码分成多个dex,将应用启动时和重要的类放在主dex中,其他代码放在次dex包中。当应用加载时,先加载主dex,等到应用加载好之后再加载次dex,从而缓解了上述两个限制。
没错啦,之前提介绍过的,通过DexPathList中的dexElement查找就是上述的实现。我们来看看DexPathList中的findClass方法:
public Class findClass(String name, List
Element封装了DexFile,DexFile终于加载dex文件,所以每个dex文件对应一个Element。element的findClass方法会调用DexFile的loadClassBinaryName方法查找类。我们将修改好的类打包成含dex的补丁包 Patch.jar,放在Element数组中的第一个,然后接下来的之前有介绍过~~~~~
底层替换方案 底层替换方案不会再加载新类,而是在Native层修改原有的类,由于是在原有的类进行修改,所以限制比较多,不能够增减原有类的方法和字段,如果我们增加了方法数,那么方法索引数也会增加,这样访问方法时无法通过索引找到正确的方法,同样字段也是类似的情况。 底层替换方案和反射的原理也有些关联,就拿替换方法来说,方法我们可以调用java.lang.Class.getDeclaredMethod,假设我们要反射Key的show方法,会调用如下:
Key.class.getDeclaredMethod("show").invoke(Key.class.newInstance()); //invoke方法 @FastNative public native Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException; //invoke是native方法,对应的jni代码为:static jobject Method_invoke(JNIEnv* env, jobject javaMethod, jobject javaReceiver, jobject javaArgs) { ScopedFastNativeObjectAccess soa(env); return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs);} //而InvkeMethod又调用了InvokeMethod函数:jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject javaMethod, jobject javaReceiver, jobject javaArgs, size_t num_frames) { ... ObjPtr
注释1获取传入的javaMethod方法(Key的show方法)在ART虚拟机中对应一个ArtMethod指针,ArtMethod结构体中包含了Java方法的所有信息,包括执行入口、访问权限、所属类和代码执行的地址等等。ArtMehod的结构体如下:
class ArtMethod FINAL {... protected: GcRoot
最重要的就是注释1中的dex_cache_resolved_methods和注释2的entry_point_from_quick_compiled_code,它们是方法的执行入口,当我们调用某一个方法时(比如Key的show)就会取得show方法的执行入口,通过执行入口就可以跳过去执行show方法。我们通过替换ArtMethod的字段者替换整个ArtMethod结构体,这就是底层替换方案。 AndFix采用的是替换ArtMethod中的字段,这样会有兼容问题,因为厂商可能会修改ArtMethod结构体,导致方法替换失败。 Sophix采用的是替换整个ArtMethod结构体,就不会存在兼容问题。 底层替换方案直接替换了方法,可以立即生效不需要重启。
Instant Run方案 Instant Run在第一次构建apk时,使用ASM在每一个方法中注入了类似的代码:
IncrementalChange localIncrementalChange = $change;//1 if (localIncrementalChange != null) {//2 localIncrementalChange.access$dispatch( "onCreate.(Landroid/os/Bundle;)V", new Object[] { this, paramBundle }); return;}
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~