Let’s start with the concept

For example, I want to add a buried point when all the pages start ~ I want to add a quick repeat click judgment when all the buttons are clicked ~ and so on. In this way, all the code of the same type in the project will be unified in the method of logical processing, called aspect oriented programming AOP

The points where we need to insert code are called pointcuts, such as when I insert it in a method of some class

The type of place I can insert in a project, called Join Point, for example, I can insert in a method, I can insert in a variable

Advice allows us to specify whether to insert before or after the pointcut, and so on

We’ll talk more about that later

Android implements AOP, and there are two main scenarios available

One is the great god github.com/JakeWharton…

One is Hujiang’s github.com/HujiangTech…

Both are aspectJ-based, so you can configure aspectJ directly, but it’s a hassle

Let’s take Hugo as an example. The pit trip begins now


First configuration

The project build. Gradle

buildscript {

  repositories {

    mavenCentral()

  }

  dependencies {

    classpath 'com. Jakewharton. Hugo: Hugo - plugin: 1.2.1'}}Copy the code

app / build.gradle

apply plugin: 'com.jakewharton.hugo'
Copy the code

The code can be set to on

Hugo.setEnabled(true|false)
Copy the code

Pay attention to

1. If there is a reference module, add the above configuration and write code in the Module

2. Lambda is not supported

Practice ~

For example, to solve the problem of quickly clicking to open multiple pages

Once configured, start writing code ~

@Aspect

public class FastClickBlockAspect {

    public static final String TAG = "FastClickBlockAspect";

    @Around("call(* android.content.Context.startActivity(..) )")

    public void onStartBefore(ProceedingJoinPoint joinPoint) {

        try {

            if (!ViewUtils.isFastClick()) {

                joinPoint.proceed();

            }

        } catch(Throwable e) { e.printStackTrace(); }}}Copy the code

This class file is stored in any package in the dependent Module (if not in the main App Module).

Without any configuration or other code handling, you don’t need to modify the original code ~ and then run the project directly

In this way, all context.startActivity places in the code will determine if it is a quick click and then execute it, achieving the goal of preventing repeated page openings


Explain the code

Aspect marks an AOP class, indicating that the class is a fixed way to handle Aspect code

Advice Pointcut insertion mode. What is the method of processing at the tangent point of a match

  • @around Surround insert. The parameter is ProceedingJoinPoint, and the method proceed() of the parameter can be called in the desired condition after the code is wrapped manually to indicate the execution of the target method
  • @before is inserted Before. Execute before the pointcut
  • @after After insertion. Execute after the pointcut
  • @ After returning. Execute after the return value
  • @ After throwing. Executes after an exception is thrown

Note: Only the Around parameter is ProceedingJoinPoint, and the proceed method needs to be called. The rest of the parameters are inserted before and after, and will not affect the execution of the original code

So with buried point we can use after or before to execute buried point requests before and after the old method;

To prevent continuous page jumps, use Around and then call the original method manually with proceed in the judgment condition

Join Point Join Point. Represents the type of location where we can insert code, used in conjunction with Pointcuts

  • Method call. Method is called. Combined with tangent writing: call (method tangent regular)
  • * * Method execution * *. Method is executed. Execution of a tangent point
  • The Constructor call. The constructor is called. Combined with pointcut notation: call (constructor pointcut regular)
  • The Constructor execution. The constructor is executed. Execution of a constructor’s tangent point.
  • The Field of the get. Property read. Combined with the tangent point writing method: get (variable tangent point regular)
  • The Field set. Property Settings. Combined with the tangent point writing: set (variable tangent point regular)
  • The Pre – init. Before initialization. Constructor tangent regular (preinitialization)
  • Init. Initialization. Initialization (constructor tangent regular)
  • The Static init. Static code block initialization. Write with a pointcut: Staticinitialization (code pointcut regular)
  • The Handler. Exception handling. Combined with the tangent point writing method: Handle (corresponding code tangent point regular)
  • Advice execution. All Advice implemented. Adviceexecution ()

The most commonly used methods are method call and execution. General system class methods are wrapped Around call, @around (call(XXX)).

If it is a custom method and you want to insert it, @before (execution(XXX))

Poincuts tangent point. Is a matching rule that indicates where to cut into the code. The rule looks like this: @annotation Access return value Type Package name. Method name (method parameter)

  • @ annotation optional. It can be used to match the pointcut of a specified annotation, or it can be used to customize annotations where special treatment is required
  • Access permission Optional. Public, private, static, etc

Return value, package name, etc., support wildcard *.. + etc.

  • * matches anything. Such as in the package name. Date can stand for java.sql.Date or java.utils.Date alone. The return value * indicates that any type of concatenation is used. *Dialog matches any XXDialog content
  • . Matches any number of content of any type. Such as in the package name. com.. Utils represents the Utils class parameter used in any Java package and its subpackages. (..) Matches any number of arguments of any type (String,..) Specify the first one, the others are not fixed
  • + means subclass. Such as Java.. *Model+, representing a subclass of a class ending in Model under any Java package or subpackage

So translate the core method part of our previous code

@Around("call(* android.content.Context.startActivity(..) )")
Copy the code

That’s when the system context.startActivity method (call) is inserted Around the code (@around),

Concrete realization method in processing, judging whether click quickly, if not quickly click to normal execution ProceedingJoinPoint. Proceed ()

The problem with the code is that context. startActivity doesn’t contain all cases,

There are also activity. startActivity, startActivityForResult, etc., which are not covered by ~, we can use our new posture to solve the problem, modify as follows

@Aspect

public class FastClickBlockAspect {

    public static final String TAG = "FastClickBlockAspect";

    @Around("call(* android.. *.startActivity*(..) )")

    public void onStartBefore(ProceedingJoinPoint joinPoint) {

        try {

            if (!ViewUtils.isFastClick()) {

                joinPoint.proceed();

            }

        } catch(Throwable e) { e.printStackTrace(); }}}Copy the code

The rules of matching tangent points are modified

@Around("call(* android.. *.startActivity*(..) )")
Copy the code

On Android, any package or sub-package… , any class * (can be an Activity, Context, or Fragment),

Custom processing when the class’s methods startActivity* (including the startActivity method and startActivityForResult method) are called

Moving on, what if you want to prevent the repeat display when you open FragmentDialog,

In this case, the wildcard cannot contain two distinct pointcut rules. We can declare multiple pointcuts and then concatenate them with logical symbols

Pointcut declaration is very simple. You can declare an empty method with @pointcut. You can also add join points after @pointcut.

Multiple methods corresponding to multiple point of tangency, and finally in the need to deal with the main method of @ Around (tangent point rule method 1 | | tangent point rule method 2) such logic pieced together

The following code

@Aspect

public class FastClickBlockAspect {

    public static final String TAG = "FastClickBlockAspect";

    @Pointcut("execution(* com.archex.core.base.BaseDialogFragment.show(..) )")

    public void showBaseDialogFragment(a) {}

    @Pointcut("call(* android.. *.startActivity*(..) )")

    public void startActivity(a) {}

    @Around("showBaseDialogFragment() || startActivity()")

    public void onStartBefore(ProceedingJoinPoint joinPoint) {

        try {

            if (!ViewUtils.isFastClick()) {

                joinPoint.proceed();

            }

        } catch(Throwable e) { e.printStackTrace(); }}}Copy the code

This is the end of simple use ~