This article related code Github address mk_aspectj, helpful words Star a wave bar.

APM Basics: AspectJ outline

As an Android developer, I have more or less encountered this situation. My App is stuck, and I probably know what the problem is? But do not know how to start, to accept other people’s project code is a mess, a big bug, but because not familiar with the business can’t touch, is there a invasive is lower, more harmonious way to modify the business code learning design patterns can reduce the coupling of business to a certain extent, but it is the idea of OOP, today I bring you a copy AOP’s faceted programming idea is to weave code into the business in a non-intrusive way. If you have some inspiration, welcome to like and forward

Key technology

So the question is, right?

  • What is the AOP?
  • What does AOP do?
  • How does AOP learn?

Do you have a lot of question marks?

What does the AOP Wikipedia say?

spect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a “pointcut” specification, such as “log all function calls when the function’s name begins with ‘set'”. This allows behaviors that are not central to the business logic (such as logging) to be added to a program without cluttering the code, core to the functionality. AOP forms a basis for aspect-oriented software development

Isn’t that hard to understand? Let me just summarize.

  • AOP is section-oriented programming, through AOP, the code can be dynamically managed in the compiler to achieve the purpose of unified maintenance.
  • AOP is a continuation of OOP and an important module of the Spring framework

Java back-end development is no stranger to Spring dynamic proxy weaving because of AOP ideas. What problem does it solve?

  • Using AOP, we can isolate the business modules, which reduces the coupling between the various parts of the business logic, increases the reusability of the program, and also improves development efficiency, such as how we measure time, how we insert code into a given business library without modifying the code.
  • With AOP, we can insert some code logic into the host in a non-intrusive state to implement special functions such as: log burying, performance monitoring, dynamic permission control, code debugging, etc.

Since AOP is so good, how can we learn from it? To learn a tool, you must first understand some of its technical terms such as:

  • Advice: strengthen

    • An enhancement is a piece of program code woven into the join point of a target class. In the Spring framework, an enhancement is used to describe a piece of program code and has another piece of join point information, which is the location of the execution point. Combining the execution point location information with the pointcut information, we can find the specific connection
  • JoinPoint: join

    • What are join points?
      • A class or program code has specific points that have boundary properties called JoinPoints
    • Join point execution at a specific location
        1. Class before initialization
        1. Class after initialization
        1. Class before a method is called
        1. Method throws an exception
    • Flaws in the Spring framework
      • Only join points of methods are supported
        • Method call before method call after method throw exception before and after method call program execution point weaving

  • PointCut: point of tangency

    • A query tool that locates a join point requires orientation information
  • Aspect: the plane

    • Components: enhancement + pointcut
  • Has: weave

  • Weave into the implementation
    • Compiler weaving
      • Ajc compiler provides
    • Class loading date woven in
      • This offer
    • Dynamic proxy weaving
      • Add enhanced generated subclasses for the target class at run time
  • Target: indicates the Target object
    • Define the PointCut
      • Where we need to add additional operations, query JoinPoint through PointCut
    • Tell the program JointCut how to enhance Advice
      • The fixed object in the Aspect is called Target, and the AOP operation is called Weaving

Master these basic knowledge,AOP almost to learn, and so on, so soon to learn, is not the source code, this Spring I did not have too much research, after the opportunity and everyone again, we began to directly into the theme, into our Title AspectJ, learn AspectJ It is important to understand AspectJ annotations, common pointcut expressions, so that you can use AspectJ correctly, implement code weaving based on your custom Gradle Plugin, and many other fun things.

2. The AspectJ

2.0.1 AspectJ annotations

2.0.1.1 @aspectj
  • The aspect class is intended to be recognized by the AJC compiler
 @Aspect
public class MkOnClickListenerAspectJ {}
Copy the code

The MkOnClickListenerAspectJ class is recognized at the compiler by AspectJ’s AJC compiler

