前言:因为最近工作需要用到AOP技术,如是在网上搜索已经有的AOP框架,找到了lancet、booster和ByteX。
其中lancet比较符合我的要求,但是使用中发现了2个问题所以放弃了。
1、lancet是基于ASM5开发的,ASM5版本过低
ASM Release | Release Date | Java Support |
---|---|---|
2.0 | 2005-05-17 | Java 5 language support |
3.2 | 2009-06-11 | support for the new invokedynamic code. |
4.0 | 2011-10-29 | Java 7 language support |
5.0 | 2014-03-16 | Java 8 language support |
6.0 | 2017-09-23 | Java 9 language support |
6.1 | 2018-03-11 | Java 10 language support |
7.0 | 2018-10-27 | Java 11 language support |
7.1 | 2019-03-03 | Java 13 language support |
8.0 | 2020-03-28 | Java 14 language support |
9.0 | 2020-09-22 | Java 16 language support |
9.1 | 2021-02-06 | JDK 17 support |
9.2 | 2021-06-26 | JDK 18 support |
2、lancet不支持最新的AGP7.0+
Android Gradle Plugin | Gradle |
---|---|
3.0.0+ | 4.1+ |
3.1.0+ | 4.4+ |
3.2.0 - 3.2.1 | 4.6+ |
3.3.0 - 3.3.3 | 4.10.1+ |
3.4.0 - 3.4.3 | 5.1.1+ |
3.5.0 - 3.5.4 | 5.4.1+ |
3.6.0 - 3.6.4 | 5.6.4+ |
4.0.0+ | 6.1.1+ |
4.1.0+ | 6.5+ |
4.2.0+ | 6.7.1+ |
7.0 | 7.0+ |
7.1 | 7.1+ |
7.2 | 7.3.3+ |
如果为了要使用lancet而降低自己项目的gradle版本比较麻烦。
byteX和booster并不是一个严格意义上的AOP框架,它们提供了AOP能力允许用户自己去扩展。所以我就以booster为基础编写一个新的AOP框架tenseiga,它基于ASM7并且支持到AGP7.0+。
目前支持的内容比较简单,后面会慢慢完善,下面简单说说它的实现。
Replace:
public class OriginJava { public int replace(int a, int b) { Log.e("siy", "OriginJava-replace-"); return a + b; } }
现在我希望替换掉replace方法中的实现,像如下这样:
public class HookJava { public int hookReplace(int a, int b) { Log.e("siy", "HookJava-hookReplace-"); //获取实例方法所在的对象 OriginJava originJava = (OriginJava) Self.get(); originJava.showToast(); //我们可以在这儿修改参数 int orgResult = (int) Invoker.invoke(a, b); return orgResult - b; } }
应该如何实现?
先说思路然后再说如何实现,思路如下:
1、把原来的replace方法原样复制一份:replace$___backup___
2、把hookReplace复制一份到OriginJava,但是方法变成静态方法:com_siy_tenseiga_test_HookJava_hookReplace
3、在原来的replace方法中调用com_siy_tenseiga_test_HookJava_hookReplace方法。
PS:既然要替换掉replace的实现为什么要把replace原样复制一份呢?这个先放这儿,后面会用到。
实现方式(实现主要采用ASM的TreeApi):
1、把原来的replace方法原样复制一份:replace$___backup___
/** * 创建一个方法 */ fun createMethod( access: Int, name: String, desc: String, exceptions: List<String>?, action: ((InsnList) -> Unit) ): MethodNode { val methodNode = MethodNode(access, name, desc, null, exceptions?.toTypedArray()) val insnList = InsnList() //加载参数 val params = Type.getArgumentTypes(desc) var index = 0; if (!TypeUtil.isStatic(access)) { index++ insnList.add(VarInsnNode(Opcodes.ALOAD, 0)) } for (t in params) { insnList.add(VarInsnNode(t.getOpcode(Opcodes.ILOAD), index)) index += t.size } //操作 action(insnList) //返回值 val ret = Type.getReturnType(desc) insnList.add(InsnNode(ret.getOpcode(Opcodes.IRETURN))) methodNode.instructions.add(insnList) return methodNode }private fun createBackupForTargetMethod(targetMethod: MethodNode): MethodNode { val newAccess: Int = targetMethod.access and (Opcodes.ACC_PROTECTED or Opcodes.ACC_PUBLIC).inv() or Opcodes.ACC_PRIVATE val newName = targetMethod.name + "${'$'}___backup___" return createMethod(newAccess, newName, targetMethod.desc, targetMethod.exceptions) { it.add(targetMethod.instructions) }.also { klass?.methods?.add(it) } }
2、把hookReplace复制一份到OriginJava,但是方法变成静态方法:com_siy_tenseiga_test_HookJava_hookReplace
方法和上面一样
3、在原来的replace方法中调用com_siy_tenseiga_test_HookJava_hookReplace方法。
/** * 替换方法体 * @param method 需要替换的方法 * * @param action 新的方法提 */ fun replaceMethodBody(method: MethodNode, action: ((InsnList) -> Unit)) { method.instructions.clear() val insnList = InsnList(); //加载参数 val params = Type.getArgumentTypes(method.desc) var index = 0; if (!TypeUtil.isStatic(method.access)) { index++ insnList.add(VarInsnNode(Opcodes.ALOAD, 0)) } for (t in params) { insnList.add(VarInsnNode(t.getOpcode(Opcodes.ILOAD), index)) index += t.size } //操作 action(insnList) //返回值 val ret = Type.getReturnType(method.desc) insnList.add(InsnNode(ret.getOpcode(Opcodes.IRETURN))) method.instructions.add(insnList) }replaceMethodBody(method) { it.add( MethodInsnNode( TypeUtil.getOpcodeByAccess(hookMethod.access), klass?.name, hookMethod.name, hookMethod.desc ) ) }
Proxy:
@NonNull public final String getString(@StringRes int resId) { return getResources().getString(resId); }
现在我希望“替换”上面的系统方法Context.getString(int)实现,像如下这样:
public String hookProxySys(int resId) { Log.e("siy", "HookJava-hookProxySys-"); //获取实例方法所在的对象 Context context = (Context) Self.get(); Log.e("siy", context.getCacheDir().getAbsolutePath()); return (String) Invoker.invoke(R.string.next); //next }
但是注意Context.getString(int)是系统方法,我们根本无法修改到它的类文件,所以我们只能代理它的调用,把所有调用Context.getString(int)的地方都修改掉。
如:
findViewById(R.id.proxySys).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e("siy", getString(R.string.app_name)); } });
我们想把getString(int) hook掉,需要如下几步:
1、把hookProxySys复制一份变成静态方法:com_siy_tenseiga_test_HookJava_hookProxySys
2、代理方法调用,将Context.getString()的调用修改为对com_siy_tenseiga_test_HookJava_hookProxySys方法的调用
具体操作和上面replace相似,这里提一下修改方法调用的实现:
MethodInsnNode.run { owner = clazz.name name = hookMethod.name desc = hookMethod.desc opcode = Opcodes.INVOKESTATIC itf = false }
项目地址:戳这里