@AndroidAopMatchClassMethod
Brief description¶
This aspect is used to match a class and its corresponding method. This aspect focuses on the execution of the method (Method Execution). Please note the difference between it and@AndroidAopReplaceClass.
@AndroidAopMatchClassMethod(targetClassName=targetclassname(includingpackagename),methodName=methodnamearray,type=matchtype,optional,defaultEXTENDSexcludeClasses=arrayofclassestoexcludeininheritancerelationship(validonlywhentypeisnotSELF),optionalexcludeWeaving=excludeweavingrangeincludeWeaving=includeweavingrange)When targetClassName is an inner class, do not use the
$character, but use.There are four types of type (default
EXTENDSis not set):SELFmeans that the match isitself of the class set by targetClassNameEXTENDSmeans that the match isall classes inherited from the class set by targetClassNameDIRECT_EXTENDSmeans that the match isdirectly inherited from the class set by targetClassNameLEAF_EXTENDSmeans that the match isTerminal inheritance (no subclasses) The class set by targetClassName
graph LRC(Class C) ---> |Class C inherits from Class B| B{Class B};B --->|Class B inherits from Class A| A[Class A];B --->|DIRECT_EXTENDS / EXTENDS| A;C ---->|LEAF_EXTENDS / EXTENDS| A;D(Class D) --->|Class D inherits from Class A| A;D --->|DIRECT_EXTENDS/ LEAF_EXTENDS / EXTENDS| A;Simply put,
LEAF_EXTENDSandDIRECT_EXTENDSare two extremes. The former focuses on the last node in the inheritance relationship, while the latter focuses on the first node in the inheritance relationship. Also note thatEXTENDSThis type of match has a wide range, and all inherited intermediate classes may also add aspect codesexcludeClasses
- If targetClassName is a class name, it means excluding some classes in the inheritance relationship. You can set multiple, and type is not SELF to be effective
- If targetClassName is a package name, it means excluding some matched classes. You can set multiple, and type is SELF to be effective
overrideMethod defaults to false
Set to true, if there is no matching method in the subclass (non-interface, can be an abstract class), the parent class method is overwritten
- targetClassName cannot contain *
- methodName cannot define [ "*" ]
- The overridden method cannot be private or final
Set to false, if there is no matching method in the subclass, no processing is done
excludeWeaving and includeWeaving are similar to exclude and include in thegetting started
Note
In addition, not all classes can be hooked in
typeType isSELFWhentargetClassNameis set, the class must be the code in the installation package.For example: if this class (such as Toast) is inandroid.jar, it will not work. If you have such a requirement, you should use@AndroidAopReplaceClasstypeWhen the type is notSELF, this aspect needs to have a matching method to work. If the subclass does not override the matching method, the subclass will not be matched. Use overrideMethod to ignore this restriction. But please note that the subclass must also be the code in the installation package- When you modify the configuration of this aspect, in most cases you should clean the project and continue development
Create an aspect processing class¶
The aspect processing class needs to implement the MatchClassMethod interface and handle the aspect logic in invoke
Note
If the point function is suspendClick here to view
Matching rules¶
You can see that in the following examples, some of the method names are set with only the method name, while others also have the return value type and parameter type. The following is an introduction
Fuzzy matching¶
- When targetClassName ends with
.*and has other characters, andtype = MatchType.SELF, it matches all classes under the package, including subpackagesas shown in Example 9 below
- When targetClassName ends with
- When methodName has only one method name
*, it matches all methods in the classas shown in Example 8 below
- When methodName has only one method name
- When methodName only writes the method name but does not write the return type and parameter type, it is also a fuzzy match, which will add all methods with the same name in the target class to the cut point
Note
For matching package names, I strongly recommend not doing this, as it intrudes too much code and significantly reduces the packaging speed. It is recommended to use it only for debugging and logging, and the original method should be released in full
Precise matching¶
Write the return type and parameter type on the methodName method name, so that you can accurately find a method to add a cut point (it will automatically degenerate to fuzzy matching when the precise matching is abnormal)
Matching formula:Return value type method name (parameter type, parameter type...)
- Return value type can be omitted
- Method name must be written
- Parameter type can be omitted. If written, wrap it with(). Multiple parameter types are separated by,. If there is no parameter, just write()
- Separate the return value type and method name with a space
- If the return value type and parameter type are not written, it means no verification
- Return value type and parameter type must be expressed in Java types. Except for the 8 basic types, other reference types arepackage name.class name
- If the function is modified with
suspend, then the return value type is written regardless of the typesuspend, parameter types should still be written according to the above points For generic information (such as collection List), the generic information must be erased
Different from targetClassName, if the method parameter and return value type are inner classes, they need to be replaced with
$instead of.
Note
AOP Code Generation Assistant, which can help you generate code with one click
Below is a comparison table of Kotlin and Java with different types. If it is Kotlin code, please check the corresponding code
(If you find any incomplete information, please give me feedback)
| Kotlin type | Java type |
|---|---|
| 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(Or do not write) | void |
| Unit? | kotlin.Unit |
| Nothing | java.lang.Void |
| Any | java.lang.Object |
Other data types not listed above are reference types, and are written aspackage name.class name
Note
vararg str : Stringin Kotlin is equivalent toString...in Java. In this matching, no matter what kind of code is used, it is represented byString[](String is used as an example here, and other types are the same)- For types with generics, do not write generics, for example,
java.lang.List<String> methodName(java.lang.List<String>)should be directly written asjava.lang.List methodName(java.lang.List)
Examples of various scenarios¶
Example 1¶
Want to monitor all startActivity jumps inherited from the AppCompatActivity class
@AndroidAopMatchClassMethod(targetClassName="androidx.appcompat.app.AppCompatActivity",methodName={"startActivity"},type=MatchType.EXTENDS)publicclassMatchActivityMethodimplementsMatchClassMethod{@Nullable@OverridepublicObjectinvoke(@NonNullProceedJoinPointjoinPoint,@NonNullStringmethodName){// Write your logic herereturnjoinPoint.proceed();}}Note: For matching subclass methods, if the subclass does not override the matching method, it is invalid. Use overrideMethod to ignore this limitation
Example 2¶
If you want to hook all android.view.View.OnClickListener onClick, to put it simply, is to globally monitor all click events set onClickListener, the code is as follows:
@AndroidAopMatchClassMethod(targetClassName="android.view.View.OnClickListener",methodName=["onClick"],type=MatchType.EXTENDS//type must be EXTENDS because you want to hook all classes that inherit OnClickListener)classMatchOnClick:MatchClassMethod{// @SingleClick(5000) //Combined with @SingleClick, add multi-click protection to all clicks, 6 or notoverridefuninvoke(joinPoint:ProceedJoinPoint,methodName:String):Any?{Log.e("MatchOnClick","=====invoke=====$methodName")returnjoinPoint.proceed()}}Here is a reminder for those who use lambda click monitoring;
ProceedJoinPoint The target is not android.view.View.OnClickListener - For Java, the target is the object of the class that sets the lambda expression - For Kotlin, the target is null
The methodName of the invoke callback is not onClick, but the method name automatically generated at compile time, similar to onCreate\(lambda\)14, which contains the lambda keyword
For the view of onClick(view:View) - If it is Kotlin code, ProceedJoinPoint.args[1] - If it is Java code, ProceedJoinPoint.args[0]
I will not go into details about this, you will know it after using it yourself;
To summarize: In fact, for all lambda's ProceedJoinPoint.args
- If it is Kotlin, the first parameter is the object of the class that sets the lambda expression, and the subsequent parameters are all the parameters of the hook method
- If it is Java, starting from the first parameter, it is all the parameters of the hook method
Example 3¶
The target class has multiple methods with the same name, and you only want to match one method (the exact matching rule is mentioned above)
packagecom.flyjingfish.test_lib;publicclassTestMatch{publicvoidtest(intvalue1){}publicStringtest(intvalue1,Stringvalue2){returnvalue1+value2;}}packagecom.flyjingfish.test_lib.mycut;@AndroidAopMatchClassMethod(targetClassName="com.flyjingfish.test_lib.TestMatch",methodName=["java.lang.String test(int,java.lang.String)"],type=MatchType.SELF)classMatchTestMatchMethod:MatchClassMethod{overridefuninvoke(joinPoint:ProceedJoinPoint,methodName:String):Any?{Log.e("MatchTestMatchMethod","======"+methodName+",getParameterTypes="+joinPoint.getTargetMethod().getParameterTypes().length);// Write your logic here// If you don't want to execute the original method logic, 👇 don't call the following sentencereturnjoinPoint.proceed()}}Example 4¶
When there are many levels of inheritance relationships, you don't want to add aspects to each level
@AndroidAopMatchClassMethod(targetClassName="android.view.View.OnClickListener",methodName=["onClick"],type=MatchType.EXTENDS// type must be EXTENDS because you want to hook all classes that inherit OnClickListener)classMatchOnClick:MatchClassMethod{// @SingleClick(5000) //Join @SingleClick to add anti-multiple clicks to all clicks, 6 or 6overridefuninvoke(joinPoint:ProceedJoinPoint,methodName:String):Any?{Log.e("MatchOnClick","=====invoke=====$methodName")returnjoinPoint.proceed()}}publicabstractclassMyOnClickListenerimplementsView.OnClickListener{@OverridepublicvoidonClick(Viewv){...//This is the necessary logic code}}binding.btnSingleClick.setOnClickListener(object:MyOnClickListener(){overridefunonClick(v:View?){super.onClick(v)//Especially this sentence calls the parent class onClick and wants to retain the logic of executing the parent class methodonSingleClick()}})Writing this way will cause the MyOnClickListener onClick above to also be added to the aspect, which is equivalent to a click that calls back twice the invoke of the aspect processing class, which may not be what we want, so we can change it like this
@AndroidAopMatchClassMethod(targetClassName="android.view.View.OnClickListener",methodName=["onClick"],type=MatchType.EXTENDS,excludeClasses=["com.flyjingfish.androidaop.test.MyOnClickListener"]//Adding this can exclude some classes)classMatchOnClick:MatchClassMethod{overridefuninvoke(joinPoint:ProceedJoinPoint,methodName:String):Any?{Log.e("MatchOnClick","=====invoke=====$methodName")returnjoinPoint.proceed()}}Example 5¶
What if the entry point is a companion object?
Suppose there is such a code
packagecom.flyjingfish.androidaopclassThirdActivity:BaseActivity(){companionobject{funstart(){...}}}@AndroidAopMatchClassMethod(targetClassName="com.flyjingfish.androidaop.ThirdActivity.Companion",methodName=["start"],type=MatchType.SELF)classMatchCompanionStart:MatchClassMethod{overridefuninvoke(joinPoint:ProceedJoinPoint,methodName:String):Any?{Log.e("MatchCompanionStart","======$methodName")returnjoinPoint.proceed()}}Example 6¶
The entry point is Kotlin Member variables of the code, want to monitor the assignment and retrieval operations
In the code, we will have such operations
You can write like this
@AndroidAopMatchClassMethod(targetClassName="com.flyjingfish.androidaop.test.TestBean",methodName=["setName","getName"],type=MatchType.SELF)classMatchTestBean:MatchClassMethod{overridefuninvoke(joinPoint:ProceedJoinPoint,methodName:String):Any?{Log.e("MatchTestBean","======$methodName");ToastUtils.makeText(ToastUtils.app,"MatchTestBean======$methodName")returnjoinPoint.proceed()}}Example 7¶
If the cut point method issuspend What about modified functions?
You can directly useFuzzy Matching
If you want to useExact Matching, the writing is as follows. For specific rules, seeExact Matching
packagecom.flyjingfish.androidaopclassMainActivity:BaseActivity2(){suspendfungetData(num:Int):Int{returnwithContext(Dispatchers.IO){getDelayResult()}}}The exact match is written as follows. Regardless of the return value type of the matching function, writesuspend. For details, see theExact Matching Part
@AndroidAopMatchClassMethod(targetClassName="com.flyjingfish.androidaop.MainActivity",methodName=["suspend getData(int)"],type=MatchType.SELF)classMatchSuspend:MatchClassMethod{overridefuninvoke(joinPoint:ProceedJoinPoint,methodName:String):Any?{Log.e("MatchSuspend","======$methodName")returnjoinPoint.proceed()}}Example 8¶
Want to match all methods of a class
@AndroidAopMatchClassMethod(targetClassName="com.flyjingfish.androidaop.SecondActivity",methodName=["*"],type=MatchType.SELF)classMatchAllMethod:MatchClassMethod{overridefuninvoke(joinPoint:ProceedJoinPoint,methodName:String):Any?{Log.e("MatchMainAllMethod","AllMethod======$methodName");returnjoinPoint.proceed()}}Example 9¶
Want to match all methods of all classes in a package
@AndroidAopMatchClassMethod(targetClassName="com.flyjingfish.androidaop.*",methodName=["*"],type=MatchType.SELF)classMatchAll:MatchClassMethod{overridefuninvoke(joinPoint:ProceedJoinPoint,methodName:String):Any?{Log.e("MatchAll","---->${joinPoint.targetClass}--${joinPoint.targetMethod.name}--${joinPoint.targetMethod.parameterTypes.toList()}");returnjoinPoint.proceed()}}* replacesclass name Or replacepart of the package name + class name, this example represents all classes under thecom.flyjingfish.androidaop package and its subpackages2. Of course, the methodName part can still be filled with multiple fuzzy matching or even exact matching method names
Example 10¶
Want to match top-level functions or top-level extension functions
- Top-level functions
Suppose the following function is located in a kotlin file namedContextEx
@AndroidAopMatchClassMethod(targetClassName="com.androidaop.ktx.ContextExKt",type=MatchType.SELF,methodName=["void toast(java.lang.String)"])classMatchContextKt:MatchClassMethod{overridefuninvoke(joinPoint:ProceedJoinPoint,methodName:String):Any?{returnjoinPoint.proceed()}}As you can see, the signature of this top-level function is nothing special, except that Kt is added to the class name
- Top-level extension functions
Still suppose the following function is located in a kotlin file namedContextEx in the kotlin file
packagecom.androidaop.ktxfunContext.hasPermission(permission:String):Boolean{returnContextCompat.checkSelfPermission(this,permission)==PackageManager.PERMISSION_GRANTED}@AndroidAopMatchClassMethod(targetClassName="com.androidaop.ktx.ContextExKt",type=MatchType.SELF,methodName=["boolean hasPermission(android.content.Context,java.lang.String)"])classMatchContextKt:MatchClassMethod{overridefuninvoke(joinPoint:ProceedJoinPoint,methodName:String):Any?{returnjoinPoint.proceed()}}This top-level extension function not only adds Kt to the class name, but also adds Kt to the class name. , and the first parameter of the function signature is your extension type, and the rest are the same