2.0.1.2 the @pointcut
  • Define a pointcut marking method
    @Pointcut("execution(void android.view.View.OnClickListener.onClick(..) )"
    public void fastClickViewPointcut(JoinPoint point) {

        Log.e("fastClickViewPointcut"."-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");

    }
Copy the code

The Pointcut annotation matches the onClick method of the OnClickListener annotation

2.0.1.3 @ Before
  • Front-loading enhancement, performed before a join point
    @Before("execution(void android.content.DialogInterface.OnClickListener.onClick(..) )"
    public void fastClickViewBefore(JoinPoint point) {
        View view =  (View) point.getArgs()[0];
        Log.e("fastClickViewBefore", view.toString()+"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
    }
Copy the code

The point of tangency expression, can match all android. The content. DialogInterface. An OnClickListener. OnClick method, and obtain the view before method, and then print it

2.0.1.4 @ After
  • Post-enhancement, performed after a join point
    @After("execution(void android.support.v7.app.AppCompatViewInflater.DeclaredOnClickListener.onClick(..) )"
    public void fastClickViewAfter(JoinPoint point) {

      View view =  (View) point.getArgs()[0];
        Log.e("fastClickViewAfter", view.toString()+"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
    }

Copy the code

The tangent point above expression, can match all DeclaredOnClickListener onClick method, and the method after obtaining the view parameter, and then print it

2.0.1.5 @ Around
  • Surround enhancement, performed before and after the pointcut
    @Around("execution(* android.view.View.OnClickListener.onClick(..) )"
    public void fastClickViewAround(ProceedingJoinPoint point) throws Throwable {

        Log.e("AspectJ"."before" );
        long startNanoTime = System.nanoTime();

        Object proceed = point.proceed();
        Log.e("AspectJ"."after" );

        long stopNanoTime = System.nanoTime();
        MethodSignature signature = (MethodSignature) point.getSignature();

        / / the method name
        String name = signature.getName();
        Log.e("AspectJ"."proceed" + name);

        Method method = signature.getMethod();
        Log.e("AspectJ", method.toGenericString());

        // Return value type
        Class returnType = signature.getReturnType();
        Log.e("AspectJ", returnType.getSimpleName());

        Class declaringType = signature.getDeclaringType();
        Log.e("AspectJ", declaringType.getSimpleName());

        Class signatureDeclaringType = signature.getDeclaringType();
        Log.e("AspectJ", signatureDeclaringType.getSimpleName());

        Class declaringType1 = signature.getDeclaringType();
        Log.e("AspectJ", declaringType1.getSimpleName());

        Class[] parameterTypes = signature.getParameterTypes();

        for (Class parameterType : parameterTypes) {
            Log.e("AspectJ", parameterType.getSimpleName());
        }

        for (String parameterName : signature.getParameterNames()) {
            Log.e("AspectJ", parameterName);
        }

        Log.e("AspectJ", String.valueOf(stopNanoTime - startNanoTime));
    }
Copy the code

Method prints log.e (“AspectJ”, “before”) before execution; Print log.e (“AspectJ”, “after”) after execution; , mainly processing different business scenarios based on the return value of PROCEED

2.0.1.6 @ AfterReturing
  • Returns the enhanced pointcut method after it returns the result
    @AfterReturning("execution(@butterknife.OnClick * *(..) )"
    public void fastClickViewAfterReturning(JoinPoint point) {
        Log.e("AfterReturning"."-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
    }

Copy the code

Can match all @butterknife.OnClick methods and print “AfterReturning” after the result returns the result

2.0.1.7 @ AfterThowing
  • Exception enhancement, executed when the pointcut throws an exception
    @AfterThrowing("execution(@butterknife.OnClick * *(..) )"
    public void fastClickViewThrowing(JoinPoint point) {
        Log.e("fastClickViewThrowing"."-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");

    }
Copy the code

execution(@butterknife.OnClick * *(..) ) when an exception is thrown, you can use this API to do log reporting

Now that we know so much about pointcuts and the use of pointcuts expressions, what are the common formulas they use? Let’s get to the summary stage.

2.0.2 Expression of tangent point

  • execution
    • Basic specification of execution
      • Execution (” Decorator pattern “? Return type model method name model (parameter model) Exception model?
        • Modifier mode: for example, public Private protected
        • Return type model: such as String Object
        • Method name model: such as traceOnClickView
        • Parametric models: e.g. Params
        • Exception model: such as ClassNotFoundException
        • ? : optional

        A recent review of AspectJ’s official documentation found an interesting Q&A with a lot of programmers talking about itIn fact, I am quite embarrassed about this problem. I have not studied it carefully before, so I wrote a test case to verify it

2.0.3 Difference between call and execution

public class C {
    public void foo(a) {
        bar();
    }

    public void bar(a) {}}public aspect A {

    // the output will be:
    // call(void C.bar())
    // execution(void C.foo())
    before() :
        call(public void C.bar()) {
          System.out.println(thisJoinPoint);
          System.out.println(thisEnclosingJoinPointStaticPart);
       }

    // the output will be:
    // execution(void C.bar())
    // execution(void C.bar())
    before() :
       execution(public voidC.bar()) { System.out.println(thisJoinPoint); System.out.println(thisEnclosingJoinPointStaticPart); }}Copy the code

In fact, the biggest difference between the two is that one is at the call point, one is at the execution point. In other words, execution is a call into a method, and call is a call into a method

2.0.4 AspectJ usage method

  • Gradle Plugin such as AspectJx
  • Gradle configuration

2.0.5 Using AspectJ with Gradle configuration

Before akulaku I made a requirement in this way, only to be scolded by the leader, who said that I did not understand the principle clearly. Yes, I copied the parameters directly from Github, and I was speechless at that time. I finished the requirement of three days’ working hours in one day, and also provided two sets of schemes Plugins are not very reliable unless you really don’t want to use three-party plugins

2.0.6 Customize Gradle Plugin

2.0.6.1 Create a new project as the main moudle, i.e. app
2.0.6.2 Creating a Plugin moudle
    1. Create an Android Lib with the moudle name plugin and the directory structure as follows

  • Delete unnecessary SRC files and so on
2.0.6.3 Creating a Grovvy directory
  • Because the development is based on the Groovy language, so the plug-in code in the SRC/main/Groovy directory, and then in the directory to create a new pageage, named: com. Making. Microkibaco. Plugin. Android
2.0.6.4 Creating the Properties file
  • In the SRC /main directory, create the resource/ meta-infgradle-plugins file once, and then create a.properties file to declare names and common package names
    • For example, I created a com. Making. Microkibaco. Properties

2.0.6.5 Adding a Dependency
  • Modify plugin/build.gradle file dependencies on gradle and grovvy

2.0.6.6 Implement plug-ins

2.0.6.7 Modifying the.properties file
  • Implementation-class = package name + class name
    • implementation-class=com.github.microkibaco.plugin.android.MkAspectjPlugin
  • Compile the plugin and find the newly generated plugin.jar file in plugin/build

2.0.7 Publishing Gradle plug-ins

  • 2.0.7.1 Release mode
    • Publish the plug-in to the local repository
      1. Introduce the mavenDeployer plug-in
      • Modify the plugin/build.gradle file and import the mavenDeployer plugin to publish to the local repository. Here are some of the more common parameters
        • GroupId Organization name or company name
        • ArtifactId Project name or module name
        • Version Current version package of the project or module
        1. Compile the plug-in
    • Publish plug-ins to remote repositories

2.0.8 Use Gradle Plugin

  • Modify the Project/build gradle configuration, format for: groupId. ArtfactId: version
  • Modify app/build.gradle format to resource/ meta-infgradle-plugins. properties prefix file name

2.0.9 Plugin Project

2.1.0 Core Ideas

  • The core module is the AJC compiler, which essentially inserts AspectJ code into the target program at compile time. The key to using AspectJ is the AJC compiler, which inserts AspectJ code into the PointCut for AOP purposes

AspectJ full buried point implementation

3.1 AspectJ Full buried Point principle

  • View in Android system, its click processing logic is realized by setting the corresponding listener object and rewriting the corresponding callback method
    • Automatic embedding, or full embedding, can be achieved by inserting the corresponding embedding code into the onClick method during application compilation, such as before generating.dex
    • AspectJ’s processing script is put into our custom plug-in, and the corresponding section class is not defined in the appropriate PointCut to match the target method we weave in, such as onClick, so that buried code can be inserted at compile time

3.2 AspectJ full buried point implementation process

3.2.1 Creating an Autotrace_SDK Android Moudle

3.2.2 Writing embedded SDK

3.2.3 Added init initialization method

Initialize the embedded SDK, usually used in Application

3.2.4 getInstance

Get the buried SDK instance object

3.2.5 Adding a Dependency

3.2.6 Initializing the buried SDK

3.2.6 Declaring a custom Application

3.2.7 Obtaining important buried point information

3.2.7.1 $element_id
    /** * Get view's anroID :id string **@param view View
     * @return String
     */
    private static String getViewId(View view) {
        String idString = null;
        try {
            if (view.getId() != View.NO_ID) {
                idString = view.getContext().getResources().getResourceEntryName(view.getId());
            }
        } catch (Exception e) {
            //ignore
        }
        return idString;
    }
Copy the code
3.2.7.2 $activity
    /** * get the Activity to which the View belongs@param view View
     * @return Activity
     */
    private static Activity getActivityFromView(View view) {
        Activity activity = null;
        if (view == null) {
            return null;
        }

        try {
            Context context = view.getContext();
            if(context ! =null) {
                if (context instanceof Activity) {
                    activity = (Activity) context;
                } else if (context instanceof ContextWrapper) {
                    while(! (contextinstanceof Activity) && context instanceof ContextWrapper) {
                        context = ((ContextWrapper) context).getBaseContext();
                    }
                    if (context instanceofActivity) { activity = (Activity) context; }}}}catch (Exception e) {
            e.printStackTrace();
        }
        return activity;
    }
    
    / / call
     activity.getClass().getCanonicalName()
Copy the code
3.2.7.2 $activity_title
    /** * get the Activity's title **@param activity Activity
  * @returnThe Activity of the title * /
 @androidx.annotation.RequiresApi(api = Build.VERSION_CODES.KITKAT)
 private static String getActivityTitle(Activity activity) {
     try {
         if(activity ! =null) {
             try {
                 String activityTitle = null;
                 if(! TextUtils.isEmpty(activity.getTitle())) { activityTitle = activity.getTitle().toString(); } String toolbarTitle = getToolbarTitle(activity);if(! TextUtils.isEmpty(toolbarTitle)) { activityTitle = toolbarTitle; }if (TextUtils.isEmpty(activityTitle)) {
                     PackageManager packageManager = activity.getPackageManager();
                     if(packageManager ! =null) {
                         ActivityInfo activityInfo = packageManager.getActivityInfo(activity.getComponentName(), 0);
                         if(! TextUtils.isEmpty(activityInfo.loadLabel(packageManager))) { activityTitle = activityInfo.loadLabel(packageManager).toString(); }}}return activityTitle;
             } catch (Exception e) {
                 return null; }}return null;
     } catch (Exception e) {
         e.printStackTrace();
         return null; }}@androidx.annotation.RequiresApi(api = Build.VERSION_CODES.KITKAT)
 private static String getToolbarTitle(Activity activity) {
     try {
         ActionBar actionBar = activity.getActionBar();
         if(actionBar ! =null) {
             if(! TextUtils.isEmpty(actionBar.getTitle())) {returnactionBar.getTitle().toString(); }}else {
             if (activity instanceof AppCompatActivity) {
                 AppCompatActivity appCompatActivity = (AppCompatActivity) activity;
                 androidx.appcompat.app.ActionBar supportActionBar = appCompatActivity.getSupportActionBar();
                 if(supportActionBar ! =null) {
                     if(! TextUtils.isEmpty(supportActionBar.getTitle())) {return Objects.requireNonNull(supportActionBar.getTitle()).toString();
                     }
                 }
             }
         }
     } catch (Exception e) {
         e.printStackTrace();
     }
     return null;
 }
Copy the code
3.2.7.2 $element_content
 / * * * support TabHost. OnTabChangeListener. OnTabChanged * (String)@param joinPoint JoinPoint
  */
 @After("execution(* android.widget.TabHost.OnTabChangeListener.onTabChanged(String))")
 public void onTabChangedAop(final JoinPoint joinPoint) {
     String tabName = (String) joinPoint.getArgs()[0];
     SensorsDataPrivate.trackTabHost(tabName);
 }
Copy the code
3.2.7.3 Customizing Attributes

    /** * Sets the View property **@paramView Specifies the view * to set@paramProperties View properties to set */
 public void setViewProperties(View view, JSONObject properties) {
     if (view == null || properties == null) {
         return;
     }

     view.setTag(R.id.sensors_analytics_tag_view_properties, properties);
 }

// Get this property

Object pObject = view.getTag(R.id.sensors_analytics_tag_view_properties);
Copy the code

4. Optimization of AspectJ full buried point implementation scheme

Problem 1: Unable to collect events bound by ButterKnife’s @onclick annotation

    /** * Supports ButterKnife@OnClickNote * *@param joinPoint JoinPoint
  */
 @After("execution(@butterknife.OnClick * *(android.view.View))")
 public void onButterknifeClickAop(final JoinPoint joinPoint) {
     View view = (View) joinPoint.getArgs()[0];
     SensorsDataPrivate.trackViewOnClick(view);
 }

Copy the code

Problem 2: Unable to collect events bound by the Android :OnClick attribute

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensorsDataTrackViewOnClick {
}

Copy the code
    / * * *@SensorsDataTrackViewOnClickNote * *@param joinPoint JoinPoint
  */
 @After("execution(@com.sensorsdata.analytics.android.sdk.SensorsDataTrackViewOnClick * *(android.view.View))")
 public void onTrackViewOnClickAop(final JoinPoint joinPoint) {
     View view = (View) joinPoint.getArgs()[0];
     SensorsDataPrivate.trackViewOnClick(view);
 }

Copy the code

Problem 3: Unable to collect MenuItem click events

    /** * Support onMenuItemSelected(int, android.view.menuItem) **@param joinPoint JoinPoint
  */
 @After("execution(* android.app.Activity.onMenuItemSelected(int, android.view.MenuItem))")
 public void onMenuItemSelectedAop(JoinPoint joinPoint) {
     MenuItem view = (MenuItem) joinPoint.getArgs()[1];
     SensorsDataPrivate.trackViewOnClick(joinPoint.getTarget(), view);
 }

Copy the code

AspectJ extends collection capabilities

Extension 1: Support for collecting AlertDialog click events

    / * * * support DialogInterface. An OnClickListener. OnClick (android. Content. DialogInterface, int) * *@param joinPoint JoinPoint
  */
 @After("execution(* android.content.DialogInterface.OnClickListener.onClick(android.content.DialogInterface, int))")
 public void onDialogClickAop(final JoinPoint joinPoint) {
     DialogInterface dialogInterface = (DialogInterface) joinPoint.getArgs()[0];
     int which = (int) joinPoint.getArgs()[1];
     SensorsDataPrivate.trackViewOnClick(dialogInterface, which);
 }
Copy the code

Extension 2: Supports collection of CheckBox SwitchCompat RadioButton,ToggleButton, RadioGroupp and other click events


 / * * * support CompoundButton. OnCheckedChangeListener. OnCheckedChanged (android.widget.Com poundButton, Boolean) * *@param joinPoint JoinPoint
  */
 @After("execution(* android.widget.CompoundButton.OnCheckedChangeListener.onCheckedChanged(android.widget.CompoundButton,boolean))")
 public void onCheckedChangedAop(final JoinPoint joinPoint) {
     CompoundButton compoundButton = (CompoundButton) joinPoint.getArgs()[0];
     boolean isChecked = (boolean) joinPoint.getArgs()[1];
     SensorsDataPrivate.trackViewOnClick(compoundButton, isChecked);
 }
Copy the code

Extension 3: Support for collecting RattingButton click events

    / * * * support RatingBar. OnRatingBarChangeListener. OnRatingChanged (android. The widget. The RatingBar, float, Boolean) *@param joinPoint JoinPoint
  */
 @After("execution(* android.widget.RatingBar.OnRatingBarChangeListener.onRatingChanged(android.widget.RatingBar,float,boolean))")
 public void onRatingBarChangedAop(final JoinPoint joinPoint) {
     View view = (View) joinPoint.getArgs()[0];
     SensorsDataPrivate.trackViewOnClick(view);
 }

Copy the code

Extension 4: Support for collecting SeekBar click events

 / * * * support SeekBar. OnSeekBarChangeListener. OnStopTrackingTouch (android. The widget. The SeekBar) *@param joinPoint JoinPoint
  */
 @After("execution(* android.widget.SeekBar.OnSeekBarChangeListener.onStopTrackingTouch(android.widget.SeekBar))")
 public void onStopTrackingTouchMethod(JoinPoint joinPoint) {
     View view = (View) joinPoint.getArgs()[0];
     SensorsDataPrivate.trackViewOnClick(view);
 }
Copy the code

Extension 5: Support collecting Spinner click events

   if (adapterView instanceof Spinner) {
             properties.put("$element_type"."Spinner");
             Object item = adapterView.getItemAtPosition(position);
             if(item ! =null) {
                 if (item instanceof String) {
                     properties.put("$element_content", item); }}}Copy the code

Extension 6: Support collecting TabHost click events

  / * * * support TabHost. OnTabChangeListener. OnTabChanged * (String)@param joinPoint JoinPoint
  */
 @After("execution(* android.widget.TabHost.OnTabChangeListener.onTabChanged(String))")
 public void onTabChangedAop(final JoinPoint joinPoint) {
     String tabName = (String) joinPoint.getArgs()[0];
     SensorsDataPrivate.trackTabHost(tabName);
 }
Copy the code

Extension 7: Support collecting click events for ListView GrildView

    /**
  * public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id)
  *
  * @param joinPoint JoinPoint
  */
 @After("execution(* android.widget.ExpandableListView.OnChildClickListener.onChildClick(android.widget.ExpandableListView, android.view.View, int, int, long))")
 public void onExpandableListViewChildClickAop(final JoinPoint joinPoint) {
     ExpandableListView expandableListView = (ExpandableListView) joinPoint.getArgs()[0];
     View view = (View) joinPoint.getArgs()[1];
     int groupPosition = (int) joinPoint.getArgs()[2];
     int childPosition = (int) joinPoint.getArgs()[3];
     SensorsDataPrivate.trackExpandableListViewChildOnClick(expandableListView, view, groupPosition, childPosition);
 }
Copy the code

Extension 8: Supports collecting click events of the ExpendableListView


  /**
  * public boolean onGroupClick(ExpandableListView expandableListView, View view, int groupPosition, long l)
  *
  * @param joinPoint JoinPoint
  */
 @After("execution(* android.widget.ExpandableListView.OnGroupClickListener.onGroupClick(android.widget.ExpandableListView, android.view.View, int, long))")
 public void onExpandableListViewGroupClickAop(final JoinPoint joinPoint) {
     ExpandableListView expandableListView = (ExpandableListView) joinPoint.getArgs()[0];
     View view = (View) joinPoint.getArgs()[1];
     int groupPosition = (int) joinPoint.getArgs()[2];
     SensorsDataPrivate.trackExpandableListViewChildOnClick(expandableListView, view, groupPosition, -1);
 }
Copy the code

Pitfalls of AspectJ

  • Unable to weave into third party library
  • This scheme is not compatible with Lambada syntax due to the programming language of the pointcut defined
  • There are compatibility issues such as D8 Gradle 4.x and so on

Business value of AspectJ

When learning a new technology, it is important to consider the business attributes that it carries with it, so what is the use of AspectJ in real development? Post one from a former colleagueMind mapsLater encountered the following problems, can give priority to using the thought of section to solve

Your likes and comments are great encouragement to me!