Skip to content

@AndroidAopMatchClassMethod

FlyJingFish edited this page Dec 11, 2023 · 79 revisions

简述

@AndroidAopMatchClassMethod 是做匹配某类及其对应方法的切面的

⚠️注意:自定义的匹配类方法切面(也就是被 @AndroidAopMatchClassMethod 注解的代码)如果是 Kotlin 代码请用 android-aop-ksp 那个库

这个方式不同于 @AndroidAopPointCut 的地方就是不用在代码内添加任何代码,包括注解

@AndroidAopMatchClassMethod(
    targetClassName = 目标类名(包含包名),
    methodName = 方法名数组,
    type = 匹配类型,非必须,默认 EXTENDS
    excludeClasses = 排除继承关系中的一些类的数组(type 不是 SELF 才有效),非必须
)

type 有四种类型(不设置默认 EXTENDS):

  • SELF 表示匹配的是 targetClassName 所设置类的自身
  • EXTENDS 表示匹配的是所有继承于 targetClassName 所设置的类
  • DIRECT_EXTENDS 表示匹配的是 直接继承于 targetClassName 所设置的类
  • LEAF_EXTENDS 表示匹配的是 末端继承(就是没有子类了) targetClassName 所设置的类

简单来说,LEAF_EXTENDSDIRECT_EXTENDS是两个极端,前者关注的是继承关系中最后一个节点,后者关注的是继承关系中第一个节点。

excludeClasses 是排除掉继承关系中的一些类,可以设置多个,且 type 不是 SELF 才有效

创建切面处理类

切面处理类需要实现 MatchClassMethod 接口,在 invoke 中处理切面逻辑

interface MatchClassMethod {
    fun invoke(joinPoint: ProceedJoinPoint, methodName:String): Any?
}

举例

例一

想要监测所有继承自 AppCompatActivity 类的所有 startActivity 跳转

@AndroidAopMatchClassMethod(
   targetClassName = "androidx.appcompat.app.AppCompatActivity",
   methodName = {"startActivity"},
   type = MatchType.EXTENDS
)
public class MatchActivityMethod implements MatchClassMethod {
    @Nullable
    @Override
    public Object invoke(@NonNull ProceedJoinPoint joinPoint, @NonNull String methodName) {
        // 在此写你的逻辑 
        return joinPoint.proceed();
    }
}

⚠️注意:对于匹配子类方法的,如果子类没有重写匹配的方法,是无效的,另外对同一个类的同一个方法不要做多次匹配,否则只有一个会生效

例二

假如想 Hook 所有的 android.view.View.OnClickListener 的 onClick,说白了就是想全局监测所有的设置 OnClickListener 的点击事件,代码如下:

@AndroidAopMatchClassMethod(
    targetClassName = "android.view.View.OnClickListener",
    methodName = ["onClick"],
    type = MatchType.EXTENDS //type 一定是 EXTENDS 因为你想 hook 所有继承了 OnClickListener 的类
)
class MatchOnClick : MatchClassMethod {
//    @SingleClick(5000) //联合 @SingleClick ,给所有点击增加防多点,6不6
    override fun invoke(joinPoint: ProceedJoinPoint, methodName: String): Any? {
        Log.e("MatchOnClick", "=====invoke=====$methodName")
        return joinPoint.proceed()
    }
}

这块提示下,对于使用了 lambda 点击监听的;

ProceedJoinPoint 的 target 不是 android.view.View.OnClickListener

  • 对于Java target 是 设置lambda表达式的那个类的对象
  • 对于Kotlin target 是 null

invoke 回调的 methodName 也不是 onClick 而是编译时自动生成的方法名,类似于这样 onCreate$lambda$14 里边包含了 lambda 关键字

对于 onClick(view:View) 的 view

  • 如果是 Kotlin 的代码 ProceedJoinPoint.args[1]
  • 如果是 Java 的代码 ProceedJoinPoint.args[0]

这块不在继续赘述了,自己用一下就知道了;

总结下:其实对于所有的 lambda 的 ProceedJoinPoint.args

  • 如果是 Kotlin 第一个参数是设置lambda表达式的那个类的对象,后边的参数就是 hook 方法的所有参数
  • 如果是 Java 从第一个参数开始就是 hook 方法的所有参数

例三

目标类有多个重名方法,只想匹配某一个方法(下文有提到精准匹配规则)

package com.flyjingfish.test_lib;

public class TestMatch {
    public void test(int value1){

    }

    public String test(int value1,String value2){
        return value1+value2;
    }
}

例如有 TestMatch 这个类 有两个名为 test 的方法,你只想匹配 test(int value1,String value2) 这个方法,那么写法如下:

package com.flyjingfish.test_lib.mycut;

