/   AOP   /

AOP: Aspect-oriented Programming.

While OOP is about dividing problems into individual modules, AOP is about unifying the management of a class of problems involving many modules. For example, there are three modules: login, transfer and large file upload. Now we need to add performance detection function, and count the time of each method of these three modules.

The OOP idea is to design a performance detection module that provides interfaces for all three modules to call. In this way, each module calls the interface of the performance detection module. If the interface is changed, it needs to be modified at the place of each invocation of the three modules.

The idea is to hook at specific pointcuts between these separate modules, adding common logic to the module without compromising the independence of the original module.

So that’s what it’s all about: unifying a class of problems that involve many modules.

Android AOP’s three musketeers: APT,AspectJ and Javassist.

  • APT applications: Dagger, butterKnife, componentization scheme, etc

  • AspectJ: mainly used for performance monitoring and log burying points

  • Javassist: Hot update (you can do things after you compile and before you pack Dex, so you can push the limits a little bit)

/   AspectJ   /

Today’s hero is AspectJ, which is mainly used in scenarios where you don’t want to intrude on the original code, such as the SDK needing to insert some code in the host without intruders, do log burial, performance monitoring, dynamic permission control, and even code debugging.

Access instructions

First, we need to add dependencies in the project root directory build.gradle:


buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com. Android. Tools. Build: gradle: 2.3.0 - beta 2'
        classpath 'com. Hujiang. Aspectjx: gradle - android plugin - aspectjx: 2.0.6'}}Copy the code

Add the AspectJ dependency to your main project or library’s build.gradle:


compile 'org. Aspectj: aspectjrt: 1.8.9' 

Copy the code

Also add the AspectJX module to build.gradle:


apply plugin: 'android-aspectjx' 

Copy the code

Can’t determine the superclass of missing Type XXXXX? Please refer to the use of excludeJarFilter in the project README.


aspectjx {
    //includes the libs that you want to weave
    includeJarFilter 'universal-image-loader'.'AspectJX-Demo/library'

    //excludes the libs that you don't want to weave
    excludeJarFilter 'universal-image-loader'
} 

Copy the code
The basic use

Looking directly at the code, in the Activity:


@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        testMethod();
    }

    private void testMethod(a) {
        Log.e(DemoAspect.TAG, "testMethod-invoke");
    } 

Copy the code

Create a New DemoAspect class:


@Aspect
public class DemoAspect {
    public static final String TAG = "DemoAspect";

    @Before("execution(* com.hujiang.library.demo.DemoActivity.test*(..) )")
    public void testAspectBefore(JoinPoint point) {
        Log.e(TAG, point.getSignature().getName()+"-before "); }}Copy the code

Print at runtime:


testMethod-before com.hujiang.library.demo E/DemoAspect: testMethod-invoke 

Copy the code

So all we need to do is insert

  1. Add annotation @aspect to class

  2. Add the comment @before to the method

  3. Before writes the relevant information to insert

Is not very simple, the following one in detail.

Advice

That is, the way in which the code we want to insert is inserted is a comment on the method.

“Before” and “After” are easy to understand, as the example above shows clearly.

AfterReturning

This applies to situations where a return value is required, such as:


private int AfterReturningTest(a) {
    Log.e(DemoAspect.TAG, "AfterReturning-invoke");
    return 10;
}

@AfterReturning(value = "execution(* com.hujiang.library.demo.DemoActivity.AfterReturning*(..) )", returning = "num")
public void testAspectAfterReturning(int num) {
    Log.e(TAG, "AfterReturning-num:" + num);
} 

Copy the code

This will get the return value in the section method. Note that the method parameter must match the value in the annotation.

[RETURNING = "red"] ===intNum.Copy the code

AfterThrowing

Collect and monitor abnormal information.


private void AfterThrowingTest(a) {
    View v = null;
    v.setVisibility(View.VISIBLE);
}

@AfterThrowing(value = "execution(* com.hujiang.library.demo.DemoActivity.AfterThrowing*(..) )", throwing = "exception")
public void testAspectAfterReturning(Exception exception) {
    Log.e(TAG, "AfterThrowing-exception:" + exception.getMessage());
} 

Copy the code

Again, the values in the parameters and annotations must be the same. Here, too, a crash occurs, not directly caught by the cut operation, but printed before the exception is thrown.

Around

It can be called before and after a method is executed, which is flexible.


private void AroundTest(a) {
    Log.e(DemoAspect.TAG, "AroundTest-invoke");
}

@Around("execution(* com.hujiang.library.demo.DemoActivity.AroundTest(..) )")
public void testAspectAround(ProceedingJoinPoint point) throws Throwable {
    Log.e(TAG, point.getSignature().getName() + "-before ");
    point.proceed();
    Log.e(TAG, point.getSignature().getName() + "-after ");
} 

Copy the code

ProceedingJoinPoint ProceedingJoinPoint’s PROCEED method invokes the original method for flexible control. You can intercept it without calling it if you want.

Pointcut

Tells the code injection tool where to inject a particular code expression. Which is the sentence in this example:


@Before("execution(* com.hujiang.library.demo.DemoActivity.test*(..) )") 

Copy the code

Let’s break it down into several parts:

@before: Advice, which is the specific insertion point, we’ve already covered

Execution: The type that handles Join points, such as Call, execution, and withincode

Execution of the call is executed before or after a call is made to the method being cut into.


// For Call:Call (Before) Pointcut{Pointcut Method} Call (After)// For Execution:
Pointcut{
  execution(Before)
    Pointcut Method
  execution(After)
} 

Copy the code

The withcode syntax is often used to filter pointcut conditions for more precise entry control, such as:


