rocoofix 怎么生成git 补丁 生成

RocooFix源码分析 - 简书
RocooFix源码分析
RocooFix很重要的一部分就是他的gradle插件,本文着重记录插件部分,而且主要针对gradle1.4以上的情况
插件(buildsrc)
RocooFix解决了nuwa不能在gradle1.4以上生效,主要是1.4以上引入的 transform API(官网解释The API existed in 1.4.0-beta2 but it's been completely revamped in 1.5.0-beta1),导致preDexTask dexTask proguardTask 都不能被直接find到(因为高版本的gradle换了task的名字 transformxxxx,而nuwa的name是写死的,导致不能被findByName找到,RocooFix通过判断当先的gradle版本来确定是不是加上transformxxxx)。 在1.4版本以上,修复的主要逻辑在 if(preDexTask) 这个判断语句的else if 里面
def rocooJarBeforeDex = "rocooJarBeforeDex${variant.name.capitalize()}"
project.task(rocooJarBeforeDex) && {
Set&File& inputFiles = RocooUtils.getDexTaskInputFiles(project, variant,
inputFiles.each { inputFile -&
def path = inputFile.absolutePath
if (path.endsWith(SdkConstants.DOT_JAR)) {
NuwaProcessor.processJar(hashFile, inputFile, patchDir, hashMap,
includePackage, excludeClass)
} else if (inputFile.isDirectory()) {
//不处理不开混淆的情况
//intermediates/classes/debug
def extensions = [SdkConstants.EXT_CLASS] as String[]
def inputClasses = FileUtils.listFiles(inputFile, extensions,
inputClasses.each { inputClassFile -&
def classPath = inputClassFile.absolutePath
if (classPath.endsWith(".class") && !classPath.contains(
"/R\$") &&
!classPath.endsWith("/R.class") &&
!classPath.endsWith("/BuildConfig.class")) {
if (NuwaSetUtils.isIncluded(classPath,
includePackage)) {
if (!NuwaSetUtils.isExcluded(classPath,
excludeClass)) {
def bytes = NuwaProcessor.processClass(
inputClassFile)
if ("\\".equals(File.separator)) {
classPath =
classPath.split("${dirName}\\\\")[1]
classPath =
classPath.split("${dirName}/")[1]
def hash = DigestUtils.shaHex(bytes)
hashFile.append(
RocooUtils.format(classPath, hash))
if (RocooUtils.notSame(hashMap, classPath,
def file = new File(
"${patchDir}${File.separator}${classPath}")
file.getParentFile().mkdirs()
if (!file.exists()) {
file.createNewFile()
FileUtils.writeByteArrayToFile(file, bytes)
def rocooJarBeforeDexTask = project.tasks[rocooJarBeforeDex]
rocooJarBeforeDexTask.dependsOn dexTask.taskDependencies.getDependencies(
rocooJarBeforeDexTask.doFirst(prepareClosure)
rocooJarBeforeDexTask.doLast(copyMappingClosure)
rocooPatchTask.dependsOn rocooJarBeforeDexTask
dexTask.dependsOn rocooPatchTask
先创建了名字为 rocooJarBeforeDex的task,在task里先获取在class被打包为dex之前的所有输入文件。看下RocooUtils#getDexTaskInputFiles()
static Set&File& getDexTaskInputFiles(Project project, BaseVariant variant, Task dexTask) {
if (dexTask == null) {
dexTask = project.tasks.findByName(getDexTaskName(project, variant));
if (isUseTransformAPI(project)) {
def extensions = [SdkConstants.EXT_JAR] as String[]
Set&File& files = Sets.newHashSet();
dexTask.inputs.files.files.each {
if (it.exists()) {
if (it.isDirectory()) {
Collection&File& jars = FileUtils.listFiles(it, extensions, true);
files.addAll(jars)
if (it.absolutePath.toLowerCase().endsWith("intermediates${File.separator}classes${File.separator}${variant.dirName}".toLowerCase())) {
files.add(it)
} else if (it.name.endsWith(SdkConstants.DOT_JAR)) {
files.add(it)
return files
return dexTask.inputs.files.
他先遍历所有的输入文件(注意下FileUtils.listFiles的用法),因为输入文件包括文件和文件夹,分情况将文件和文件夹放入文件set中
如果是文件夹,把文件夹内后缀为jar的文件取出放入set
如果文件夹的绝对路径以intermediates/classes + variant.dirName结尾(文件夹里都是.class文件),就把这个文件夹放到set
如果是文件,而且后缀是jar就把这个文件放入set
获取到所有的输入文件后,对其进行统一处理,其实也是针对jar文件和class文件。
对于jar文件,则直接沿用nuwa的处理方式NuwaProcessor#processJar ,就不贴出这个方法的代码了,大概要实现的就是判断输入的文件(jar)中的类是否要注入Hack(处理ISPREVERIFIED),需要注入的类以操作字节码的形式注入Hack类到构造函数里,听过美团的robust的知乎live,据说这样处理不会增加方法数是因为本来都会给每一个类增加一个默认的构造函数,所以操作构造函数不会增加方法数(字节码操作使用开源库asm)。值得一提的是,NuwaProcessor#processJar这个方法还将mapping文件传入,是为了处理混淆的情况,mapping文件保存的是上一次的混淆配置,使用这个才能让补丁类定位到真正的打补丁的位置,要不然会gg。
接下来就到了下一个else if语句中,这段分支语句就是处理之前说的文件set的第二点,文件夹intermediates/classes/xxxx,里面放置的都是class文件,针对class进行处理。它还是用FileUtils.listFiles方法取出这些文件夹中的.class文件以一个文件set保存,接着遍历这个set,剔除不应该注入的类(R文件类,BuildConfig相关类,在gradle中标注不需要热修复的类等等),后调用NuwaProcessor#processClass这个方法来处理应该注入Hack到构造函数中的类,还是字节码啦。 之后就是生产hash文件的逻辑了。
跳出处理文件和插入字节码的task就是处理每一个task顺序的问题,可以看到rocooJarBeforeDexTask要依赖于dexTask.taskDependencies.getDependencies(dexTask),也就是原来的dexTask之前的任务(将class/jar打包为dex之前的任务) 。也就是rocooJarBeforeDexTask要在原本dexTask之前的任务的之后,在执行rocooJarBeforeDexTask开始的时候doFirst执行prepareClosure闭包的任务,在执行rocooJarBeforeDexTask结束的时候通过doLast执行copyMappingClosure闭包的任务。rocooPatchTask (制作补丁的task)在rocooJarBeforeDexTask之后执行,之后原本的dexTask要在制作补丁之后执行。
所以顺序是这样的 :原本dexTask之前就要执行的task -&
字节码注入的task -& prepareClosure -& copyMappingClosure -& 制作补丁的(taskrocooPatchTask) -& dexTask(字节码到dex)
ps :prepareClosure 和 copyMappingClosure 方法的执行 应该是在rocooJarBeforeDexTask任务开始和结束的时候执行
def rocooJarBeforeDexTask = project.tasks[rocooJarBeforeDex]
rocooJarBeforeDexTask.dependsOn dexTask.taskDependencies.getDependencies(
rocooJarBeforeDexTask.doFirst(prepareClosure)
rocooJarBeforeDexTask.doLast(copyMappingClosure)
rocooPatchTask.dependsOn rocooJarBeforeDexTask
dexTask.dependsOn rocooPatchTask
RocooFix还封装了从补丁类到dex的功能RocooUtils#makeDex,从代码可以看出,是用代码调用了Android的build-tools的dex工具,将jar打包为Android运行的dex文件。
public static makeDex(Project project, File classDir) {
if (classDir.listFiles() != null && classDir.listFiles().size()) {
StringBuilder builder = new StringBuilder();
def baseDirectoryPath = classDir.getAbsolutePath() + File.
getFilesHash(baseDirectoryPath, classDir).each {
builder.append(it)
def hash = DigestUtils.shaHex(builder.toString().bytes)
def sdkDir
Properties properties = new Properties()
File localProps = project.rootProject.file("local.properties")
if (localProps.exists()) {
properties.load(localProps.newDataInputStream())
sdkDir = properties.getProperty("sdk.dir")
sdkDir = System.getenv("ANDROID_HOME")
if (sdkDir) {
def cmdExt = Os.isFamily(Os.FAMILY_WINDOWS) ? '.bat' : ''
def stdout = new ByteArrayOutputStream()
// 注意看这里 调用dex工具的命令行方法
project.exec {
commandLine "${sdkDir}${File.separator}build-tools${File.separator}${project.android.buildToolsVersion}${File.separator}dx${cmdExt}",
"--output=${new File(classDir.getParent(), PATCH_NAME).absolutePath}",
"${classDir.absolutePath}"
standardOutput = stdout
def error = stdout.toString().trim()
if (error) {
println "dex error:" + error
修复Libs(RocooFix)
dex插入就不说了,已经有很多现有的优秀文章了,值得一提的是RocooFix支持runningTimeFix,和普通Java修复的方式不同的是,他使用了 也就是nativeHook的形式,实现了即时修复的效果,同阿里系的nativeHook修复方式,HookManager就是Legend中hook的类了。
private static void replaceMethod(Class&?& aClass, Method fixMethod, ClassLoader classLoader) throws NoSuchMethodException {
Method originMethod = aClass.getDeclaredMethod(fixMethod.getName(), fixMethod.getParameterTypes());
HookManager.getDefault().hookMethod(originMethod, fixMethod);
} catch (Exception e) {
Log.e(TAG, "replaceMethod", e);
Android学习者
Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线)。分布式系统的协调导致了样板模式, 使用Spring Cloud开发人员可以快速地支持实现这些模式的服务和应用程序。他们将在任何分布式...
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金相信有很多朋友...
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金 相信有很多...
百战程序员_ Java1573题 QQ群:034603 掌握80%年薪20万掌握50%年薪10万 全程项目穿插, 从易到难,含17个项目视频和资料持续更新,请关注www.itbaizhan.com 国内最牛七星级团队马士兵、高淇等11位十年开发经验专...
第十章:Android的消息机制 Handler是在某一个线程中创建的,然后在其他线程中去调用,因此handler可以轻松实现业务逻辑在指定线程中调用。最常见的就是更新UI的业务逻辑在子线程调用,主线程处理。 由于控件的更新不是线程安全的,因此要使用主线程Handler去更...
眼睛干涩难受,但还没敷面膜,等敷完面膜再摘阴隐形眼镜吧,不然看不清镜中的自己,怕面膜在脸上铺不平。
我的拖延症很严重,本以为这周五出差的,所以昨天开始忙出差的工作,后来才发现我记错时间了,应该是下周五出差,幸亏犯了这错误,不然手头的工作又不知何时做!都说细节决定成败...
浪卷飞沙, 大漠迷天, 风暴炫舞为谁狂。 弯弓搭箭, 射断天涯, 意气风发到谁家。 高山揽月, 邀客诗发, 傲视平川盼谁夸。 大海无助, 涛载奇葩, 乐斩波澜摇碎家。 骄阳似火, 汗落云霞 蒸蒸日上为谁狂。 蔑视蛮夷, 吓破贼胆, 南海军演万炮发。 玩弄霸权, 妄想独大, ...
调查就像“十月怀胎”,解决问题就像“一朝分娩”。——毛泽东 来源: 王辉的日志 反对本本主义 (一九三年五月) 毛泽东的这篇文章是为了反对当时红军中的教条主义思想而写的。那时没有用“教条主义”这个名称,而叫它做“本本主义”。 一没有调查,没有发言权⑴ 你对于某个问题没有调查...
A . 学习内容 1.绝对路径、相对路径2.表单相关元素3.iframe4.关于内联元素5.切图 B . 学习到的要点总结 1. 绝对路径、相对 绝对路径 ———— 从盘符开始的路径 相对路径 ———— 相对当前文件所在的路径—— 同级目录
src='down.j...
一直以来,我都喜欢奔跑。而且还跑的很快,总会跑在前边,以至于现在走路都过快。我身边的亲人朋友都说我走路带风。没错从小到大真的很享受奔跑前方的感觉但是却不知走在后头的感受。 还记得在还没上学的时候,跟着爷爷奶奶生活。爷给我过生日,煮了一锅面条,端着从厨房往外走。我当时五六岁个...[热修复]-源码级分析以及项目实践 - 简书
[热修复]-源码级分析以及项目实践
最近在研究gradle ,插件化~自己碰到的坑很多.今天先总结一下
以下这三个都研究过,原理都是一样的,区别就在于用哪个更方便.
在这里我会讲述一下,这里面的原理和自己爬的坑,以便大家理解,还有少爬坑~~
原理是需要懂得~
不然,你遇到错误不会解决,并且你始终会是初级工程师~
首先,按照顺序,介绍下目前三种热修复的方式:
1.Nuwa (基于gradle写的脚本,操作起来比较麻烦,需要拷贝和运行命令行~)
2.andfix (看过~)
3.基于Nuwa和andfix升级的RocooFix(上线项目正在用)
有两种模式 静态修复某种情况下需要重启应用。2动态修复,无需重启应用即可生效。
4.如果觉得上述的几个demo,没有实战的结合,那么请用我的项目玩一玩.自己的git成熟的架构项目,里面集成了热修复RocooFix,当然也可以学习其中的架构,大家可以下载,欢迎大家批评~
一.热修复原理
Android 插件化技术
这里写图片描述
插件化其实就是class动态加载技术
其中,热修复就是讲dex,进行分包,然后让虚拟机先加载第一个dex,的过程.
当然,说永远比做简单,对,这就是我现在还不能像这些大师一样,动手写点为人民服务的东西,自己会努力的~~ ps(鸡汤):程序员靠手吃饭,而不是靠嘴~~
原理第一步:理解CassLoader,
Android 中有三个 ClassLoader, 分别为URLClassLoader、PathClassLoader、DexClassLoader。其中
URLClassLoader 只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器。
PathClassLoader 它只能加载已经安装的apk。
DexClassLoader 在虚拟机上加载 dex上面的类
DexClassLoader 包含有一个dex数组Element[] dexElements,其中每个dex文件是一个Element,当需要加载类的时候会遍历 dexElements,如果找到类则加载,如果找不到从下一个 dex 文件继续查找。
BaseDexClassLoader源码(大家可以自行研究):
原理第二步:防止被打上CLASS_ISPREVERIFIED.
当dalvik在加载android程序时候,其实不是读的class文件,
而是首先对dex进行优化,生成Odex(Optimized dex )文件(为了适配不同硬件平台),
虚拟机在启动优化的时候,会有一个选项就是 verify 选项,当 verify 选项被打开的时候,就会执行一次校验,校验的目的是为了判断,这个类是否有引用其他 dex 中的类,如果没有,那么这个类会被打上一个 CLASS_ISPREVERIFIED 的标志。一旦被打上这个标志,就无法再从其他 dex 中替换这个类了。而这个选项开启,则是由虚拟机控制的。(引自张涛-开源实验室)
二.代码实现
RocooFix代码实现之app如何动态加载更新:合并dex:
实现原理和Nuwa一样~,只不过它对sdk进行了适配.
都是分别获取主应用和补丁的dex中的PathList.dexElements,然后将两者进行拼接(补丁包在最前面即, newEles[0] =),形成新的array,然后返回给classLoader.
private static final class V24 {
private static void install(ClassLoader loader, List&File& additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException, InstantiationException, ClassNotFoundException {
//寻找pathList,并且将私有变为可访问
Field pathListField = findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
//寻找寻找pathList的dexElements,并且将私有变为可访问
Field dexElement = findField(dexPathList, "dexElements");
Class&?& elementType = dexElement.getType().getComponentType();
Method loadDex = findMethod(dexPathList, "loadDexFile", File.class, File.class, ClassLoader.class, dexElement.getType());
loadDex.setAccessible(true);
//利用反射调用类中的方法
Object dex = loadDex.invoke(null, additionalClassPathEntries.get(0), optimizedDirectory, loader, dexElement.get(dexPathList));
Constructor&?& constructor = elementType.getConstructor(File.class, boolean.class, File.class, DexFile.class);
constructor.setAccessible(true);
Object element = constructor.newInstance(new File(""), false, additionalClassPathEntries.get(0), dex);
//将将两者进行拼接(补丁包在最前面即, newEles[0] =)
Object[] newEles = new Object[1];
newEles[0] =
expandFieldArray(dexPathList, "dexElements", newEles);
RocooFix代码实现之如何编译生成补丁包,强大的grooxy脚本构建项目:
如何逃过 虚拟机打上CLASS_ISPREVERIFIED ?
在RocooFixPlugin中作者采用了nuwa的字节码的方法
HotFixProcessors.processClass()
来避免这个问题.有兴趣的大家可 以研究一下
如何判定那个文件进行了改变?
在 project.task(rocooClassBeforeDex) 中
NuwaProcessor.processJar(hashFile, inputFile, patchDir, h
ashMap, includePackage, excludeClass)
循环遍历工程中的全部类对每个类计算hash值,并写入到hashFile文件中.通过比较hashFile文件与原工程的hashFile(即这里的hashMap参数) ,得到所有修改过的类生成这些类的class文件,以及所有修改过的class文件的集合jar文件。
def path = inputFile.absolutePath
//借用nvwa的代码,就是找出哪些类是发生了改变,应该生成对应的补丁。
//如果不是support包或者引入的依赖库,则开始生成代码修改部分的hotfix包
if (NuwaProcessor.shouldProcessPreDexJar(path)) {
NuwaProcessor.processJar(hashFile, inputFile, patchDir, hashMap, includePackage, excludeClass)
/*循环遍历循环遍历工程中的全部类对每个类计算hash值,并写入到hashFile文件中.通过比较hashFile文件与原工程的hashFile(即这里的classHashMap参数) ,得到所有修改过的类生成这些类的class文件,以及所有修改过的class文件的集合jar文件。
NuwaProcessor.processClasses(inputFile, includePackage, excludeClass, dirName, hashMap, patchDir)
def path = inputFile.absolutePath
if (path.endsWith(".class") && !path.contains("/R\$") && !path.endsWith("/R.class") && !path.endsWith("/BuildConfig.class")) {
if (NuwaSetUtils.isIncluded(path, includePackage)) {
if (!NuwaSetUtils.isExcluded(path, excludeClass)) {
//写入字节吗,防止被打上class_prevertified
def bytes = NuwaProcessor.processClass(inputFile)
path = path.split("${dirName}/")[1]
def hash = DigestUtils.shaHex(bytes)
hashFile.append(RocooUtils.format(path, hash))
if (RocooUtils.notSame(hashMap, path, hash)) {
def file = new File("${patchDir}/${path}")
file.getParentFile().mkdirs()
if (!file.exists()) {
file.createNewFile()
FileUtils.writeByteArrayToFile(file, bytes)
三.项目实践(具体项目请看我的github)
配置roccofix简单配置 请到,
详细的配置说明,请查看我的git项目
public class RocooApplication extends Application {
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
RocooFix.init(this);
//方案1:静态启用,一般在Application里加载补丁
* 从Assets里取出补丁,一般用于测试
* @param context
* @param assetName
RocooFix.initPathFromAssets(Context context, String assetName);
* 从指定目录加载补丁
* @param context
* @param dexPath
RocooFix.applyPatch(Context context, String dexPath);
//方案2:动态打补丁,立即生效,有性能问题,适用于补丁方法数较少的情况,建议在ART虚拟机里启用该模式
* 从Asset里加载补丁,一般用于本地测试
* @param context
* @param assetName
RocooFix.initPathFromAssetsRuntime(Context context, String assetName) ;
* 从指定目录加载补丁
* @param context
* @param dexPath
RocooFix.applyPatchRuntime(Context context, String dexPath)
Configuration
在root的build.gradle增加如下内容:
repositories {
url "http://dl.bintray.com/dodola/maven"
dependencies {
classpath 'com.dodola:rocoofix:1.1’
在你项目的build.gradle文件里添加如下配置
apply plugin: 'com.dodola.rocoofix'
repositories {
"http://dl.bintray.com/dodola/maven"
rocoo_fix {
includePackage = ['com/dodola/rocoofix']//限制需要制作补丁的package
excludeClass = ['BaseApplication.class']//将不需要加到patch里的类写在这里
preVersionPath = '1'//注意:此项属性只在需要制作补丁的时候才需开启!!如果不需要制作补丁则需要去掉此项
enable = true//注意:关掉此项会无法生成Hash.txt文件
scanref=true//默认为 false,开启这个选项会将与补丁 class 相引用的 class 都打入包中来解决 ART 虚拟机崩溃问题,功能 Beta 中
dependencies {
compile 'com.dodola:rocoo:1.0'
这里主要介绍一下preVersionPath这个属性的作用。
rocoo_fix将制作补丁的步骤透明化,用户无需手动备份hash.txt文件,插件会自动根据当前的versionCode生成hash.txt和mapping.txt文件到指定目录,比如:
上一个版本发布的时候版本号是1,那么生成的文件会放在app源码目录/rocooFix/version1/[debug]|[release]的目录下,如果需要制作补丁那么在配置里指定preVersionPath 属性,它的值是上一个版本的版本号,这里的值是1,
然后将build.gradle的versionCode的号码修改,这里修改成2,只要和之前的版本不同就可以,没有具体值的要求
-keep class com.dodola.rocoofix.** {*;}
-keep class com.lody.legend.** {*;}
Build Patch
下面演示一下使用项目demo生成补丁的制作过程
假如我们需要打补丁的文件是
package com.dodola.
public class HelloHack {
public String showHello() {
return "hello world";
此时build.gradle里的VersionCode是1
这里写图片描述
运行一次应用,这时会在app的目录下生成如下文件:
这里写图片描述
这里可以看做是我们已经发布版本的hash.txt
假设我们需要修复步骤1 里的showHello方法,修改如下:
package com.dodola.
public class HelloHack {
public String showHello() {
return "hello Hack";//此处修复,补丁加载后该方法返回hello hack
修改build.gradle 文件里rocoo_fix项,让其执行patch 的task,配置如下
rocoo_fix {
preVersionPath = '1'//注意:这里指定的是需要打补丁的VersionCode
enable = true
修改当前项目的versionCode为2,说明这个是一个升级fix版本。
enter description here
正常发布应用,此时会在下图所示的路径中生成补丁文件:
这里写图片描述
我们可以反编译一下来确认补丁是否正常
这里写图片描述
这边说几点注意事项:
1.注意代码混淆配置,如果项目第三方比较多,就比较蛋疼了...,可仿照
的混淆规则进行编写.
对于不会进行 proguard-rules的同学
1.Warning: can't find referenced field/method '...' in library class (or: can't find referenced classes..)
2.Warning: library class ... depends on program class ...
3.Warning: class file ... unexpectedly contains class ...
Warning:Ignoring InnerClasses attribute for an anonymous inner class
报错情况1,3:
大概意思是程序里面用到了库里的类,但库里没有这些类。所以警告了,同时,可以用命令来忽略这些警告。
只是忽略警告的话,实际上还是没有解决问题,后来查找了一些资料,发现是proguard在混淆的时候,把库里的jar包也混淆了,导致程序找不到这些类。于是解决方法就简单了,只需要在proguard.conf配置中用keep命令保留报警的类就行了。但是通常报警的类有成百上千个,不可能一个个保留,有一个简单的方法是只要类的路径前面有相同的根,就可以保留一个总的达到保留下面所有类的目的。比如:
1.-dontwarn org.springframework.**
//报错的包路径
2.-keep class org.springframework.** { *;}
//报错的包路径
报错情况2:
第2个问题与第一个类似,也是保留相应的类就可以了。
大概意思是类文件的目录名跟类的包名不一致导致了警报,需要确保目录和包名一样才行。特别是WEB-INF/classes目录下的文件需要打成jar包放到WEB-INF/lib 目录下。
我的这个问题正是出在WEB-INF/classes/....目录这儿,然后我把classes下的类打包放到了lib下,结果然并卵。后来又试了试,发现因为proguard会同时读取classes下的类和lib下的jar包,jar包像上面说的一样是没有问题的,但是classes下的类目录必然要带着classes,然而类名却不是以WEB-INF.classes开头,所以这个警报不可避免会出现。最后消除警报是在-injars里面添加过滤把classes下的类都过滤掉,还有别忘了把jar包添加到lib里。
-injars D:/OrbitService/target/OrbitService.war(!WEB-INF/classes/**)
报错情况4 : 出现这个问题的愿意很简单。
就是android sdk tools版本过低,升级一下就可以了
2.生成patch.jar的步骤
生成patch流程
3.混淆一定要开.
至于没什么,因为涉及到hash和mapping,还有源码的脚本中
4.在实际项目中运用
进入app 请求服务器,看是否有补丁,以及补丁的版本,然后根据版本进行判断, 下载 调用 RocooFix.applyPatch(context, dexPath); ,
同时 记录版本信息, 保存到 sharepreference, 然后悄悄的进行,下次启动再请求网络,得到数据,根据数据判断补丁版本是否对应, 然后进行上述操作~,
5.android热修复,原理是一样的,有些错误不认识,注意查看 message
这里写图片描述
关于具体的操作,解释.我这边都放在了自己的代码中,大家可以查看,理解.
最后,附上自己的请查看我的git项目,我会不定期的更新,整合欢迎star谢谢~:
努力的人,运气总不会太差。
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金相信有很多朋友...
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金 相信有很多...
读书笔记,写写画画记忆更深刻,如果还能梳理一下的话,那就更好了。 热修复技术介绍 探索之路 最开始,手淘是基于Xposed进行了改进,产生了针对Android Dalvik虚拟机运行时的Java Method Hook技术——Dexposed。 但该方案对于底层Dalvik...
针对app线上修复技术,目前有好几种解决方案,开源界往往一个方案会有好几种实现。重复的实现会有造轮子之嫌,但分析解决方案在技术上的探索和衍变,这轮子还是值得去推动的 关于Hot Fix技术 Hot Fix技术,简单来说就是针对线上已发布app出现了bug,在不推送新版本的情...
一、概述 最新github上开源了很多热补丁动态修复框架,大致有: https://github.com/dodola/HotFix https://github.com/jasonross/Nuwa https://github.com/bunnyblue/DroidFi...
【我的一周目标】:练习按照写的情绪处理流程处理情绪 【进度】: 1完成情绪处理流程图 2完成情绪消解练习9个 3写情绪分析7篇 4流程图增加流程2个 目标达成进度100% 【总结】: 这次上课收获不少,之前没有想明白的一些事情能想得更加清晰了。 对于周目标而言,设制一定要量...
红色文化调研心得体会(原版)
日至21日,为了追寻革命足迹,学习红色文化,我与几个同学一起参观了杨虎城将军烈士陵园、革命公园等地。这次活动是使我们在了解中国革命相关历史知识的同时,接受的一次难得的爱国主义传统文化教育。
六月三十号,我怀着满腔热情,从家里出发去上海,途中暴雨,湿透了一双鞋子,扔掉了。 七月一号早上七点,抵达上海,望着林立的摩天大楼,我和朋友都觉得这个暑假一定能干一番大事。一晚没动的胃提醒我们该吃点东西了,于是草草吃了一碗米粉,转而去面试。 第一次是面试KTV服务员,工资很理...
土壤微生物驱动亚热带森林演替过程中土壤有机碳的形成和周转,且土壤微生物残留物是土壤有机碳库的重要来源,在长期土壤碳固持方面起重要作用。氨基糖是微生物残留物的生物标识物,其积累和转化特性可以指示微生物对土壤有机质积累的贡献。在亚热带森林演替过程中,地上植被从针叶林转变为阔叶林...}

我要回帖

更多关于 steamfix补丁 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信