Android AOP框架AspectJ使用详解
作者:三十二蝉
前言
之前了解过android的AOP框架,用法主要用来打日志;现在有一个需求需要函数在新线程中执行,并且函数主体执行完之后,在UI线程返回结果。想到手写的话,每次都要new Thread的操作,比较麻烦;因此就尝试用注解的方法解决这个问题。
AspectJ的使用核心就是它的编译器,它就做了一件事,将AspectJ的代码在编译期插入目标程序当中,运行时跟在其它地方没什么两样,因此要使用它最关键的就是使用它的编译器去编译代码ajc。ajc会构建目标程序与AspectJ代码的联系,在编译期将AspectJ代码插入被切出的PointCut中,已达到AOP的目的。
因此,无论在什么IDE上(如果使用命令行就可以直接使用ajc编译了),问题就是让IDE使用ajc作为编译器编译代码。
代码实现
注解使用
代码主要通过TraceLog、RunOnNewThread、RunOnNewThreadWithUICallback这三个注解与AOP容器关联。使用方法如下:
@TraceLog @RunOnNewThread public void checkAndRestartDownloadTask(final boolean isAutoCache) { DownloadManager.getInstance().startService(isAutoCache); } @TraceLog @RunOnNewThreadWithUICallback public Boolean isShowTipsForFirstVideoCache(DBQueryCallback<Boolean> callback) { if (!PreferenceClient.is_first_video_cache_done.getBoolean() && (DownloadManager.getInstance().getFinishedTaskSize(true, false) > 0 || DownloadManager.getInstance().getFinishedTaskSize(true, true) > 0)) { PreferenceClient.is_first_video_cache_done.setBoolean(true); return true; } return false; }
checkAndRestartDownloadTask方法,希望方法体在一个新的线程执行并打印方法执行的Log;isShowTipsForFirstVideoCache方法,希望方法体在一个新的线程执行,并将函数的结果通过DBQueryCallback这个回调回传给UI线程,同时打印方法执行的Log。
AOP容器识别这三个注解,并实现注解解释器。
@Aspect public class TudouDownloadAspect { public static final String TAG = TudouDownloadAspect.class.getSimpleName(); private static final String THREAD_CALLBACK_POINT_METHOD = "execution(@com.download.common.aspect.RunOnNewThreadWithUICallback * *(.., com.download.common.callback.DBQueryCallback))"; private static final String THREAD_CALLBACK_POINT_CONSTRUCTOR = "execution(@com.download.common.aspect.RunOnNewThreadWithUICallback *.new(.., com.download.common.callback.DBQueryCallback))"; private static final String THREAD_POINT_METHOD = "execution(@com.download.common.aspect.RunOnNewThread * *(..))"; private static final String THREAD_POINT_CONSTRUCTOR = "execution(@com.download.common.aspect.RunOnNewThread *.new(..))"; private static final String LOG_POINT_METHOD = "execution(@com.download.common.aspect.TraceLog * *(..))"; private static final String LOG_POINT_CONSTRUCTOR = "execution(@com.download.common.aspect.TraceLog *.new(..))"; @Pointcut(THREAD_CALLBACK_POINT_METHOD) public void methodAnnotatedWithThread(){} @Pointcut(THREAD_CALLBACK_POINT_CONSTRUCTOR) public void constructorAnnotatedWithThread(){} @Pointcut(THREAD_POINT_METHOD) public void methodAnnotatedWithNewThread(){} @Pointcut(THREAD_POINT_CONSTRUCTOR) public void constructorAnnotatedWithNewThread(){} @Pointcut(LOG_POINT_METHOD) public void methodAnnotatedWithLog(){} @Pointcut(LOG_POINT_CONSTRUCTOR) public void constructorAnnotatedWithLog(){} /** * @RunOnNewThreadWithUICallback 的注解解释器 * */ @Around("methodAnnotatedWithThread() || constructorAnnotatedWithThread()") public Object wrapNewThreadWithCallback(final ProceedingJoinPoint joinPoint) throws Throwable { Log.v(TAG, "in wrapNewThreadWithCallback"); Object[] objs = joinPoint.getArgs(); final DBQueryCallback callback = (DBQueryCallback) objs[objs.length-1]; new Thread(new Runnable() { @Override public void run() { try { final Object obj = joinPoint.proceed(); DownloadClient.getInstance().mainHandler.post(new Runnable() { @Override public void run() { if (obj != null) callback.querySuccess(obj); else callback.queryFail(); } }); } catch (Throwable throwable) { throwable.printStackTrace(); } } }).start(); return null; } /** * @RunOnNewThread 的注解解释器 * */ @Around("methodAnnotatedWithNewThread() || constructorAnnotatedWithNewThread()") public void wrapNewThread(final ProceedingJoinPoint joinPoint) throws Throwable { Log.v(TAG, "in wrapNewThread"); new Thread(new Runnable() { @Override public void run() { try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } } }).start(); } /** * @TraceLog 的注解解释器 * */ @Before("methodAnnotatedWithLog() || constructorAnnotatedWithLog()") public void wrapWithLog(JoinPoint joinPoint) throws Throwable { Log.v(TAG, "before->" + joinPoint.getTarget().toString() + "---" + joinPoint.getSignature().getName()); } }
- @Aspect:声明一个AOP容器
- @Pointcut:声明一个切入点
- @Around:将函数主体包裹起来,在函数主体前、后插入代码
- @Before:在函数主体执行之前插入代码
使用Gradle脚本加载AOP容器
buildscript { repositories { mavenLocal() maven { url "https://jitpack.io" } } dependencies { classpath 'org.aspectj:aspectjtools:1.8.+' //AspectJ脚本依赖 } } dependencies { compile 'org.aspectj:aspectjrt:1.8.+' //AspectJ 代码依赖 } //AspectJ AOP容器加载脚本 final def log = project.logger final def variants = project.android.libraryVariants variants.all { variant -> JavaCompile javaCompile = variant.javaCompile javaCompile.doLast { String[] args = ["-showWeaveInfo", "-1.5", "-inpath", javaCompile.destinationDir.toString(), "-aspectpath", javaCompile.classpath.asPath, "-d", javaCompile.destinationDir.toString(), "-classpath", javaCompile.classpath.asPath, "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)] log.debug "ajc args: " + Arrays.toString(args) MessageHandler handler = new MessageHandler(true); new Main().run(args, handler); for (IMessage message : handler.getMessages(null, true)) { switch (message.getKind()) { case IMessage.ABORT: case IMessage.ERROR: case IMessage.FAIL: log.error message.message, message.thrown break; case IMessage.WARNING: log.warn message.message, message.thrown break; case IMessage.INFO: log.info message.message, message.thrown break; case IMessage.DEBUG: log.debug message.message, message.thrown break; } } } }
备注
@RunOnNewThreadWithUICallback这个注解的匹配规则需要函数的最后一个参数为DBQueryCallback(必须要有一个回调参数,不然怎么回传给UI线程~)。函数的返回值必须和DBQueryCallback的泛型类型一致,因为需要将返回值传入回调当中;
new Thread(new Runnable() { @Override public void run() { try { final Object obj = joinPoint.proceed(); DownloadClient.getInstance().mainHandler.post(new Runnable() { @Override public void run() { if (obj != null) callback.querySuccess(obj); else callback.queryFail(); } }); } catch (Throwable throwable) { throwable.printStackTrace(); } } }).start();
注意final Object obj = joinPoint.proceed();,执行了函数体以后,我们默认取到的是一个Object类型的返回值,所以不能用基本数据类型(bool用Boolean,int用Interger)。还有一点,Java中的null是可以转化为任意类型的,所以就算在函数体直接返回null,执行final Object obj = joinPoint.proceed();,这个类型转化也是不会有问题。亲测有效,可以放心使用
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。