12个Android中使用AspectJ实现AOP的实例操作


在这里插入图片描述

AspectJ语法

https://www.eclipse.org/aspectj/doc/released/progguide/index.html

AspectJ配置

根目录的build中的dependencies增加:

classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.6'

app或者library的build中增加aspectjrt的依赖:

implementation 'org.aspectj:aspectjrt:1.8.14'

另外还需在app和library中增加配置:

apply plugin: 'android-aspectjx'

AspectJ实例

添加日志、权限拦截、防止双击、状态检测、埋点、异步、拦截崩溃、HOOK、缓存、Null判空、添加View控件、拦截Toast等,你能想到的,AOP都能做到。

1. 添加日志

  1. 功能:
    计算方法执行的时间并打印出来。
  2. annotation
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD})
public @interface InsertLog {
}
  1. aspect
@Aspect
public class InsertLogAspect {

    private final String TAG = "InsertLogAspect";
    private final String POINTCUT = "execution(@com.androidwind.quickaop.library.annotation.InsertLog * *(..))";
    private long startTime, endTime;
    private String className;
    private String methodName;

    @Pointcut(POINTCUT)//切点annotation
    public void onInsertLogMethod() {

    }

    @Before("onInsertLogMethod()")//Before:在原方法前面插入
    public void before(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        className = methodSignature.getDeclaringType().getSimpleName();
        methodName = methodSignature.getName();
        startTime = System.nanoTime();
        Log.i(TAG, className + "." + methodName + "'s start time: " + startTime);
    }

