题目出自鸿神玩安卓svip免费交流群html
第一周:尝试修改java字节码java
1.选择javassist或者asm尝试修改一个java class,作任意修改便可[达标]。
2.尝试给一个java class方法中的方法添加耗时。
3.尝试在Android项目编译阶段给java class方法添加耗时检测。android
以前在项目开发中为了实现消息推送的各个平台版本sdk(小米,华为,OPPO,vivo,极光)。在写这个多平台推送的sdk过程当中,发现小米手机启动时,小米推送和极光推送的服务都同时启动了。致使后台发起的推送收到了两次(后天是全平台推送的)。原本只要手机端只要启动一个推送服务,结果应该只会收到一个推送。当时猜想多是注册了某个广播接收者而后在某些时候启动了极光服务,如今从新回顾经过Android Studio的Analyze APK
(build->Analyze APK)时,极光服务是经过provider
启动的,会有一些sdk会在provider
中初始化,见你的Android库是否还在Application中初始化?。git
<provider android:name="cn.jpush.android.service.DownloadProvider" android:exported="true" android:authorities="com.wantu.kouzidashen.DownloadProvider" />
复制代码
public class DownloadProvider extends ContentProvider {
...
private void init() {
try {
if (a.d(this.getContext().getApplicationContext())) {
JCoreInterface.register(this.getContext());
}
} catch (Throwable var1) {
}
}
}
public class JCoreInterface {
...
public static void register(Context var0) {
Bundle var1 = new Bundle();
i.a().b(var0, "intent.INIT", var1);
}
}
public final class i {
public final void b(Context var1, String var2, Bundle var3) {
try {
var1 = cn.jiguang.d.a.a(var1);
if (this.a(var1)) {
JCoreInterface.execute("SDK_MAIN", new j(this, var1, var2, var3), new int[0]);
}
} catch (Throwable var4) {
cn.jiguang.e.c.c("JServiceCommandHelper", "onAction failed", var4);
}
}
}
复制代码
在平时启动极光服务经过JPushInterface.init()
方法最终也会调用JCoreInterface.execute
。所以为了不在小米/华为等自己具备推送平台的手机在启动时启动了极光推送,须要设置一个flag
标志控制execute
方法的执行:github
public class JCoreInterface{
public static void execute{
if(flag)return;//修改的代码
...
}
}
复制代码
在开始想经过JD-GUI
来修改代码,而后编译成新的jar包。可是发现太难了,相关的Context
环境没有,并且极光的jar包是混淆过的,JD-GUI
反编译的最终效果不必定每一个都正确,会有一些文件不识别。
事实上咱们想要的效果只是修改个别文件,而后覆盖相应的目录便可,这样改动最小。最终经过查询,javassist(Java Programming Assistant)进入个人视野。
javassist
是一个java字节码编辑工具,能够很简单的修改class,操做方式优势相似于反射接口调用。web
首先准备JD-GUI
,idea
,而后下载javassist,在Android SDK
目录下的platforms/android-28
下找出android.jar
,而后下载极光的jar包, 咱们用idea
新建一个java
项目,而后新建libs
目录,而后加入javassist.jar
。右键Add as Library
加入到库中。在src中新建一个Test
类, 首先在JCoreInterface
(在jpush-android-3.2.0.jar
中)中加入JPUSH_IS_INIT
静态变量。编程
public class Test {
public static void main(String[] args) {
ClassPool pool = ClassPool.getDefault();
try {
pool.insertClassPath("/xxx/JavassistTest/libs/jcore-android-1.2.7.jar");
pool.insertClassPath("/xxx/JavassistTest/libs/jpush-android-3.2.0.jar");
pool.insertClassPath("/xxx/JavassistTest/libs/android.jar");
CtClass c = pool.get("cn.jpush.android.api.JPushInterface");//找到JPushInterface类
CtField bField = new CtField(CtClass.booleanType,"JPUSH_IS_INIT",c2);//添加JPUSH_IS_INIT静态变量
bField.setModifiers(Modifier.PUBLIC|Modifier.STATIC);
c.addField(bField);
CtMethod initMethod = c.getDeclaredMethod("init");//在init方法最前面插入代码 JPUSH_IS_INIT = true;
initMethod.insertBefore("JPUSH_IS_INIT = true;");
CtMethod stopMethod = c.getDeclaredMethod("stopPush");////stopPush方法中 JPUSH_IS_INIT = true;
stopMethod.insertBefore("JPUSH_IS_INIT = false;");
c.writeFile("jpush-android"); //输出目录jpush-android
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
在当前工程jpush-android
目录下,咱们找到了cn/jpush/android/api/JPushInterface.class
,idea打开反编译以下:api
public class JPushInterface {
...
public static boolean JPUSH_IS_INIT;
...
public static void init(Context var0) {
JPUSH_IS_INIT = true;
...
}
...
public static void stopPush(Context var0) {
JPUSH_IS_INIT = false;
g.a("JPushInterface", "action:stopPush");
...
}
复制代码
至此JPushInterface
已经加入了JPUSH_IS_INIT
标志,而且在init
和stopPush
中进行修改。接着须要修改jcore-android-1.2.7.jar
中JCoreInterface.execute
方法。在此以前,须要将当前的JPushInterface.class
覆盖到jpush-android-3.2.0.jar
中。拷贝一份命名为jpush-android-3.2.0-fix.jar
,经过360压缩
软件打开,将修改JPushInterface.class
文件覆盖到对应目录便可。接着就能够修改JCoreInterface
了,代码以下:app
ClassPool pool = ClassPool.getDefault();
try {
pool.insertClassPath("/xxx/JavassistTest/libs/jcore-android-1.2.7.jar");
pool.insertClassPath("/xxx/JavassistTest/libs/jpush-android-3.2.0-fix.jar");
pool.insertClassPath("/xxx/JavassistTest/libs/android.jar");
CtClass c = pool.get("cn.jiguang.api.JCoreInterface");
CtMethod method = c.getDeclaredMethod("execute");
method.insertBefore("if(!cn.jpush.android.api.JPushInterface.JPUSH_IS_INIT)return;");//JPUSH_IS_INIT为false,直接return返回
c.writeFile("jpush-android");
} catch (Exception e) {
e.printStackTrace();
}
复制代码
最后获得修改后的JCoreInterface
,反编译以下:框架
package cn.jiguang.api;
...
public class JCoreInterface {
public static void execute(String var0, Runnable var1, int... var2) {
if (JPushInterface.JPUSH_IS_INIT) {
cn.jiguang.d.h.i.a(var0, var1);
}
}
}
复制代码
稍微与修改时候的代码有所不一样,可是总体的逻辑是正确的。而后一样经过压缩软件覆盖修改,咱们就实现了可控制启动的极光推送jar包。
ASM是一款字节码操做与分析的开源框架,能够经过二进制形式(内存)修改已有class或者动态生成class。它提供了许多api用于字节码转换构建与分析。较于javassist
,ASM
相对复杂,门槛较高。ASM操做基于指令级别,提供了多种修改和分析API,小而快速,强大。
因为ASM操做字节码是基于指令的,所以要对jvm
要有必定了解,推荐你们阅读《深刻理解Java虚拟机》和《本身动手写Java虚拟机》,而《本身动手写Java虚拟机》
实践性强,你们能够经过go语言编程的形式学习Java虚拟机。
ASM
api主要有如下关键类:
ClassReader: 用于解析class文件,经过accept
接收ClassVisitor
对象访问具体的字段,方法等.
ClassVisitor:class访问者.
ClassWriter: 继承自ClassVisitor
,用于修改或生成class,一般配合ClassReader
和ClassVisitor
修改class.
这里在asm4-guide
第63页经过LocalVariablesSorter
为方法添加耗时检测
public class MethodLogAdapter extends ClassVisitor {
public MethodLogAdapter(int api) {
super(api);
}
private String owner;
private boolean isInterface;
public boolean changed; //是否修改过
public MethodLogAdapter(ClassVisitor cv) {
super(ASM4, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
cv.visit(version, access, name, signature, superName, interfaces);
owner = name;
isInterface = (access & ACC_INTERFACE) != 0;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
exceptions);
if (!isInterface && mv != null && !name.equals("<init>")) {
mv = new MethodLogAdapter.LogMethodAdapter(access, name, desc, mv);
}
return mv;
}
class LogMethodAdapter extends LocalVariablesSorter {
private int time;
private String name;
private boolean hasMethodLog;//是否具备MethodLog注解
public LogMethodAdapter(int access, String name, String desc, MethodVisitor mv) {
super(ASM4, access, desc, mv);
this.name = name;
}
@Override
public void visitCode() {
super.visitCode();
if (hasMethodLog) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",
"nanoTime", "()J");
time = newLocal(Type.LONG_TYPE);//声明临时变量time
mv.visitVarInsn(LSTORE, time);//将返回的时间戳保存到临时变量
}
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
hasMethodLog = "Lannotations/MethodLog;".equals(descriptor);
if (!changed && hasMethodLog) changed = true;
return super.visitAnnotation(descriptor, visible);
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
if (hasMethodLog) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",
"nanoTime", "()J");
mv.visitVarInsn(LLOAD, time);//加载time临时变量
mv.visitInsn(LSUB);//与当前时间戳相减
mv.visitVarInsn(LSTORE, 3);
Label l3 = new Label();
mv.visitLabel(l3);
//如下是将方法耗时打印出来 Log.i("当前类名","方法名:"+time)
mv.visitLdcInsn(owner);
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn(name + ":");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(LLOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)V", false);
}
}
super.visitInsn(opcode);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack + 4, maxLocals);
}
}
}
复制代码
而后配合ClassReader
,ClassWriter
修改class,给TestActivity
添加方法耗时检测
public class AsmTest {
public static void main(String[] args) {
try {
changeTest();
} catch (Exception e) {
e.printStackTrace();
}
}
//class文件信息读取
private static void changeTest() throws Exception {
String classPath = "out/production/ClassEditTest/test/TestActivity.class";
ClassReader reader = new ClassReader(new FileInputStream(new File(classPath)));
ClassWriter cw = new ClassWriter(reader,ClassWriter.COMPUTE_MAXS);
MethodLogAdapter adapter = new MethodLogAdapter(cw);
reader.accept(adapter,ClassReader.EXPAND_FRAMES);
System.out.println(adapter.changed);
byte[] bytes = cw.toByteArray();
FileOutputStream fos = new FileOutputStream(new File("test.class"));
fos.write(bytes);
}
}
复制代码
对比原先和修改后的代码以下
public class TestActivity {//修改前
@MethodLog
public void test() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void test2() {
Log.i("test","test123");
}
}
复制代码
public class TestActivity {//修改后
public TestActivity() {
}
@MethodLog
public void test() {
long var1 = System.nanoTime();
try {
Thread.sleep(100L);
} catch (InterruptedException var5) {
var5.printStackTrace();
}
long var3 = System.nanoTime() - var1;
Log.i("test/TestActivity", "test:" + var3);
}
public void test2() {
this.test();
}
}
复制代码
以前经过Transform实现了简易版路由框架,不过是经过javassist
实现的,虽然实现更简单,可是不如ASM
操做快速,因此本次经过ASM
实现。
一样的建立一个名称为buildSrc
(注意大小写)的Android Library
,这样咱们的插件直接可使用了,具体如何实现插件能够参照基于Transform实现更高效的组件化路由框架的配置方式。
添加MethodLogTransform
处理方法耗时
class MethodLogTransform extends Transform {
@Override
String getName() {
return "MethodLog"
}
...
@Override
void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
for (TransformInput input : inputs) {
for (DirectoryInput dirInput : input.directoryInputs) {//目录中的class文件
readClassWithPath(dirInput.file)
File dest = outputProvider.getContentLocation(dirInput.name,
dirInput.contentTypes,
dirInput.scopes,
Format.DIRECTORY)
FileUtils.copyDirectory(dirInput.file, dest)
}
for (JarInput jarInput : input.jarInputs) {//jar(第三方库,module)
if (jarInput.scopes.contains(QualifiedContent.Scope.SUB_PROJECTS)) {//module library
//todo 为jar包添加耗时
}
copyFile(jarInput, outputProvider)
}
}
}
//
void readClassWithPath(File dir) {//从编译class文件目录找到注解
def root = dir.absolutePath
dir.eachFileRecurse { File file ->
def filePath = file.absolutePath
if (!filePath.endsWith(".class")) return
def className = getClassName(root, filePath)
if (isSystemClass(className)) return
hookClass(filePath, className)
}
}
void hookClass(String filePath, String className) {
ClassReader reader = new ClassReader(new FileInputStream(new File(filePath)))
ClassWriter cw = new ClassWriter(reader,ClassWriter.COMPUTE_MAXS)
MethodLogAdapter adapter = new MethodLogAdapter(cw)
reader.accept(adapter,ClassReader.EXPAND_FRAMES)
System.out.println(adapter.changed)
if(adapter.changed){
byte[] bytes = cw.toByteArray()
FileOutputStream fos = new FileOutputStream(new File(filePath))
fos.write(bytes)
}
}
...
}
复制代码