我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版,欢迎购买。点击进入详情
文章目录
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/39504896af4d030b30f39753602dee50.png)
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. 添加日志
- 功能:
计算方法执行的时间并打印出来。 - annotation
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD})
public @interface InsertLog {
}
- 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)));//换算成毫秒
}
}
- 使用方法
@InsertLog
public void insertLog(View view) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, "this is an insert log.");
}
2. 防止快速点击
- 功能
防止段时间内对view控件的多次点击,这里可以设置时间间隔,即多长时间内不能多次点击。 - annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SingleClick {
long value() default 1000L;
int[] ids() default {View.NO_ID}; //支持多个值
}
- 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;
}
}
- 使用方法
@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. 登录态检测
- 功能
检测当前是否已经登录,如果登录继续执行,如果没登录则提示。 - annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface CheckLogin {
}
- 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();
}
}
}
- 使用方法
@CheckLogin
public void checkLogin(View view) {
Log.i(TAG, "this is an operation after login.");
}
4. 请求权限
- 功能
在执行方法前检测app是否已经获得了相应的权限。
这里引入RxPermissions作为权限检测工具。 - annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RequirePermission {
String[] value();
}
- 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();
}
}
});
}
}
}
- 使用方法
@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. 埋点
- 功能
添加埋点信息,用于数据统计功能。 - annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface EventTracking {
String key();
String value();
}
- 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();
}
}
- 使用方法
@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. 异步执行
- 功能
保证方法是通过异步方式执行,这里使用RxJava实现异步。 - annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Asynchronize {
}
- 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();
}
}
- 使用方法
@Asynchronize
public void asynchronize(View view) {
System.out.println("[Thread Name-asynchronize: ]" + Thread.currentThread().getName());
}
7. 异常捕获
- 功能
捕获此方法所可能产生的异常情况,保证执行此方法不会导致app崩溃。 - annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface CatchException {
}
- 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();
}
}
- 使用方法
@CatchException
public void catchException(View view) {
String s = null;
s.toString();
}
8. Hook方法
- 功能
可以在执行某个方法的前和后,执行另外指定的方法。 - annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface HookMethod {
String beforeMethod();
String afterMethod();
}
- 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);
}
}
}
}
- 使用方法
@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. 缓存
- 功能
将方法执行结果保存到缓存中,这里使用sharedpreferences。 - annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cache {
String key();
}
- 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;
}
}
- 使用方法
@Cache(key = "name")
public String cache(View view) {
return "Jerry";
}
10. Null检查
- 功能
对入参进行null检测。 - annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NullCheck {
int position() default 0;//the input params position
}
- 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();
}
}
}
}
- 使用方法
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的控制
- 功能
Android UI初始化完毕后,对View的操作,这里以显示一个View布局为例。 - annotation
这里需要注意ElementType选择的是TYPE,因为作用的是类。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface AddView {
}
- 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);
}
}
}
- 使用方法
@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信息
- 功能
这里我们实现拦截一个Toast信息,并且更改Toast里面的内容。
这里主要用到了aspectj的call方法。
call是在方法调用处织入代码,execution是在方法内部织入代码。 - annotation
ElementType选择的也是TYPE。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface AddView {
}
- 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]");
}
}
- 使用方法
在一个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