根据上面的文章,在虚拟机启动的时候,当verify选项被打开的时候,如果static方法、private方法、构造函数等,其中的直接引用(第一层关系)到的类都在同一个dex文件中,那么该类就会被打上CLASS_ISPREVERIFIED
标志。
那么,我们要做的就是,阻止该类打上CLASS_ISPREVERIFIED
的标志。
注意下,是阻止引用者的类,也就是说,假设你的app里面有个类叫做LoadBugClass
,再其内部引用了BugClass
。发布过程中发现BugClass
有编写错误,那么想要发布一个新的BugClass
类,那么你就要阻止LoadBugClass
这个类打上CLASS_ISPREVERIFIED
的标志。
也就是说,你在生成apk之前,就需要阻止相关类打上CLASS_ISPREVERIFIED
的标志了。对于如何阻止,上面的文章说的很清楚,让LoadBugClass
在构造方法中,去引用别的dex文件,比如:hack.dex中的某个类即可。
ok,总结下:
其实就是两件事:1、动态改变BaseDexClassLoader对象间接引用的dexElements;2、在app打包的时候,阻止相关类去打上CLASS_ISPREVERIFIED
标志。
如果你没有看明白,没事,多看几遍,下面也会通过代码来说明。
三、阻止相关类打上CLASS_ISPREVERIFIED
标志
ok,接下来的代码基本上会通过https://github.com/dodola/HotFix所提供的代码来讲解。
那么,这里拿具体的类来说:
大致的流程是:在dx工具执行之前,将LoadBugClass.class
文件呢,进行修改,再其构造中添加System.out.println(dodola.hackdex.AntilazyLoad.class)
,然后继续打包的流程。注意:AntilazyLoad.class
这个类是独立在hack.dex中。
ok,这里大家可能会有2个疑问:
- 如何去修改一个类的class文件
- 如何在dx之前去进行疑问1的操作
(1)如何去修改一个类的class文件
这里我们使用javassist来操作,很简单:
ok,首先我们新建几个类:
package dodola.hackdex;
public class AntilazyLoad
{
}
package dodola.hotfix;
public class BugClass
{
public String bug()
{
return “bug class”;
}
}
package dodola.hotfix;
public class LoadBugClass
{
public String getBugString()
{
BugClass bugClass = new BugClass();
return bugClass.bug();
}
}
注意下,这里的package,我们要做的是,上述类正常编译以后产生class文件。比如:LoadBugClass.class,我们在LoadBugClass.class的构造中去添加一行:
System.out.println(dodola.hackdex.AntilazyLoad.class)
下面看下操作类:
package test;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
public class InjectHack
{
public static void main(String[] args)
{
try
{
String path = “/Users/zhy/develop_work/eclipse_android/imooc/JavassistTest/”;
ClassPool classes = ClassPool.getDefault();
classes.appendClassPath(path + “bin”);//项目的bin目录即可
CtClass c = classes.get(“dodola.hotfix.LoadBugClass”);
CtConstructor ctConstructor = c.getConstructors()[0];
ctConstructor
.insertAfter(“System.out.println(dodola.hackdex.AntilazyLoad.class);”);
c.writeFile(path + “/output”);
} catch (Exception e)
{
e.printStackTrace();
}
}
}
ok,点击run即可了,注意项目中导入javassist-*.jar的包。
首先拿到ClassPool对象,然后添加classpath,如果你有多个classpath可以多次调用。然后从classpath中找到LoadBugClass,拿到其构造方法,在其最后插入一行代码。ok,代码很好懂。
ok,我们反编译看下我们生成的class文件:
[图片上传失败…(image-39ebb7-1603688194099)]
ok,关于javassist,如果有兴趣的话,大家可以参考几篇文章学习下:
(2)如何在dx之前去进行(1)的操作
ok,这个就结合https://github.com/dodola/HotFix的源码来说了。
将其源码导入之后,打开app/build.gradle
apply plugin: ‘com.android.application’
task(‘processWithJavassist’) << {
String classPath = file(‘build/intermediates/classes/debug’)//项目编译class所在目录
dodola.patch.PatchClass.process(classPath, project(‘:hackdex’).buildDir
.absolutePath + ‘/intermediates/classes/debug’)//第二个参数是hackdex的class所在目录
}
android {
applicationVariants.all { variant ->
variant.dex.dependsOn << processWithJavassist //在执行dx命令之前将代码打入到class中
}
}
你会发现,在执行dx之前,会先执行processWithJavassist这个任务。这个任务的作用呢,就和我们上面的代码一致了。而且源码也给出了,大家自己看下。
ok,到这呢,你就可以点击run了。ok,有兴趣的话,你可以反编译去看看dodola.hotfix.LoadBugClass
这个类的构造方法中是否已经添加了改行代码。
关于反编译的用法,工具等,参考:http://blog.csdn.net/lmj623565791/article/details/23564065
ok,到此我们已经能够正常的安装apk并且运行了。但是目前还未涉及到打补丁的相关代码。
四、动态改变BaseDexClassLoader对象间接引用的dexElements
ok,这里就比较简单了,动态改变一个对象的某个引用我们反射就可以完成了。
不过这里需要注意的是,还记得我们之前说的,寻找class是遍历dexElements;然后我们的AntilazyLoad.class
实际上并不包含在apk的classes.dex中,并且根据上面描述的需要,我们需要将AntilazyLoad.class
这个类打成独立的hack_dex.jar,注意不是普通的jar,必须经过dx工具进行转化。
具体做法:
jar cvf hack.jar dodola/hackdex/*
dx --dex --output hack_dex.jar hack.jar 1212
如果,你没有办法把那一个class文件搞成jar,去百度一下…
ok,现在有了hack_dex.jar,这个是干嘛的呢?
应该还记得,我们的app中部门类引用了AntilazyLoad.class
,那么我们必须在应用启动的时候,将这个hack_dex.jar插入到dexElements,否则肯定会出事故的。
那么,Application的onCreate方法里面就很适合做这件事情,我们把hack_dex.jar放到assets目录。
下面看hotfix的源码:
/*
- Copyright © 2015 Baidu, Inc. All Rights Reserved.
*/
package dodola.hotfix;
import android.app.Application;
import android.content.Context;
import java.io.File;
import dodola.hotfixlib.HotFix;
/**
- Created by sunpengfei on 15/11/4.
*/
public class HotfixApplication extends Application
{
@Override
public void onCreate()
{
super.onCreate();
File dexPath = new File(getDir(“dex”, Context.MODE_PRIVATE), “hackdex_dex.jar”);
Utils.prepareDex(this.getApplicationContext(), dexPath, “hackdex_dex.jar”);
HotFix.patch(this, dexPath.getAbsolutePath(), “dodola.hackdex.AntilazyLoad”);
try
{
this.getClassLoader().loadClass(“dodola.hackdex.AntilazyLoad”);
} catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
}
ok,在app的私有目录创建一个文件,然后调用Utils.prepareDex将assets中的hackdex_dex.jar写入该文件。
接下来HotFix.patch就是去反射去修改dexElements了。我们深入看下源码:
/*
- Copyright © 2015 Baidu, Inc. All Rights Reserved.
*/
package dodola.hotfix;
/**
- Created by sunpengfei on 15/11/4.
*/
public class Utils {
private static final int BUF_SIZE = 2048;
public static boolean prepareDex(Context context, File dexInternalStoragePath, String dex_file) {
BufferedInputStream bis = null;
OutputStream dexWriter = null;
bis = new BufferedInputStream(context.getAssets().open(dex_file));
dexWriter = new BufferedOutputStream(new FileOutputStream(dexInternalStoragePath));
byte[] buf = new byte[BUF_SIZE];
int len;
while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) {
dexWriter.write(buf, 0, len);
}
dexWriter.close();
bis.close();
return true;
}
ok,其实就是文件的一个读写,将assets目录的文件,写到app的私有目录中的文件。
下面主要看patch方法
/*
- Copyright © 2015 Baidu, Inc. All Rights Reserved.
*/
package dodola.hotfixlib;
import android.annotation.TargetApi;
import android.content.Context;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
/* compiled from: ProGuard */
public final class HotFix
{
public static void patch(Context context, String patchDexFile, String patchClassName)
{
if (patchDexFile != null && new File(patchDexFile).exists())
{
try
{
if (hasLexClassLoader())
{
injectInAliyunOs(context, patchDexFile, patchClassName);
} else if (hasDexClassLoader())
{
injectAboveEqualApiLevel14(context, patchDexFile, patchClassName);
} else
{
injectBelowApiLevel14(context, patchDexFile, patchClassName);
}
} catch (Throwable th)
{
}
}
}
}
这里很据系统中ClassLoader的类型做了下判断,原理都是反射,我们看其中一个分支hasDexClassLoader()
;
private static boolean hasDexClassLoader()
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
尾声
一转眼时间真的过的飞快。我们各奔东西,也各自踏上了自己的旅途,但是即使多年不见,也因为这份情谊我们依旧如从前那般“亲密”。不忘初心方得始终。加油吧,程序员们,在我看来35岁,40岁从来不是危机,只要永远不要忘记自己为何踏上征程!
最后需要同款资料的,可以 **私信我点击【学习】**我愿意分享给你!
为了让更多在学习中或者最近要准备面试的朋友们看到这篇文章,希望你们能多多评论,点赞+转发!
Android)**
[外链图片转存中…(img-P0vFf4T2-1710854440226)]
尾声
一转眼时间真的过的飞快。我们各奔东西,也各自踏上了自己的旅途,但是即使多年不见,也因为这份情谊我们依旧如从前那般“亲密”。不忘初心方得始终。加油吧,程序员们,在我看来35岁,40岁从来不是危机,只要永远不要忘记自己为何踏上征程!
最后需要同款资料的,可以 **私信我点击【学习】**我愿意分享给你!
为了让更多在学习中或者最近要准备面试的朋友们看到这篇文章,希望你们能多多评论,点赞+转发!
再次感谢所有给我提供过题目的朋友们,感谢一路有你!