本文有两个目的:
1.是分析一下hotfix的实现,
2.换一种实现防止类打上CLASS_ISPREVERIFIED标识
首先分析一下hitfix的实现:
hotfix工程结构
1.app是demo的工程
2.builSrc是操作class文件的封装类,做的操作是向class的构造函数中插入了这么一段代码
编辑class文件使用的工具是javassist。
3.hackdex工程是防止类呗打上impressive标识的类。
4.hotfixlib是动态加载工具的封装
动态修复有两个关键的点:
1.防止类被打上CLASS_ISPREVERIFIED
2.动态加载需要修复的类
1.防止类被打上CLASS_ISPREVERIFIED
防止类被打上CLASS_ISPREVERIFIED的关键是:可能出现bug需要热修复的类必须引用一个app外部的类
hotfix的做法是使用腾讯公开的方法,对应的类是PatchClass,使用javassist工具在app打包之前,把可能需要修复的类的class文件进行修改,每个类的构造中都插入了这句话。
引用的类AntilazyLoader以jar包的方式动态加载,放在了asset文件夹中,程序运行时把jar拷贝到sd卡中,这个jar是用dx命令处理过的jar文件,命令如下(loader是要处理的jar,loader_dex.jar是处理后的jar):
dx --dex --output=loader_dex.jar loader.jar
工程的gradle文件中需要做的是:
这是一个task,gradle编译工程实际上就是由多个task组成,按照一定的顺序执行task。例如先编译java代码,打包代码为jar,打包成app,这三个操作可以写作3个task,按照顺序执行就生成了一个app。当然实际的过程不是这样,这里只是一个比喻。
然后是:
在最下面的红色方框中有一句话variant.dex.dependsOn << processWithJavassist //在执行dx命令之前将代码打入到class中
这里的variant.dex是应该是在app打包过程中打包处理class文件的任务。dependsOn表示依赖与某个任务,只有一排的任务执行完了它才能执行。后面的processWithJavassist就是前面提到的task,这个task的目的就是向class文件中的狗仔函数插入代码。这句话的意思是处理class文件之前(比如打包成dex文件)先向每个class文件的构造中插入代码,然后再打包。这样每个class都引用一个app外部的类,防止打上CLASS_ISPREVERIFIED标识。
2.动态加载需要修复的类
动态加载的核心原理就是下面两张图,是BaseDexClassLoader的源码(图非原创,源自网络,本人没找到这两个类的源码。。。),BaseDexClassLoader中的DexPathList的dexElements属性,存储的是所有的类。
把修复好bug的类合并到app的classloader的dexElements中,就可以用修复好bug的类替换原有的类了。
hotfix加载类的源码是:
str是包涵修复好bug类的jar包路径,str2是需要加载的类名。本质上的操作时实例化一个classloader,加载补丁包,将dexElenemts合并到app的classloader的dexElements数组中,并且放到最前面。(其实最后一句pathClassLoader.loaderClass(str2)不要也可以)
改进:hotfix框架为了实现类中引用app以外的类,通过使用javassist向字节码中插入代码的方式实现。这种方式要引用javassist包并且需要对javassist有一定的功底。另外还需要对gradle构建android项目有比较深入的了解。
笔者所说的改进是:要实现引用app外部的类还有一个方法,只需要把相关的类打包成jar,但是编译app时不将jar打包进app即可。
这里的改进涉及到插件化开发的一些知识,先给大家讲解一下基本的设计思路,深入了解需要自行学习,这里不做深入讲解。
对插件化开发或者更换app主题(Theme)了解的童鞋们可能知道,当我们想动态更换app的主题时,
首先,一般会有一个公共接口Interface库,里面定义了基本的主题(Theme)内容,比如:标题字体颜色、背景色等等,我们的app要引用这个Interface库。
第二,Interface导出jar,供主题(Theme)工程使用,重点来了,使用Interface接口的jar包,但是打包主题(Theme)的时候,不管你是打包成app还是其它的包(例如arr,貌似可以打包资源文件)不要把Interface的jar包打包进去,否则会报错,错误类型是:
Class ref in pre-verified class resolved to unexpected implementation
和热修复时类被打上了CLASS_ISPREVERIFIED标识报的错误一样。
当时研究插件化开发的时候还不是很理解这个错误的根本所在,后来研究热修复的时候想起来了这一点,前后一对比发现两者的错误类型是一样的,就想是不是可以不借助javassist和gradle中修改variant.dex.dependsOn
<< processWithJavassist 进行防止类被打上CLASS_ISPREVERIFIED标识。
看到这里有的童鞋可能猜到接下来怎么做了。既然借助javassist和修改gradle编译过程是为了实现引用app外部类,那为何不打包一个jar1,所有可能被热修复的类都引用这个类,但是打包app的时候不要把这个jar1文件打入app中不就可以了么。jar2包放在asset文件夹中app运行的时候考到sd卡中,动态加载jar2就可以了。
这里又一个需要注意的点,对于初步接触热修复的童鞋有一个容易犯的错误,上面我提到了两个jar包jar1和jar2,他们有什么区别和联系呢?我们开发的时候引用的jar包都是正常的jar包,但是android的classloader加载的jar包必须是dex命令处理过的,就是上面我提到的dx命令:
dx --dex --output=loader_dex.jar loader.jar
jar1是正常情况导出的包,我们在项目中引用的包。jar2是jar1用dx命令处理过的,放在asset中,app运行时考到sd卡中,动态加载。jar1和jar2包含的类是完全一样的,区别就是一个没有用dx处理过,一个用dx命令处理过。
至于如何实现打包时不把jar打包进app,我简单说一下,网上都可以搜到。
1.eclipse jar包不要放到libs文目录下,libs目录下都是会被打入app中的,你可以随便见一个目录,jar包考进去,右键jar包,build path ---> add to build path即可,这样这个jar就不会被打入app中。
2.studio 依赖某个jar包一般是compile fileTree(dir: ‘libs‘, include: [‘*.jar‘]),表示编译libs下所有的jar文件。我们需要建一个新目录,把jar考进去,然后provided ‘需要引用的jar‘,正常的compile是表示把引用的东西都要打包进app,provided表示只引用不打包。有的童鞋可能想为啥还要打包jar1,直接provided工程不就的了么,只需要一个jar2就够了。因为provided只能引用jar包,不能引用工程。。。
以上就是所有内容,亲测没问题,欢迎指正。