La realización de AOP en Android y el uso de AspectJ

1. Breve introducción y diferencia entre OOP y AOP

OOP (Programación Orientada a Objetos): Este es el desarrollo orientado a objetos en nuestro Android. Las tres características de la orientación a objetos son encapsulación, herencia y polimorfismo. No hay mucho que repetir aquí.

AOP (Programación Orientada a Aspectos): Programación Orientada a Aspectos; AOP es extraer aspectos en el proceso de procesamiento de la lógica empresarial, es decir, un cierto paso o etapa del procesamiento del programa, para lograr un bajo acoplamiento entre códigos, separación de códigos y mejora de códigos. Reutilización


En segundo lugar, la realización de AOP en Android

2.1 Introducción a la anotación de Java

La función de anotación se ha utilizado en nuestro desarrollo andorid. Las bibliotecas de terceros con anotaciones son ButterKnif, dagger2, EventBus, Retrofit, de hecho, algunas de las funciones centrales de estas bibliotecas también se implementan basadas en AOP. Pero también usan otros complementos, como APT, APT escanea la información de la anotación en el código durante la compilación del programa y genera código Java para que implementemos nuestras funciones sin necesidad de procesarlo manualmente.

Java Annotation es un mecanismo de anotación introducido por JDK5.0. En nuestro código. A menudo puede ver @Override: que el método anula el método principal.

java中的Annotation:

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

2.2, anotación personalizada más reflexión para lograr la función findViewById

Anotación personalizada, realice su propia anotación

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

Herramienta de procesamiento de reflejos de MyInject
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();
                    }
                }
            }
        }
    }
}

Usar en actividad
    @MyInject(R.id.button)
    Button button;

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

De esta manera, nos hemos dado cuenta de la función de findViewById. No es difícil encontrar que esta función es muy similar a findViewById de ButterKnif, pero existen diferencias esenciales. Debido a que usamos la reflexión, consume mucho rendimiento en Android. Por lo tanto, esas bibliotecas de terceros utilizarán Annotation + APT para traducir las anotaciones en código Java para evitar la pérdida de rendimiento. Pero si sabe esto, el entrevistador continuará preguntándole sobre los principios de la biblioteca de terceros, ¡para que no se quede sin palabras! !

En tercer lugar, los escenarios de uso y uso de AspectJ (énfasis)

AspectJ: es una herramienta de generación de código, la gramática de AspectJ se utiliza para definir la gramática de reglas de generación de código

3.1, referencia en el proyecto

En el proyecto build.gradle:

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

Agregar en la parte superior de build.gradle en la aplicación

apply plugin: 'android-aspectjx'

3.2. Escenario de uso: punto enterrado de datos

Por lo general, nuestros puntos enterrados de datos serán monitoreados a través de registerActivityLifecycleCallbacks en la aplicación. Pero aquí usamos AspectJ. El código es el siguiente (las etiquetas aquí son @Before, @After, la ejecución de las palabras clave, la llamada se explican en detalle más adelante, aquí implementamos las funciones primero):

//标注我们要通过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());
    }
}

En este punto, después de obtener el ciclo de vida de todas las actividades, se puede realizar la función de punto enterrado; nota y resumen:

  • Nunca lo use
@Before("execution(* android.app.Activity.on**(..))")

La mayoría de los usuarios de Internet usan esta oración, la práctica encontró que además de caminar, nuestra Actividad también irá a Actividad del sistema y Actividad de fragmentos. Al menos 3 veces


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

Si su BaseActivity no implementa el ciclo de vida del sistema, encontrará que no funciona. Entonces, por ejemplo, cuando desee capturar el ciclo de vida onStart y onPause, debe implementarlo en BaseActivity, incluso si el cuerpo del método está vacío.

De hecho, esto también se puede lograr con la siguiente sintaxis, siempre que todas sus actividades deban usar la cadena "Actividad" al final del nombre de la clase.

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

3.3. Escenario de uso: verificación de inicio de sesión

Cuando estamos desarrollando el proyecto, a menudo es necesario iniciar sesión en algunas funciones antes de poder utilizarlas. Si no ha iniciado sesión, vaya a la página de inicio de sesión. De esta manera, el juicio if / else es inevitable. De la siguiente manera, haga clic en el código cuando siga

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

Entonces, ¿cómo utilizar AOP de forma no intrusiva?
Primero definimos una etiqueta

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

Entonces mira nuestro Aspecto:

@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为空,快去登录把!!");
        }
    }
}

Entonces mira nuestro método de seguimiento. Después de marcar con @IsLogin, puede manejar directamente el estado de inicio de sesión. Acoplamiento realmente bajo, alta reutilización de código

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

Cuatro, palabras clave comunes de AspectJ y sus diferencias

4.1 Introducción a las anotaciones comunes

  • @Before: significa ejecutar antes del método
    //意思是onActivityPause会在BaseActivity.onPause()方法前执行
    @Before("execution(* com.lihang.aoptestpro.BaseActivity.onPause(..))")
    public void onActivityPause(JoinPoint joinPoint) throws Throwable {
    
    

    }
  • @After: la misma razón es ejecutar después del método
  • @Around: incluye las funciones de @Befor y @After, que se pueden controlar
    //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为空,快去登录把!!");
        }
    }

punto importante:

  1. Cuando Action es Before o After, el parámetro de entrada del método es JoinPoint.
  2. Cuando Action es Around, el parámetro de entrada del método es ProceedingPoint.
  3. La mayor diferencia entre Around y Before y After:
    ProceedingPoint es diferente de JoinPoint en que proporciona el método de proceder para ejecutar el método de destino.

4.2 Introducción a las palabras clave comunes

Revisé mucha información, el mismo código. Mira el código generado.

  • llamada: insertada fuera del cuerpo de la función

Simplemente pon:

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

  • ejecución: insertado en el cuerpo de la función

Simplemente pon:

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

Aunque sé cómo funciona. Pero el autor también tiene una pregunta, es decir, la llamada y la ejecución pueden lograr la misma función. Pero, ¿qué escena es mejor usar? Espero que alguien que sepa me pueda ayudar a contestar

referencias

Mire la fuerte inserción de AspectJ en Android.

AOP y el amor y el odio de Android .

Punto enterrado de la automatización de Android: el uso del SDK de Hujiang basado en AspectJ.

Mi cuenta oficial

También me estoy preparando para una entrevista recientemente. El método de aprendizaje de Feynman comienza con descubrirlo por sí mismo y lo narra en un lenguaje sencillo. Bloguear también es el propósito. Los colegas que se están preparando para los materiales de la entrevista encuentran nuevos puntos de conocimiento, y si desea romperlos, puede seguir la cuenta oficial si lo desea.

Supongo que te gusta

Origin blog.csdn.net/leol_2/article/details/105416332
Recomendado
Clasificación