公司打算開發一款全新的To C產品,所以我開始作一些搭建框架的事兒以及POC。新的產品可以使用一些比較新的技術,在新產品中我大量使用了Rx。這就致使了原先的AOP框架在某些場景下是沒法使用的,藉此機會我順便升級了一下原先的AOP框架。java
回顧一下以前寫過的一篇文章概括AOP在Android開發中的幾種常見用法
AOP框架地址:github.com/fengzhizi71…git
在以前的文章中講過@Trace,它能追蹤某個方法花費的時間。若是想這樣追蹤匿名內部類花費的時間,原先的代碼是沒法使用的,只能追蹤到initData()花費的時間。github
@Trace
private void initData() {
Observable.create(new ObservableOnSubscribe<String>() {
@Trace
@Override
public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {
e.onNext("111");
e.onNext("222");
e.onNext("333");
}
}).subscribe(new Consumer<String>() {
@Trace
@Override
public void accept(@NonNull String str) throws Exception {
}
});
}複製代碼
改了一下TraceAspectapp
package com.safframework.aop;
import com.safframework.aop.annotation.Trace;
import com.safframework.log.L;
import com.safframework.tony.common.utils.Preconditions;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
/** * Created by Tony Shen on 16/3/22. */
@Aspect
public class TraceAspect {
private static final String POINTCUT_METHOD = "execution(@com.safframework.aop.annotation.Trace * *(..))";
private static final String POINTCUT_CONSTRUCTOR = "execution(@com.safframework.aop.annotation.Trace *.new(..))";
@Pointcut(POINTCUT_METHOD)
public void methodAnnotatedWithTrace() {
}
@Pointcut(POINTCUT_CONSTRUCTOR)
public void constructorAnnotatedTrace() {
}
@Around("methodAnnotatedWithTrace() || constructorAnnotatedTrace()")
public Object traceMethod(final ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Trace trace = methodSignature.getMethod().getAnnotation(Trace.class);
if (!trace.enable()) {
return joinPoint.proceed();
}
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();
final StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
if (Preconditions.isBlank(className)) {
className = "Anonymous class";
}
L.i(className, buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));
return result;
}
/** * Create a log message. * * @param methodName A string with the method name. * @param methodDuration Duration of the method in milliseconds. * @return A string representing message. */
private static String buildLogMessage(String methodName, long methodDuration) {
StringBuilder message = new StringBuilder();
message.append(methodName);
message.append("()");
message.append(" take ");
message.append("[");
message.append(methodDuration);
message.append("ms");
message.append("]");
return message.toString();
}
}複製代碼
在這裏,
@Pointcut 表示攔截的切入點方法,是方法級別之上的註解,可是不執行方法體,只表示切入點的入口。框架
@Around 用於判斷是否執行以上的攔截。ide
這樣,原先的代碼就能work了,在建立Observable時發射了三次,因此在subscribe時也接收到三次accept(),符合預期。
優化
@HookMethod在以前的文章中也講到過,是比較經典的AOP使用方式,能在方法執行先後進行hook。ui
我一樣也修改了HookMethodAspect,使得@HookMethod也可以在匿名內部類中使用,知足相關的業務場景。this
RxView.clicks(holder.imageView)
.compose(RxJavaUtils.preventDuplicateClicksTransformer())
.subscribe(new Consumer<Object>() {
@HookMethod(beforeMethod = "saveContentToDB")
@Override
public void accept(@NonNull Object o) throws Exception {
Intent intent = new Intent(mContext, ContentDetailActivity.class);
intent.putExtra("content_item",item);
intent.putExtra("page_start_time",new DateTime());
mContext.startActivity(intent);
}
private void saveContentToDB() {
if (User.currentUser().isLoggedIn()){
App.getInstance().queue.addOperation(new Operation() {
@Override
public void run(Queue queue, Bundle bundle) {
DBUtils.insertContent(item);
}
});
}
}
});複製代碼
須要注意的是,目前beforeMethod、afterMethod所對應的方法只能位於所在類中。可能,將來會針對這一塊作一些優化。spa
因爲跟業務結合緊密,@CheckLogin並不在框架中。可是在項目中使用它徹底沒問題。
首先,定義註解
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
/** * Created by Tony Shen on 2017/7/26. */
@Target({METHOD, CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface CheckLogin {
}複製代碼
再定義一個Aspect
/** * Created by Tony Shen on 2017/7/26. */
@Aspect
public class CheckLoginAspect {
private static final String POINTCUT_METHOD = "execution(@cn.magicwindow.toutiao.aop.CheckLogin * *(..))";
private static final String POINTCUT_CONSTRUCTOR = "execution(@cn.magicwindow.toutiao.aop.CheckLogin *.new(..))";
@Pointcut(POINTCUT_METHOD)
public void methodAnnotatedWithCheckLogin() {
}
@Pointcut(POINTCUT_CONSTRUCTOR)
public void constructorAnnotatedCheckLogin() {
}
@Around("methodAnnotatedWithCheckLogin() || constructorAnnotatedCheckLogin()")
public void checkLogin(final ProceedingJoinPoint joinPoint) throws Throwable {
if (!User.currentUser().isLoggedIn()) {
Router.getInstance().open("login");
return;
}
joinPoint.proceed();
}
}複製代碼
@CheckLogin能夠直接在匿名內部類中使用,它會先判斷用戶是否登陸,若是沒有登陸就會跳轉到登陸頁面。若是已經登陸,則處理後面的業務邏輯。
RxView.clicks(favorite)
.compose(RxJavaUtils.preventDuplicateClicksTransformer())
.compose(RxLifecycle.bind(ContentDetailActivity.this).toLifecycleTransformer())
.subscribe(new Consumer<Object>() {
@CheckLogin
@Override
public void accept(@NonNull Object o) throws Exception {
......
}
});複製代碼
@CheckLogin 也能夠結合相似butterknife這樣的框架使用。
@CheckLogin
@OnClick(id=R.id.button_favorite)
void onClickFav() {
......
}複製代碼
本文有一點小小的標題黨,其實我所作的修改並不單單是爲了RxJava。可是RxJava已經變得愈來愈流行,AOP也可以跟Rx很好地相結合。本文除了記錄工做中我的所使用的一些東西,也但願可以起到一些拋磚引玉的做用。