The realization of AOP in Android and the use of AspectJ

1. Brief introduction and difference between OOP and AOP

OOP (Object Oriented Programming): This is the object-oriented development in our android. The three characteristics of object-oriented are encapsulation, inheritance and polymorphism. Not much to repeat here.

AOP (Aspect Oriented Programming): Aspect Oriented Programming; AOP is the extraction of aspects in the business logic processing process, that is, a certain step or stage of program processing, in order to achieve low coupling between codes, code separation, and improve code Reusability


Second, the realization of AOP in Android

2.1 Introduction to Java Annotation

The annotation function has been used in our andorid development. The third-party libraries with annotations are ButterKnif, dagger2, EventBus, Retrofit, in fact, some of the core functions of these libraries are also implemented based on AOP. But they also use other plug-ins, such as APT, APT scans the annotation information in the code during program compilation, and generates java code for us to implement our functions without us needing to process it manually.

Java Annotation is an annotation mechanism introduced by JDK5.0. In our code. You can often see @Override: that the method overrides the parent method.

java中的Annotation:

@Deprecated  --  所标注内容,不再被建议使用。
@Override    --  只能标注方法,表示该方法覆盖父类中的方法。
@Documented  --  所标注内容,可以出现在javadoc中。
@Inherited   --  只能被用来标注“Annotation类型”,它所标注的Annotation具有继承性。
@Retention   --  只能被用来标注“Annotation类型”,而且它被用来指定Annotation的RetentionPolicy属性。
@Target      --  只能被用来标注“Annotation类型”,而且它被用来指定Annotation的ElementType属性。
@SuppressWarnings --  所标注内容产生的警告,编译器会对这些警告保持静默。

2.2, custom Annotation plus reflection to achieve the findViewById function

Custom Annotation, realize your own annotation

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyInject {
    
    
    int value();
}

