如何在android中将方法调用注入到另一个方法中

我有一个class,

  • 一个叫something的领域,
  • 一个名为setSomething的setter方法,
  • 一个叫做onChange的方法,每次改变时都应该调用它。

我希望能够自由地添加更多的领域,并为他们所有人都有相同的行为。

我不想手动调用onChange因为,

  1. 很多样板,
  2. 代码将被写入Kotlin,所以我不想编写setter函数。

我已经能够想到的理想解决方案是在编译时每个setter方法的return之前以某种方式注入onChange调用。

我已经看过注释处理,但是显然类并不是真的在那个阶段编译的,所以我不得不重新生成整个类。 我不完全明白这一点。

另一个选项似乎是编写一个gradle插件,它将find相关的类并修改它们的字节码。

实际上,我已经开始将它作为一个纯Java项目(gradle插件是半完成的),并且已经能够find类并注入方法调用。 虽然似乎无法成功地将结果写入类文件。

这是我的(使用BCEL ):

 public class StateStoreInjector { public static void main(String[] args) { // Find all classes that extends StateStore Reflections reflections = new Reflections("tr.xip.statestore"); Set<Class> classes = reflections.getSubTypesOf(StateStore.class); for (Class c : classes) { try { JavaClass clazz = Repository.lookupClass(c.getName()); JavaClass superClazz = Repository.lookupClass(StateStore.class.getName()); if (Repository.instanceOf(clazz, superClazz)) { injectInClass(clazz, superClazz); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } private static void injectInClass(JavaClass clazz, JavaClass superClazz) { ClassGen classGen = new ClassGen(clazz); ConstantPoolGen cp = classGen.getConstantPool(); // Find the onChange method Method onChangeMethod = null; for (Method m : superClazz.getMethods()) { if (m.getName().equals("onChange")) { onChangeMethod = m; } } if (onChangeMethod == null) { throw new RuntimeException("onChange method not found"); } ClassGen superClassGen = new ClassGen(superClazz); ConstantPoolGen superCp = superClassGen.getConstantPool(); // Add onChange method ref to the class ConstantPool MethodGen onChangeMethodGen = new MethodGen(onChangeMethod, superClassGen.getClassName(), superCp); cp.addMethodref(onChangeMethodGen); // Loop through all methods to inject method invocations if applicable for (Method m : clazz.getMethods()) { // Skip methods with names shorter than 3 chars - we're looking for setters and setters would be min 4 chars if (m.getName().length() < 3) continue; // Check if the method actually starts with the keyword "set" boolean isSetMethod = m.getName().substring(0, 3).toUpperCase().equals("SET"); // Get method name without the "set" keyword String methodName = m.getName().substring(3, m.getName().length()); // Check that we actually have a field set by this setter - that this setter is "valid" boolean fieldWithSameNameExists = false; for (Field f : clazz.getFields()) { if (f.getName().toUpperCase().equals(methodName.toUpperCase())) { fieldWithSameNameExists = true; break; } } // Proceed with injection if criteria match Method newMethod = null; if (isSetMethod && fieldWithSameNameExists) { newMethod = injectInMethod(m, onChangeMethodGen, classGen, cp); } // Injection returned. Do we have a new/modified method? Yes? Update and write class. if (newMethod != null) { classGen.removeMethod(m); classGen.addMethod(newMethod); classGen.update(); try { String packageName = clazz.getPackageName().replace(".", "/"); String className = clazz.getClassName(); className = className.substring(className.lastIndexOf(".") + 1, className.length()); clazz.dump(packageName + "/" + className + "Edited.class"); } catch (IOException e) { e.printStackTrace(); } } } } private static Method injectInMethod(Method m, MethodGen onChangeMethodGen, ClassGen cg, ConstantPoolGen cp) { MethodGen methodGen = new MethodGen(m, cg.getClassName(), cp); InstructionList il = methodGen.getInstructionList(); println(il.toString() + "pre insert ^"); // Find the "return" instruction Instruction returnInstruction = null; for (Instruction i : il.getInstructions()) { if (i.getOpcode() == 177) returnInstruction = i; } // If found, insert onChange invocation instruction before the return instruction if (returnInstruction != null) { int index = cp.lookupMethodref(onChangeMethodGen); // Find the index of the onChange method in the CP il.insert(returnInstruction, new INVOKEVIRTUAL(index)); // Insert the new instruction println(il.toString() + "post insert ^"); il.setPositions(); // Fix positions println(il.toString() + "post set pos ^"); il.update(); methodGen.update(); return methodGen.getMethod(); } return null; } private static void println(String message) { System.out.println(message); } } 

输入Java类:

 public class DummyStateStore extends StateStore { private int id = 4321; public void setId(int id) { this.id = id; } public int getId() { return id; } } 

母店类:

 public class StateStore { public void onChange() { // notifies all subscribers } } 

输出(反编译)类文件:

 public class DummyStateStore extends StateStore { private int id = 4321; public DummyStateStore() { } public void setId(int id) { this.id = id; } public int getId() { return this.id; } } 

日志输出:

  0: aload_0[42](1) 1: iload_1[27](1) 2: putfield[181](3) 2 5: return[177](1) pre insert ^ 0: aload_0[42](1) 1: iload_1[27](1) 2: putfield[181](3) 2 -1: invokevirtual[182](3) 26 5: return[177](1) post insert ^ 0: aload_0[42](1) 1: iload_1[27](1) 2: putfield[181](3) 2 5: invokevirtual[182](3) 26 8: return[177](1) post set pos ^ 

(我通过调试代码检查了索引26,它 CP中的正确索引)

现在,问题是:

  1. 为什么不能在反编译的代码中看到调用,但它似乎被添加到说明列表中? 我错过了什么?
  2. 我将在哪里将导出修改后的类文件在一个Android版本中包含在最终的apk中?

您正在尝试使用reflection,但不应该使用Kotlin来创建更高阶的函数(将函数用作输入的函数)。

你可以做这样的事情:

 class ChangeableType(private var value: T, private val onChange: () -> Unit) { fun set(value: T) { this.value = value this.onChange.invoke() } } class MyRandomClass() { val something = ChangeableType(0, { System.print("Something new value: $value") }) val anotherThing = ChangeableType("String", { System.print("Another thing new value: $value") }) } class ConsumingClass { val myRandomClass = MyRandomClass() fun update() { myRandomClass.apply { something.set(1) anotherThing.set("Hello World") } } }