Android AOP 工具Tenseiga来啦

前言:因为最近工作需要用到AOP技术,如是在网上搜索已经有的AOP框架,找到了lancetboosterByteX

其中lancet比较符合我的要求,但是使用中发现了2个问题所以放弃了。

1、lancet是基于ASM5开发的,ASM5版本过低

ASM ReleaseRelease DateJava Support
2.02005-05-17Java 5 language support
3.22009-06-11support for the new invokedynamic code.
4.02011-10-29Java 7 language support
5.02014-03-16Java 8 language support
6.02017-09-23Java 9 language support
6.12018-03-11Java 10 language support
7.02018-10-27Java 11 language support
7.12019-03-03Java 13 language support
8.02020-03-28Java 14 language support
9.02020-09-22Java 16 language support
9.12021-02-06JDK 17 support
9.22021-06-26JDK 18 support

2、lancet不支持最新的AGP7.0+

Android Gradle PluginGradle
3.0.0+4.1+
3.1.0+4.4+
3.2.0 - 3.2.14.6+
3.3.0 - 3.3.34.10.1+
3.4.0 - 3.4.35.1.1+
3.5.0 - 3.5.45.4.1+
3.6.0 - 3.6.45.6.4+
4.0.0+6.1.1+
4.1.0+6.5+
4.2.0+6.7.1+
7.07.0+
7.17.1+
7.27.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
}

项目地址:戳这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值