@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        test1();
        test2();
    }

    public void test(a) {
        Log.e("qiuyunfei"."test");
    }

    public void test1(a) {
        test();
    }

    public void test2(a) {
        test();
    } 

Copy the code

If we want to cut the test method, but only want to call test in test2 before executing the cut method, we need withincode.


// In the test() method
@Pointcut("withincode(* com.hujiang.library.aspect.MainActivity.test2(..) )")
public void invoke2(a) {}// call the test() method
@Pointcut("call(* com.hujiang.library.aspect.MainActivity.test(..) )")
public void invoke(a) {}Test2 (); test2(); test2()
@Pointcut("invoke() && invoke2()")
public void invokeOnlyIn2(a) {}@Before("invokeOnlyIn2()")
public void beforeInvokeOnlyIn2(JoinPoint joinPoint) {
    String key = joinPoint.getSignature().toString();
    Log.d(TAG, "beforeInvokeOnlyIn2: " + key);
} 

Copy the code

MethodPattern: This is the most important expression, roughly :@ annotation and access, type of return value, and package name. Function name (parameter)

@annotations and access permissions (public/ Private/Protect, and static/final) : optional. If they are not set, they are all selected by default. In the case of access permissions, public, private, Protect, static, and final functions will all search if access permissions are not set as a condition.

Return value type: is the return value type of a normal function. If the type is not qualified, the * wildcard character is used.

Package name. Function name: Function used to find matches. You can use wildcards, including and… Plus sign. Where sign is used to match division. Any character other than the sign, and… Represents any subpackage, and the + sign represents a subpackage.


* com.hujiang.library.demo.DemoActivity.test*(..) 

Copy the code

The first part: “” indicates the return value,” “indicates that the return value is of any type.

The second part: is the typical package name path, which can contain “” to carry out the wildcard, several” “no difference. At the same time, there can be “&&, | |,!” To do a combination of conditions. Any method whose name starts with test is written like test*.

The third part :() represents the method’s arguments. You can specify the type, such as android.os.bundle, or (…). This can be used to represent any type or number of arguments, or can be mixed (Android.os.bundle,…). This means that the first parameter is bundle, whatever you want.

Custom Pointcuts: Sometimes we need to specify which methods need to be AOP manipulated with specific goals, which can also be done through annotations. Note first:


@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AspectAnnotation {
} 

Copy the code

Then define it in the section class:

// Define a Pointcut that uses this annotation
@Pointcut("execution(@com.hujiang.library.aspect.AspectAnnotation * *(..) )")
public void AspectAnnotation(a){}@Before("AspectAnnotation()")
public void testAspectAnnotation(JoinPoint point){
    Log.e(TAG, point.getSignature().getName() + "-Before ");
}

// Used in the Activity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    AnnotationTest();
}
@AspectAnnotation
private void AnnotationTest(a) {
    Log.e(DemoAspect.TAG, "AnnotationTest-invoke");
} 

Copy the code

Using MethodPattern is very simple, and we’ve already introduced MethodPattern, so you can’t omit the @ comment here.

/ AspectJ combat /

Implement the login check operation

Many apps have this requirement, before the operation to remind the user to register and log in, jump to the registration or login interface, if implemented with AspectJ is very simple and non-invasive.


private static final String TAG = "AspectCommonTool";

@Pointcut("execution(@xxx.aspectj.annotation.NeedLogin * *(..) )")
public void needLoginMethod(a) {}/ * * *@NeedLoginInsert * into the method if used in a non-activity@NeedLogin, the argument must be passed Context as the jump start page */
@Around("needLoginMethod()")
public void onNeedLoginAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    Context mContext = null;
    / / proceedingJoinPoint getThis () can get object to call the method
    if (proceedingJoinPoint.getThis() instanceof Context) {
        mContext = (Context) proceedingJoinPoint.getThis();
    } else {
        / / proceedingJoinPoint getArgs () can get all the parameters to the method
        for (Object context : proceedingJoinPoint.getArgs()) {
            if (context instanceof Context) {
                mContext = (Context) context;
                break; }}}if (mContext == null) {
        return;
    }
    if (LoginUtils.isLogin()) {
        /** * If the user logs in, the original method */ is used
        proceedingJoinPoint.proceed();
    } else {
        /** * Jump to the main login page */
    } 

Copy the code

Easy to use, non-invasive, and easy to maintain. Similar ideas can be implemented: check network health, check permission status, avoid multiple button clicks, auto-complete caching, and so on.

Performance monitoring

AspectJ’s main applications on Android are performance monitoring, log burying, and so on. Here is a simple example:

We monitor the loading time of the layout to determine whether the layout is too nested or too duplicated, causing the Activity to start slowly. First, we know that the Activity loads the layout using setContentView:

  1. Layout parsing process, IO process

  2. Create View as reflection process

Both of these steps are time-consuming operations, so we need to monitor the setContentView.


@Around("execution(* android.app.Activity.setContentView(..) )")
public void getContentViewTime(ProceedingJoinPoint point) throws Throwable {
    String name = point.getSignature().toShortString();
    long time = System.currentTimeMillis();
    point.proceed();
    Log.e(TAG, name + " cost: " + (System.currentTimeMillis() - time));
}

//Activity
public class DemoActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}Copy the code

The following logs are displayed:


DemoAspect: AppCompatActivity.setContentView(..) cost: 76 

Copy the code

Summary /

In daily development, we can upload the time to the server, collect user information, find the stalled Activity and make corresponding optimization.

Of course, this is a very simple implementation, and in practice you can monitor any location you want. Android learning PDF+ architecture video + interview document + source notes