@AndroidAopMatchClassMethod(
        targetClassName = "com.flyjingfish.test_lib.TestMatch",
        methodName = ["java.lang.String test(int,java.lang.String)"],
        type = MatchType.SELF
)
class MatchTestMatchMethod : MatchClassMethod {
  override fun invoke(joinPoint: ProceedJoinPoint, methodName: String): Any? {
    Log.e("MatchTestMatchMethod","======"+methodName+",getParameterTypes="+joinPoint.getTargetMethod().getParameterTypes().length);
    // 在此写你的逻辑 
    //不想执行原来方法逻辑,👇就不调用下边这句
    return joinPoint.proceed()
  }
}

例四

继承关系层次较多时不想每层都加入切面

@AndroidAopMatchClassMethod(
    targetClassName = "android.view.View.OnClickListener",
    methodName = ["onClick"],
    type = MatchType.EXTENDS //type 一定是 EXTENDS 因为你想 hook 所有继承了 OnClickListener 的类
)
class MatchOnClick : MatchClassMethod {
//    @SingleClick(5000) //联合 @SingleClick ,给所有点击增加防多点,6不6
    override fun invoke(joinPoint: ProceedJoinPoint, methodName: String): Any? {
        Log.e("MatchOnClick", "=====invoke=====$methodName")
        return joinPoint.proceed()
    }
}
public abstract class MyOnClickListener implements View.OnClickListener {
    @Override
    public void onClick(View v) {
        ...
        //这块有必要的逻辑代码
    }
}
binding.btnSingleClick.setOnClickListener(object :MyOnClickListener(){
    override fun onClick(v: View?) {
        super.onClick(v)//尤其是这句调用父类 onClick 想要保留执行父类方法的逻辑
        onSingleClick()
    }
})

这么写,会导致上边的 MyOnClickListener onClick 也加入切面,如此一来相当于一个点击有回调了两次切面处理类的 invoke ,这可能不是我们想要的,所以可以这么改

@AndroidAopMatchClassMethod(
    targetClassName = "android.view.View.OnClickListener",
    methodName = ["onClick"],
    type = MatchType.EXTENDS,
    excludeClasses = ["com.flyjingfish.androidaop.test.MyOnClickListener"]//加上这个可以排除掉一些类
)
class MatchOnClick : MatchClassMethod {
    override fun invoke(joinPoint: ProceedJoinPoint, methodName: String): Any? {
        Log.e("MatchOnClick", "=====invoke=====$methodName")
        return joinPoint.proceed()
    }
}

或者设置 type 为 LEAF_EXTENDS 直接过滤掉中间类(这让我想起一句广告:没有中间商赚差价)

例五

切入点是伴生对象怎么办?

假如有这样一个代码

package com.flyjingfish.androidaop

class ThirdActivity : BaseActivity() {
    companion object{
        fun start(){
            ...
        }
    }
}

伴生对象修饰符 companion 首字母大写,如下即可

@AndroidAopMatchClassMethod(
    targetClassName = "com.flyjingfish.androidaop.ThirdActivity.Companion",
    methodName = ["start"],
    type = MatchType.SELF
)
class MatchCompanionStart : MatchClassMethod {
    override fun invoke(joinPoint: ProceedJoinPoint, methodName: String): Any? {
        Log.e("MatchCompanionStart", "======$methodName")
        return joinPoint.proceed()
    }
}

匹配规则

可以看到上述例子中有的设置的方法名就只写了方法名,有的也写上了返回值类型和参数类型,下边介绍下

模糊匹配

methodName 方法名不写返回类型和参数类型就是模糊匹配,模糊匹配会将目标类中所有同名方法都加入切点

精准匹配

methodName 方法名写上返回类型、参数类型,这样就可以精准到一个方法加入切点

匹配的写法公式: 返回值类型 方法名(参数类型,参数类型)

  • 返回值类型 可以不用写
  • 方法名 必须写
  • 参数类型 可以不用写,写的话用 () 包裹起来,多个参数类型用 , 隔开,没有参数就只写 ()
  • 返回值类型 和 方法名 之间用空格隔开
  • 返回值类型 和 参数类型 不写的话就是不验证
  • 返回值类型 和 参数类型 都要用 Java 的类型表示,除了 8 种基本类型之外,其他引用类型都是 包名.类名

下边给出类型表示不同的 Kotlin 对 Java 对照表,如果是 Kotlin 代码请对号入座

(有发现不全的可以跟我反馈)

Kotlin 类型 Java 类型
Int int
Short short
Byte byte
Char char
Long long
Float float
Double double
Boolean boolean
Int? java.lang.Integer
Short? java.lang.Short
Byte? java.lang.Byte
Char? java.lang.Character
Long? java.lang.Long
Float? java.lang.Float
Double? java.lang.Double
Boolean? java.lang.Boolean
String java.lang.String
Unit void
Unit? java.lang.Void
Any java.lang.Object

其他不在上表中的数据类型,都属于引用类型,写法就是 包名.类名