一、知识详解模块
1.dex/class深入讲解
2.jvm/dvm/art三个虚拟机的深入讲解
3.class Loader类加载器的深入讲解
二、热修复应用模块
1.热修复原理深入讲解
2.如何合理的接入开源的热修复框架
3.开源框架的原理及版本发布控制
三、插件化应用模块
1.插件化原理以及组件化的区别
2.如何将应用插件化
3.插件化能为我们带来那些好处
一、知识详解模块
1.Class文件结构深入解析(生成、执行)
2.dex文件结构深入解析(生成、执行)
3.Class与Dex的对比
Class文件是什么?如何生成、作用以及文件格式详解
其是能够被JVM虚拟机识别加载并执行的文件格式
(1)IDE编译器--build生成
(2)javac命令生成
(3)通过java命令去执行class文件 javac -target 1.6 -source 1.6 Hello.java
记录一个类文件的所有信息
Class文件结构解析:
(1)一种8字节的二进制流文件
(2)各个数据按顺序紧密的排列、无间隙
(3)每一个类或接口都独自占据一个class文件
http://blog.csdn.net/linzhuowei0775/article/details/49556621 Class类文件的结构
u4 magic; 它的唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件
u2 minor_version; 次版本号
u2 major_version; 主版本号(java版本)
u2 constant_pool_count; 常量池的数量(这个容量计数是从1而不是从0开始)
cp_info constant_pool[constant_pool_count-1]; 表类型数据集合,即常量池中每一项常量都是一个表,共有11种结构各不相同的表结构数据
u2 access_flags; ----------------------------作用域
u2 this_class; ------------------------------JVM生成的本类对象
u2 super_class; -----------------------------JVM生成的父类对象
u2 interfaces_count; ------------------------接口
u2 interfaces[interfaces_count];
u2 fields_count; ----------------------------变量
field_info fields[fields_count];
u2 methods_count; ---------------------------方法
method_info methods[methods_count];
u2 attributes_count; ------------------------属性
attribute_info attributes[attributes_count];
Class文件的弊端:
(1)内存占用大,不适合移动端
(2)JVM采用堆栈加载模式,加载速度慢
(3)文件IO操作多,类查找慢
Dex文件是什么?如何生成、作用、文件格式详解
其是一个能够被DVM识别,加载并执行的文件格式
(1)通过IDE自动--build生成
(2)通过DX命令生成dex文件 dx --dex --output Hello.dex Hello.class
(3)手动运行dex文件到手机 dalvikvm -cp /scard/Hello.dex Hello
记录整个工程中所有类文件的信息
Dex文件结构解析:
(1)一个8字节二进制流文件
(2)各个数据按照顺序紧密排列无缝隙
(3)整个应用中的所有Java源文件都放在一个Dex文件中
http://blog.csdn.net/feglass/article/details/51761902 Android Dex文件结构解析
主要分为三部分:文件头、索引区、数据区
header---文件头
索引区
string_ids--字符串的索引
type_ids ---类型索引
proto_ids --方法原型的索引
field_ids ----域的索引
method_ids ---方法的索引
数据区
class_def -----类的定义区
data -----------数据区
link_data -------链接数据区
Class与Dex的区别
(1)本质上两者是一样的,dex是从class文件演变过来的 dx --dex --output xx.dex xx.class
(2)class文件存在很多的冗余信息,dex会去除冗余并合并
(3)结构不一样
第二章
1.JVM虚拟机结构解析
2.Dalvik与JVM的不同
3.ART与Dalvik相比有哪些优势
1.JVM虚拟机结构解析
(1)JVM整体结构讲解
(2)Java代码的编译和执行过程
(3)内存管理和垃圾回收
JAVA虚拟机、Dalvik虚拟机和ART虚拟机简要对比:
JVM:Java的跨平台性是由JVM来实现的,就是把平台无关的.class字节码翻译成平台相关的机器码,来实现的跨平台;
JVM在把描述类的数据从class文件加载到内存时,需要对数据进行校验、转换解析和初始化,最终才形成可以被虚拟机直接使用的JAVA类型,因为大量的冗余信息,会严重影响虚拟机解析文件的效率
DVM:为了减小执行文件的体积,安卓使用Dalvik虚拟机,SDK中有个dx工具负责将JAVA字节码转换为Dalvik字节码,dx工具对JAVA类文件重新排列,将所有JAVA类文件中的常量池分解,消除其中的冗余信息,重新组合形成一个常量池,所有的类文件共享同一个常量池,使得相同的字符串、常量在DEX文件中只出现一次,从而减小了文件的体积.
Dalvik执行的是dex字节码,依靠JIT编译器去解释执行,运行时动态地将执行频率很高的dex字节码翻译成本地机器码,然后在执行,但是将dex字节码翻译成本地机器码是发生在应用程序的运行过程中,并且应用程序每一次重新运行的时候,都要重新做这个翻译工作,因此,及时采用了JIT,Dalvik虚拟机的总体性能还是不能与直接执行本地机器码的ART虚拟机相比
ART:
Dalvik虚拟机执行的是dex字节码,ART虚拟机执行的是本地机器码
安卓运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者重新将自己的应用直接编译成目标机器码,也就是说,应用程序仍然是一个包含dex字节码的apk文件。所以在安装应用的时候,dex中的字节码将被编译成本地机器码,之后每次打开应用,执行的都是本地机器码
JIT与AOT两种编译模式
JIT:即时编译技术,JIT会在运行时分析应用程序的代码,识别哪些方法可以归类为热方法,这些方法会被JIT编译器编译成对应的汇编代码,然后存储到代码缓存中,以后调用这些方法时就不用解释执行了,可以直接使用代码缓存中已编译好的汇编代码。这能显著提升应用程序的执行效率
AOT:
ART优点:
①系统性能显著提升
②应用启动更快、运行更快、体验更流畅、触感反馈更及时
③续航能力提升
④支持更低的硬件
ART缺点
①更大的存储空间占用,可能增加10%-20%
②更长的应用安装时间
总的来说ART就是“空间换时间”
JVM与DVM的对比:
(1)JAVA虚拟机运行的是JAVA字节码,Dalvik虚拟机运行的是Dalvik字节码
(2)Dalvik可执行文件体积更小
(3)JVM基于栈,DVM基于寄存器
注意:
在DVM虚拟机中,总是在运行时通过JIT把字节码文件编译成机器码,因此程序跑起来就比较慢,所以在ART模式(4.0引入,5.0设置为默认解决方案)上,就改为AOT预编译,也就是在安装应用或者OTA系统升级时提前把字节码编译成机器码,这样就可以直接运行了,提高了运行的效率。但是AOT的缺点,每次执行的时间都太长,且ROM空间占用有比较大,所以在Android N上google做了混合编译,即同时支持JIT+AOT。
简单来讲:安装的时候不做编译,而是JIT解释字节码,以便能够快速启动,在运行的时候分析运行过程中的代码以及区分“热代码”,这样就可以在机器空闲的时候对通过dex2aot这部分热代码采用AOT进行编译存储为base.art文件然后在下次启动的时候一次性把app image加载进来到缓存,预先加载代替用时查找以提升应用的性能,并且同一个应用可以编译数次,以找到“热”的代码路径或者对已经编译的代码进行新的优化。
JVM结构:
虚拟机内存区域分为:运行时数据区+(执行引擎+本地库接口+本地方法库)
运行时数据区:方法区、Java栈、Java堆、本地方法栈、程序计数器
Class文件----(通过类加载器子系统)-----加载到内存空间(方法区、JAVA堆、JAVA栈、本地方法栈)-------垃圾收集器(GC)
内存优化方案
JVM内存管理
1.JAVA栈(虚拟机栈):存储JAVA方法执行时所有的数据;存放基本型,以及对象引用。线程私有区域
其由栈帧组成,一个栈帧代表一个方法的执行,每个方法在执行的同时都会创建一个栈帧
JAVA栈帧--每个方法从调用到执行完成就对应一个栈帧在虚拟机中入栈到出栈
JAVA栈帧存储:局部变量、栈操作数、动态链接、方法出口.
2.JAVA堆:所有通过new创建的对象产生的内存都在堆中分配存放对象实例和数组,此区域也是垃圾回收器主要作用的区域。
特点:
是虚拟机中最大的一块内存,是GC要回收的部分
堆区内存:
Young generation(新创建的存储区)
Old generation(暂时用不到的,不可达的对象存储区)若是Young与Old都存储满了 就会爆出OOM异常
Permanent generation
分成两个的原因:有利于动态调整两个区域的大小
即时通信服务时调用MSG消息比较多,就可以把Young调整的大一些,这样便于内存的分配,加快对象的创建
3.本地方法栈:是专门为native方法提供服务的 虚拟机中的JIT即时编译单元负责本机系统中比如C语言或者C++语言和Java程序间的互相调用,
这个Native Method Stack就是用来存放与本线程互相调用的本机代码
4.方法区:存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的数据,是所有线程的共享区域。
也称为永久代(Permanent Generation)但随着Java8的到来,已放弃永久代改为采用Native Memory来实现方法区的规划。
此区域回收目标主要是针对常量池的回收和对类型的卸载
5.程序计数器:可看做是当前线程所执行的字节码的行号指示器;如果线程在执行Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;
如果执行的是Native方法,这个计数器的值为空(Undefined)
垃圾回收:
垃圾收集算法(垃圾确认)
1.引用计数算法(JDk1.2之前提供)
优点:实现简单,判定效率高
缺点:两个不可达的对象相互引用,导致内存无法回收。
2.JDK1.2之后采用可达性算法(根搜索算法)---采用离散数学原理
从GC Roots为根节点一直向下遍历,找到下一个节点。。。。,这样找不到的就是不可达的,那这些就是垃圾,被垃圾回收器回收
引用的类型:
强引用(弱引用内存不足也不会回收,除非OOM,它是内存泄漏的主要原因之一)通常new产生的对象就是强引用
软引用(SoftReference内存充足时,保持引用,内存不足时,进行回收)
弱引用(WeakReference不管JVM的内存空间是否足够,总会回收该对象占用的内存)
虚引用
垃圾回收算发:(垃圾回收)
1.标记清除算法--
好处:不需要对象的移动,并且仅对不存活的对象进行处理
在存活对象比较多的情况下极为高效,效率问题,标记和清除两个过程的效率都不高
坏处:一般是使用单线程操作,直接回收不存活对象,会造成大量的不连续的内存碎片,从而不利于后续对一些对象的分配
2.复制算法
好处:在存活对象比较少时极为的高效,实现简单,运行高效;每次都是对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等情况,只要移动堆顶指针,按顺序分配内存即可
坏处:需要一块内存空间作为交换空间
3.标记--整理算法--标记清除算法基础之上的一个算法,解决了内存碎片的问题,主要针对对象存活率高的老年代
4.分代收集算法---根据对象的存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、
没有额外空间对它进行分配担保,就必须使用标记—清除算法或标记—整理算法来进行回收
垃圾回收的触发机制:(垃圾回收的时机)
java虚拟机无法再为新的对象分配内存空间了
手动调用System.gc()方法
低优先级的GC线程,被JVM启动时就会执行GC
Dalvik VM 与JVM的不同
1.执行的文件不同,一个是class的一个是dex的
2.类加载的系统与JVM区别较大
3.可以同时存在多个dvm
4.Dalvik是基于寄存器的,而JVM是基于堆栈的
Dalvik VM与ART的不同之处
DVM使用JIT(即时编译每次运行都会执行编译)来将节码转换成机器码,效率低
ART采用了AOT预编译技术,执行速度更快(JDK 1.9 代码的分段缓存技术,执行速度更快)
ART会占用更多的应用安装时间和存储空间
第三章:
Java中的ClassLoader回顾
Android中的ClassLoader的作用详解
Android中的动态加载比一般Java程序复杂在哪里
Android ClassLoader概述
Android中ClassLoader的种类
Android中ClassLoader的特点
ClassLoader源码讲解
Java中的ClassLoader回顾
BootstrapClassLoader: Load JRE\lib\rt.jar或者Xbootclasspath选项指定的jar包
ExtensionClassLoader: Load JRE\lib\ext\*.jar或者Djava.ext.dirs指定目录下的Jar包
AppClassLoader: Load CLASSPATH或者Djava.class.path所指定的目录下的类和Jar包
CustomClassLoader:通过java.lang.ClassLoader的子类自定义加载Class
Android中ClassLoader的种类
BootClassLoader:Load JRE\lib\rt.jar或者Xbootclasspath选项指定的jar包及Framework层的一些Class字节码文件
PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器。
DexClassLoader:可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现热修复的重点。
BaseDexClassLoader:是PathClassLoader与DexClassLoader的父类
一个应用程序最少需要
BootClassLoader、PathClassLoader两个类加载器
特点及作用
双亲代理模型的特点
(先判断类是否是被当前的ClassLoader加载过,加载过则返回,没有加载过在去看其父类ClassLoader是否加载过,加载过返回,否的话,最终由其子类ClassLoader去加载)
这个特点也就意味着一个类被位于事务中的任意一个ClassLoader加载过,那么在今后的整个系统的生命周期中,该类就不会再重新被加载了,大大提高了类加载的一个效率。
类加载的共享功能(Framework里面的一些类一旦被顶层的ClassLoader加载过,就会保存在缓存里面,以后就不要重新加载了)
类加载的隔离功能(不同继承路径上的ClassLoader加载的类肯定不是同一个类,可以防止用户数据被篡改,防止自定义类冒充核心类库,以访问核心类库中的成员变量)
双亲委派机制的描述:
当某个特定的类加载器在接到类加载的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类的加载任务,就成功返回;
只有父类加载器无法完成加载任务时,自己才去加载。
意义:防止内存中出现多份同样的字节码;例如:
比如两个类A和类B都要加载System类:
如果不用委托而是自己加载自己的,那么类A就会加载一份System字节码,然后类B又会加载一份System字节码,这样内存中就出现了两份System字节码。
如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,
如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了
双亲委派机制也就是热修复在技术上可以实现的根本依据,即:多个dex文件组成一个数组Element[],按照顺序加载,对于一个已经加载的Class是不会再次加载的。
同一个ClassLoader加载过的类可以称之为同一个类(还要有相同的包名、类名)
Android ClassLoader加载顺序:
ClassLoader.java中 loadClass()方法,在该方法中判断是否是被加载过,若没有主要用到了findClass()方法,而在ClassLoader.java中,findClass()是一个空实现,于是到PathClassLoader,DexClassLoader中查看
发现两者都是共同父类BaseDexClassLoader中实现的,此时调用到了DexPathList对象的findClass()方法,而DexPathList对象实在BaseDexClassLoader的构造方法中通过new实现的,于是找到
DexPathList类的构造方法中找到makeDexElements()方法,该方法返回一个Element[]数组,在该方法makeDexElements()中遍历所有的Dex文件,调用loaddexFile()方法,在该方法中
返回了DexFile对象,并最终存到Element[]数组中。此时,再来看DexPathList对象的findClass()方法中遍历Element[]数组,对数组中的每一个Element元素(DexFile)调用DexFile对象的
loadClassBinaryName()方法返回一个Class对象,而loadClassBinaryName()最终调用的是Native方法defineClassNative()该方法是C与C++实现的。
Android动态加载的难点:
1.有许多组件类需要注册之后才能使用
2.资源的动态加载很复杂
3.Android程序运行时需要一个上下文环境
第四章热修复详解:
热修复的基本概念讲解
当前市面上比较流行的几种热修复技术
方案对比及技术选型
什么是热修复?
动态修复、动态更新
热修复有哪些好处
热修复的流行技术:
QQ空间的超级补丁方案
微信的Tinker
阿里的AndFix,dexposed(最新版本Sophix 3.2.0)
美团的Robust,饿了吗的migo,百度的hotfix
第五章 本章概述
AndFix的基本介绍
AndFix执行流程及核心原理
使用AndFix完成线上bug修复
AndFix源码讲解
https://www.cnblogs.com/wetest/p/7595958.html
https://segmentfault.com/a/1190000011365008
http://blog.csdn.net/lostinai/article/details/54694959
AndFix修复即时生效的原理:
腾讯系的方案为什么不能及时运行?
腾讯系的方案是基于类加载机制采用全量替换的原理完成的,由于类加载的双亲代理特点,已加载到内存中的Class是不会再次加载的。
因此,采用腾旭系方案,不会及时生效,不重启则原来的类还在虚拟机中,就无法加载新类,当再次启动的时候,去加载新合成的dex文件,以完成增量更新。
这样Tinker将新旧两个DEx的差异放在补丁包中,下发到移动端,在本地采用反射的原理修改dexElements数组,合成完整的dex文件。
但是全量替换会造成补丁包体积过大,因此,采用了Dexdiff算法,大大优化下发差异包的大小。
在Android N中完全废弃掉PathClassloader,而采用一个新建Classloader来加载后续的所有类,即可达到将cache无用化的效果。
AndFix的方案采用底层替换方案,即:通过修改一个方法包括方法入口地址在内的每一项数据,使之指向一个新的方法。
具体来讲就是:
通过反射机制得到所要替换的Method对象所对应的object,进而找到这个方法底层Java函数所对应ArtMethod的真实地址,把旧函数的所有变量都替换为新函数的
因为,每一个java方法在DVM/ART中都对应着一个底层Java函数,在Art模式中是:ArtMethod,ArtMethod记录了这个Java方法的所有信息,包括所属类、访问权限、代码执行地址等等。
通过env->FromReflectedMethod,可以由Method对象得到这个方法对应的ArtMethod的真正起始地址。然后就可以把它强转为ArtMethod指针,从而对其所有成员进行修改。
这样全部替换完之后就完成了热修复逻辑。以后调用这个方法时就会直接走到新方法的实现中了
AndFix兼容性的根源所在:
采用的Native替换方案:都是写死了ArtMethod结构体(Andfix是把底层结构强转为了art::mirror::ArtMethod),AndFix里面的ArtMethod是遵照android虚拟机art源码里面的ArtMethod构建的。
由于Android是开源的,各个手机厂商都可以对代码进行改造,而Andfix里ArtMethod的结构是根据公开的Android源码中的结构写死的,若是厂商对开源的ArtMethod结构体进行了修改,
和源码里面的结构体不一致,替换则出现了问题。
优化策略:
直接替换ArtMethod而不是直接替换,以解决兼容性问题,
即时生效带来的限制:只能是方法级别的替换,不能添加新的字段,不能增加方法,以防止在计算sizeOf()时出现差错。
AndFix支持从2.3到7.0的Android版本,包括ARM和X86架构,Dalvik和ART运行时,32位和64位
AndFix的实现原理是方法的替换,使得有Bug的方法永远都不会被执行到。(是阿里的热修复开源版本)当前最新的
热修复版本是阿里HotFix 3.0(Sophix 3.2.0)
https://help.aliyun.com/document_detail/61082.html?spm=a2c4g.11186623.6.547.3I5XDy Sophix 3.2.0的稳健接入
dexposed和andfix是alibaba的开源项目,都是apk增量更新的实现框架,目前dexposed的兼容性较差,只有2.3,4.0~4.4兼容,
其他Android版本不兼容或未测试,详细可以去dexposed的github项目主页查看,而andfix则兼容2.3~6.0,所以就拿这个项目来实现增量更新吧。
AndFix的集成阶段
Gradle添加依赖 compile 'com.alipay.euler:andfix:0.5.0@aar'
1.https://github.com/hanfengzqh/AndFix AndFix的GitHub地址
1.AndFix的 PatchManager 的初始化
mPatchManager = new PatchManager(context);
mPatchManager.init(Utils.getVersionName(context));//
//进行热修复之后,为了将SortedSet
mPatchs中的Patch每一个文件取出调用mAndFixManager.fix();
mPatchManager.loadPatch();
2.加载我们的patch文件
if (mPatchManager != null) {
//网络有热修复文件(.apatch)时调用,将.apatch补丁文件,复制到包内/apatch/文件夹下,并将
//包内文件夹下的.apatch文件Patch文件,转换为调用loadPatch(Patch)方法
mPatchManager.addPatch(path);
}
AndFix的准备阶段
1.build一个有bug的old apk并安装到手机上
2.分析问题解决bug后,build一个new apk
补丁会生成一个以.apatch结尾的文件
AndFix bug修复的常用两条指令:
usage: apkpatch -f -t -o