MyInject's reflection processing tool
public class MyInjectUtils {
    
    
    public static void injectViews(Activity activity) {
    
    
        Class<? extends Activity> object = activity.getClass(); // 获取activity的Class
        Field[] fields = object.getDeclaredFields(); // 通过Class获取activity的所有字段
        for (Field field : fields) {
    
     // 遍历所有字段
            // 获取字段的注解,如果没有ViewInject注解,则返回null
            MyInject viewInject = field.getAnnotation(MyInject.class);
            if (viewInject != null) {
    
    
                int viewId = viewInject.value(); // 获取字段注解的参数,这就是我们传进去控件Id
                if (viewId != -1) {
    
    
                    try {
    
    
                        // 获取类中的findViewById方法,参数为int
                        Method method = object.getMethod("findViewById", int.class);
                        // 执行该方法,返回一个Object类型的View实例
                        Object resView = method.invoke(activity, viewId);
                        field.setAccessible(true);
                        // 把字段的值设置为该View的实例
                        field.set(activity, resView);
                    } catch (NoSuchMethodException e) {
    
    
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
    
    
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

Use in Activity
    @MyInject(R.id.button)
    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyInjectUtils.injectViews(this);
    }

In this way, we have realized the function of findViewById. It is not difficult to find that this function is very similar to ButterKnif's findViewById, but there are essential differences. Because we use reflection, it is very performance-consuming in android. So those third-party libraries will use Annotation+APT to translate annotations into Java code to avoid performance loss. But if you know this, the interviewer will continue to ask you about the principles of the third-party library, so you won't be speechless! !

Third, the use and usage scenarios of AspectJ (emphasis)

AspectJ: is a code generation tool, AspectJ grammar is used to define code generation rules grammar

3.1, reference in the project

In the project build.gradle:

    dependencies {
    
    
        //...
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'
    }

Add at the top of build.gradle in app

apply plugin: 'android-aspectjx'

3.2. Use scenario: data buried point

Usually our data buried points will be monitored through registerActivityLifecycleCallbacks in Application. But here we use AspectJ. The code is as follows (the tags here are @Before, @After, the keywords execution, call are explained in detail later, here we implement the functions first):

//标注我们要通过Aspect语法生成代码的辅助类
@Aspect
public class AspectHelper {
    
    
    private final String TAG = this.getClass().getSimpleName();
    
    //com.lihang.aoptestpro.BaseActivity 是我项目里的BaseActivity
    //这句代码实现的功能是:会打印我们项目里所有Activity里所有的on开头的方法
    //joinPoint.getThis().getClass().getSimpleName() 当前Activity的类名
    @Before("execution(* com.lihang.aoptestpro.BaseActivity.on**(..))")
    public void onActivityStart(JoinPoint joinPoint) throws Throwable {
    
    
        String key = joinPoint.getSignature().toString();
        Log.i(TAG, key + "============" + joinPoint.getThis().getClass().getSimpleName());
    }

    //会打印我们项目里所有Activity里的onPause方法。
    @Before("execution(* com.lihang.aoptestpro.BaseActivity.onPause(..))")
    public void onActivityPause(JoinPoint joinPoint) throws Throwable {
    
    
        String key = joinPoint.getSignature().toString();
        Log.i(TAG, key + "============" + joinPoint.getThis().getClass().getSimpleName());
    }
}

At this point, after getting the life cycle of all activities, the buried point function can be realized; note and summary:

  • Never use it
@Before("execution(* android.app.Activity.on**(..))")

Most of the Internet use this sentence, practice found that in addition to walking our Activity will also go system Activity and FragmentActivity. At least 3 times


  • Use BaseActivity
@Before("execution(* com.lihang.aoptestpro.BaseActivity.on**(..))")

If your BaseActivity does not implement the system life cycle, you will find that it does not go. So for example, when you want to catch the onStart and onPause life cycle, you must implement it in BaseActivity, even if the method body is empty.

In fact, this can also be achieved with the following syntax, provided that all your Activities must use the "Activity" string as the end of the class name

@Before("execution(* com.lihang.aoptestpro.*Activity.on**(..))")

3.3. Usage scenario: login verification

When we are developing the project, some functions often need to be logged in before they can be used. If you are not logged in, go to the login page. In this way, if/else judgment is unavoidable. As follows, click the code when following

    public void follow() {
    
    
        if (MyApplication.getInstance().getLoginUser() != null) {
    
    
            User user = MyApplication.getInstance().getLoginUser();
            Log.i(TAG, "已登录,user不为空,用user信息去实现关注");
        } else {
    
    
            Log.i(TAG, "未登录,跳转登录页面");
        }
    }

So how to use AOP non-intrusively?
First we define a label

@Target(ElementType.METHOD)//这里是标注方法,之前那个Filed是标注属性
@Retention(RetentionPolicy.RUNTIME)
public @interface IsLogin {
    
    
}

Then look at our Aspect:

@Aspect
public class AspectHelper {
    
    
    private final String TAG = this.getClass().getSimpleName();
    
    @Around("execution(@com.lihang.aoptestpro.IsLogin * *(..))")
    public void isLoginOn(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        if (MyApplication.getInstance().getLoginUser() != null) {
    
    
            //joinPoint.proceed()可以看成就是我们用@IsLogin标注的那个方法,调用proceed意思就是继续执行方法
            //这里的意思就是所有用标注@IsLogin标注的,是登录状态才会继续执行方法,否则会执行我们下面的去登录,不会执行原方法
            joinPoint.proceed();
        } else {
    
    
            Log.i(TAG, "user为空,快去登录把!!");
        }
    }
}

Then look at our follow method. After marking with @IsLogin, you can directly handle the login status. Really low coupling, high code reusability

    @IsLogin
    public void follow() {
    
    
        User user = MyApplication.getInstance().getLoginUser();
        Log.i(TAG, "已登录,user不为空,用user信息去实现关注");
    }

Four, AspectJ common keywords and their differences

4.1 Introduction to common annotations

  • @Before: Means to execute before the method
    //意思是onActivityPause会在BaseActivity.onPause()方法前执行
    @Before("execution(* com.lihang.aoptestpro.BaseActivity.onPause(..))")
    public void onActivityPause(JoinPoint joinPoint) throws Throwable {
    
    

    }
  • @After: The same reason is to execute after the method
  • @Around: includes the functions of @Befor and @After, which can be controlled
    //joinPoint.proceed()是控制方法是否继续往下执行
    //在joinPoint.proceed()前的逻辑代码,就是实现@Before的功能,在方法前执行
    //在joinPoint.proceed()后的逻辑代码,就是实现@After的功能,在方法后执行
    @Around("execution(@com.lihang.aoptestpro.IsLogin * *(..))")
    public void isLoginOn(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        if (MyApplication.getInstance().getLoginUser() != null) {
    
    
            joinPoint.proceed();
        } else {
    
    
            Log.i("MainActivity", "user为空,快去登录把!!");
        }
    }

important point:

  1. When Action is Before or After, the method input parameter is JoinPoint.
  2. When Action is Around, the method input parameter is ProceedingPoint.
  3. The biggest difference between Around and Before and After:
    ProceedingPoint is different from JoinPoint in that it provides the proceed method to execute the target method.

4.2 Introduction to common keywords

Looked through a lot of information, the same code. Look at the generated code.

  • call: inserted outside the function body

Simply put:

Call(Before)
Pointcut{
    
    
   Pointcut Method
}
Call(After)

  • execution: inserted in the function body

Simply put:

Pointcut{
    
    
  execution(Before)
    Pointcut Method
  execution(After)
}

Although I know how it works. But the author also has a question, that is, call and execution can achieve the same function. But which scene is better to use? I hope someone who knows can help me answer

references

Look at AspectJ's strong insertion in Android.

AOP and Android's love and hatred.

Android automation buried point: the use of the Hujiang SDK based on AspectJ.

My official account

I am also preparing for an interview recently. Feynman’s learning method starts from figuring it out by himself and narrates it in plain language. Blogging is also the purpose. Colleagues who are preparing for the interview materials encounter new knowledge points, and if you want to break them, you can follow the official account if you like

Guess you like

Origin blog.csdn.net/leol_2/article/details/105416332