    /**
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("onInsertLogMethod()")//重写原方法
    public void doInsertLogMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        Log.i(TAG, "Around before proceed:" + className + "." + methodName);
        //
        Object result = joinPoint.proceed();//执行原方法
        //
        Log.i(TAG, "Around after proceed:" + className + "." + methodName);
    }

    @After("onInsertLogMethod()")//After:在原方法后面插入, 注意要写在Around后
    public void after() {
        endTime = System.nanoTime();
        Log.i(TAG, className + "." + methodName + "'s end time: " + endTime);
        Log.i(TAG, className + "." + methodName + " spent: " + (TimeUnit.NANOSECONDS.toMillis(endTime - startTime)));//换算成毫秒
    }
}
  1. 使用方法
@InsertLog
    public void insertLog(View view) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.i(TAG, "this is an insert log.");
    }

2. 防止快速点击

  1. 功能
    防止段时间内对view控件的多次点击,这里可以设置时间间隔,即多长时间内不能多次点击。
  2. annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SingleClick {
    long value() default 1000L;

    int[] ids() default {View.NO_ID}; //支持多个值
}
  1. aspectj
@Aspect
public class SingleClickAspect {

    private final String TAG = "SingleClickAspect";

    private static int TIME_TAG = R.id.click_time;

    @Pointcut("execution(@com.androidwind.quickaop.library.annotation.SingleClick * *(..))")
    public void onSingleClickMethod() {
    }

    @Around("onSingleClickMethod() && @annotation(singleClick)")
    public void doSingleClickMethod(ProceedingJoinPoint joinPoint, SingleClick singleClick) throws Throwable {
        Log.d("SingleClickAspect", "singleClick=" + singleClick.hashCode());
        View view = null;
        for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof View) {
                view = (View) arg;
                break;
            }
        }
        if (view != null) {
            Object tag = view.getTag(TIME_TAG);
            long lastClickTime = ((tag != null) ? (long) tag : 0);
            Log.d("SingleClickAspect", "lastClickTime:" + lastClickTime);
            long currentTime = Calendar.getInstance().getTimeInMillis();
            long value = singleClick.value();
            int[] ids = singleClick.ids();
            if (currentTime - lastClickTime > value || !hasId(ids, view.getId())) {
                view.setTag(TIME_TAG, currentTime);
                Log.d("SingleClickAspect", "currentTime:" + currentTime);
                joinPoint.proceed();//执行原方法
            }

        }
    }

    public static boolean hasId(int[] arr, int value) {
        for (int i : arr) {
            if (i == value)
                return true;
        }
        return false;
    }
}
  1. 使用方法
@SingleClick(value = 2000L, ids = {R.id.singleClick1})
    public void singleClick1(View view) {
        Log.i(TAG, "this is a single click log.");
    }

    @SingleClick(ids = {R.id.singleClick2})
    public void singleClick2(View view) {
        Log.i(TAG, "this is a single click log.");
    }

3. 登录态检测

  1. 功能
    检测当前是否已经登录,如果登录继续执行,如果没登录则提示。
  2. annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface CheckLogin {
}
  1. aspectj
@Aspect
public class CheckLoginAspect {
    private final String TAG = "CheckLoginAspect";
    private final String POINTCUT = "execution(@com.androidwind.quickaop.annotation.CheckLogin * *(..))";

    @Pointcut(POINTCUT)
    public void onCheckLoginMethod() {
    }

    @Around("onCheckLoginMethod()")
    public void doCheckLoginMethod(ProceedingJoinPoint joinPoint) throws Throwable {
       if (MyApplication.isLogin()) {
           joinPoint.proceed();//执行原方法
       } else {
           Toast.makeText(MyApplication.getApplication(), "还未登录", Toast.LENGTH_SHORT).show();
       }
    }
}
  1. 使用方法
@CheckLogin
    public void checkLogin(View view) {
        Log.i(TAG, "this is an operation after login.");
    }

4. 请求权限

  1. 功能
    在执行方法前检测app是否已经获得了相应的权限。
    这里引入RxPermissions作为权限检测工具。
  2. annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RequirePermission {

    String[] value();
}
  1. aspectj
@Aspect
public class RequirePermissionAspect {

    private final String TAG = "RequirePermissionAspect";
    private final String POINTCUT = "execution(@com.androidwind.quickaop.annotation.RequirePermission * *(..))";

    @Pointcut(POINTCUT)
    public void onRequirePermissionMethod() {
    }

    @Around("onRequirePermissionMethod() && @annotation(requirePermission)")
    public void doRequirePermissionMethod(ProceedingJoinPoint joinPoint, RequirePermission requirePermission) throws Throwable {
        FragmentActivity activity = null;
        final Object object = joinPoint.getThis();
        if (object instanceof FragmentActivity) {
            activity = (FragmentActivity) object;
        } else if (object instanceof Fragment) {
            activity = ((Fragment) object).getActivity();
        }
        if (activity == null) {
            joinPoint.proceed();
        } else {
            new RxPermissions(activity)
                    .request(requirePermission.value())
                    .subscribe(new Consumer<Boolean>() {
                        @Override
                        public void accept(Boolean granted) throws Exception {
                            if (granted) { // Always true pre-M
                                try {
                                    joinPoint.proceed();
                                } catch (Throwable throwable) {
                                    throwable.printStackTrace();
                                }
                            } else {
                                Toast.makeText(MyApplication.getApplication(), "授权失败!", Toast.LENGTH_SHORT).show();
                            }
                        }
                    });
        }
    }
}
  1. 使用方法
@RequirePermission(value = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE})
    public void requirePermission(View view) {
        Toast.makeText(MyApplication.getApplication(), "授权成功,继续进行", Toast.LENGTH_SHORT).show();
    }

5. 埋点

  1. 功能
    添加埋点信息,用于数据统计功能。
  2. annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface EventTracking {

    String key();

    String value();
}
  1. aspectj
@Aspect
public class EventTrackingAspect {

    private final String TAG = "EventTrackingAspect";
    private final String POINTCUT = "execution(@com.androidwind.quickaop.library.annotation.EventTracking * *(..))";

    @Pointcut(POINTCUT)
    public void onEventTrackingMethod() {
    }

    @Around("onEventTrackingMethod() && @annotation(eventTracking)")
    public void doEventTrackingMethod(ProceedingJoinPoint joinPoint, EventTracking eventTracking) throws Throwable {

        String key = eventTracking.key();
        String value = eventTracking.value();
        SPUtils.getInstance().put(key, value);
        joinPoint.proceed();
    }
}
  1. 使用方法
@EventTracking(key = "1000", value = "埋点值1")
    public void eventTracking(View view) {
        Log.i(TAG, "this is an event tracking log, the eventTracking is " + SPUtils.getInstance().getString("1000"));
    }

6. 异步执行

  1. 功能
    保证方法是通过异步方式执行,这里使用RxJava实现异步。
  2. annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Asynchronize {
}
  1. aspectj
@Aspect
public class AsynchronizeAspect {

    private final String TAG = "AsynchronizeAspect";
    private final String POINTCUT = "execution(@com.androidwind.quickaop.annotation.Asynchronize * *(..))";

    @Pointcut(POINTCUT)
    public void onAsynchronizeMethod() {
    }

    @Around("onAsynchronizeMethod()")
    public void doAsynchronizeMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        Observable.create(new ObservableOnSubscribe<Object>() {
            @Override
            public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
                System.out.println("[Thread Name-AsynchronizeAspect: ]" + Thread.currentThread().getName());
                try {
                    joinPoint.proceed();
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }
            }
        })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe();
    }
}
  1. 使用方法
@Asynchronize
    public void asynchronize(View view) {
        System.out.println("[Thread Name-asynchronize: ]" + Thread.currentThread().getName());
    }

7. 异常捕获

  1. 功能
    捕获此方法所可能产生的异常情况,保证执行此方法不会导致app崩溃。
  2. annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface CatchException {
}
  1. aspectj
@Aspect
public class CatchExceptionAspect {

    private final String TAG = "CatchExceptionAspect";
    private final String POINTCUT = "execution(@com.androidwind.quickaop.library.annotation.CatchException * *(..))";

    @Pointcut(POINTCUT)
    public void onCatchExceptionMethod() {
    }

    @Around("onCatchExceptionMethod()")
    public void doCatchExceptionMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            joinPoint.proceed();
        } catch (Exception e) {
            LogUtils.e(TAG, getException(e));
        }
    }

    private String getException(Throwable ex) {
        StringWriter errors = new StringWriter();
        ex.printStackTrace(new PrintWriter(errors));
        return errors.toString();
    }
}
  1. 使用方法
@CatchException
    public void catchException(View view) {
        String s = null;
        s.toString();
    }

8. Hook方法

  1. 功能
    可以在执行某个方法的前和后,执行另外指定的方法。
  2. annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface HookMethod {
    String beforeMethod();

    String afterMethod();
}
  1. aspectj
@Aspect
public class HookMethodAspect {

    private final String TAG = "HookMethodAspect";
    private final String POINTCUT = "execution(@com.androidwind.quickaop.library.annotation.HookMethod * *(..))";

    @Pointcut(POINTCUT)
    public void onHookMethodMethod() {
    }

    @Around("onHookMethodMethod()")
    public void doHookMethodMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        HookMethod hookMethod = method.getAnnotation(HookMethod.class);

        if (hookMethod == null) {
            return;
        }

        String beforeMethod = hookMethod.beforeMethod();
        String afterMethod = hookMethod.afterMethod();

        if (!StringUtils.isEmpty(beforeMethod)) {
            try {
                ReflectUtils.reflect(joinPoint.getTarget()).method(beforeMethod);
            } catch (ReflectUtils.ReflectException e) {
                e.printStackTrace();
                Log.e(TAG, "no method " + beforeMethod);
            }
        }

        joinPoint.proceed();

        if (!StringUtils.isEmpty(afterMethod)) {
            try {
                ReflectUtils.reflect(joinPoint.getTarget()).method(afterMethod);
            } catch (ReflectUtils.ReflectException e) {
                e.printStackTrace();
                Log.e(TAG, "no method " + afterMethod);
            }
        }
    }
}
  1. 使用方法
@HookMethod(beforeMethod = "beforeMethod", afterMethod = "afterMethod")
    public void hookMethod(View view) {
        Log.i(TAG, "this is a hookMethod");
    }

    private void beforeMethod() {
        Log.i(TAG, "this is a before method");
    }

    private void afterMethod() {
        Log.i(TAG, "this is an after method");
    }

9. 缓存

  1. 功能
    将方法执行结果保存到缓存中,这里使用sharedpreferences。
  2. annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cache {

    String key();
}
  1. aspectj
@Aspect
public class CacheAspect {
    private static final String POINTCUT_METHOD = "execution(@com.androidwind.quickaop.library.annotation.Cache * *(..))";

    @Pointcut(POINTCUT_METHOD)
    public void onCacheMethod() {
    }

    @Around("onCacheMethod() && @annotation(cache)")
    public Object doCacheMethod(ProceedingJoinPoint joinPoint, Cache cache) throws Throwable {
        String key = cache.key();

        Object result = joinPoint.proceed();
        if (result instanceof String) {
            SPUtils.getInstance().put(key, (String)result);
        }

        return result;
    }
}
  1. 使用方法
@Cache(key = "name")
    public String cache(View view) {
        return "Jerry";
    }

10. Null检查

  1. 功能
    对入参进行null检测。
  2. annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NullCheck {
    int position() default 0;//the input params position
}
  1. aspectj
@Aspect
public class NullCheckAspect {

    private final String TAG = "NullCheckAspect";
    private final String POINTCUT = "execution(@com.androidwind.quickaop.library.annotation.NullCheck * *(..))";

    @Pointcut(POINTCUT)
    public void onNullCheckMethod() {
    }

    @Around("onNullCheckMethod() && @annotation(nullCheck)")
    public void doNullCheckMethod(ProceedingJoinPoint joinPoint, NullCheck nullCheck) throws Throwable {
        int position = nullCheck.position();
        Object[] objects = joinPoint.getArgs();
        if (objects.length > 0 && position < objects.length) {
            if (!ObjectUtils.isEmpty(objects[position])) {
                joinPoint.proceed();
            }
        }
    }
}
  1. 使用方法
public void nullCheck(View view) {
        getString("Tommy", null);
    }

    @NullCheck(position = 1)
    private void getString(String name, String country) {
        Log.i(TAG, "this is after nullcheck input");
    }

11. view的控制

  1. 功能
    Android UI初始化完毕后,对View的操作,这里以显示一个View布局为例。
  2. annotation
    这里需要注意ElementType选择的是TYPE,因为作用的是类。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface AddView {
}
  1. aspectj
@Aspect
public class AddViewAspect {
    private final String TAG = "AddViewAspect";
    private final String POINTCUT = "execution(* android.app.Activity.onCreate(..)) && within(@com.androidwind.quickaop.annotation.AddView *)";

    @After(POINTCUT)
    public void addView(JoinPoint joinPoint) throws Throwable {
        FragmentActivity activity = null;
        String signatureStr = joinPoint.getSignature().toString();
        Log.d(TAG, "[addView]" + signatureStr);
        final Object object = joinPoint.getThis();
        if (object instanceof FragmentActivity) {
            activity = (FragmentActivity) object;
            TextView tv = activity.findViewById(R.id.view);
            tv.setVisibility(View.VISIBLE);
        }
    }
  }
  1. 使用方法
@AddView
public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "[addView]" + "MainActivity->onCreate");
        System.out.println("[Thread Name-MainActivity: ]" + Thread.currentThread().getName());
    }
 }

12. 更改Toast信息

  1. 功能
    这里我们实现拦截一个Toast信息,并且更改Toast里面的内容。
    这里主要用到了aspectj的call方法。
    call是在方法调用处织入代码,execution是在方法内部织入代码。
  2. annotation
    ElementType选择的也是TYPE。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface AddView {
}
  1. aspectj
@Aspect
public class AddViewAspect {

    private final String TAG = "AddViewAspect";
    private final String POINTCUT = "execution(* android.app.Activity.onCreate(..)) && within(@com.androidwind.quickaop.annotation.AddView *)";
    
    @Before("call(* android.widget.Toast.show())")
    public void changeToast(JoinPoint joinPoint) throws Throwable {
        Toast toast = (Toast) joinPoint.getTarget();
        toast.setText("修改后的toast");
        Log.d(TAG, "[changeToast]");
    }
}
  1. 使用方法
    在一个Activity类中,这个类有AddView注释修饰。
public void toast(View view) {
        Toast.makeText(this,"原始的toast",Toast.LENGTH_SHORT).show();
    }

AOP开发问题总结

https://blog.csdn.net/ddnosh/article/details/103988614

github地址

实例代码参考工程:
https://github.com/ddnosh/QuickAOP

猜你喜欢

转载自blog.csdn.net/ddnosh/article/details/104146242