背景最近我们收到一些运营需求,需要嵌入一些数据。当我们第一次收到需求时,只有一个解决方案。问题在于向每个页面添加监控并添加我们想要的任何业务数据。巨大的工作量和超强的人脉关系,想想都让人难以承受。我忍不住为我的头发感到难过。然后我了解到面向方面的编程是可能的。接下来是一个关于实际使用Aspectj 的故事。
实际操作参考第一步的项目配置,将其添加到项目的build.gradle中的依赖中。
步骤2:在APP的build.gradle依赖中引入以下代码。
实现“org.aspectj:aspectjrt:1.8.9”
* 第三步是将其添加到应用程序的build.gradle中的android目录中。
import org.aspectj.bridge.IMessageimport org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Mainfinal def log=project.loggerandroid.applicationVariants.all {variant -//if (!variant.buildType.isDebuggable()) {//log.debug('Skipping non-debuggable build type '${variant.buildType.name}'.')//return//}//此代码导致release javaCompile=ariant.javaCompilerjavaCompile JavaCompile 失败。 doLast {String[] args=['-showWeaveInfo','-1.8','-inpath', javaCompile.destinationDir.toString(),'-aspectpath', javaCompile.classpath.asPath,'-d', javaCompile 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 。错误message.message, message. thrownbreakcase IMessage.WARNING:log.warn message.message, message. thrownbreakcase IMessage.INFO:log.info message.message, message. thrownbreakcase IMessage.DEBUG:log.debug message.message, message. thrownbreak}}}
上面这段是从网上复制的别人的文章,当时有一个bug,导致我无法成功插入官方包部分的代码。这段代码是后来才发现的。我成功注释掉了下面的代码。
if (!variant.buildType.isDebuggable()) {log.debug('跳过不可调试的构建类型'${variant.buildType.name}'.')return}
至此,AOP配置完成。然后根据你的需求贴出你已经完成的相关业务代码。
1. 统计页面停留时间。
这里的统计采用极光推送统计。官方文档要求在每个页面生命周期的开头和结尾添加两句话。无论如何,如果将其添加到每个页面,它将如下所示:工作量……很难说。我们来看看切面是如何实现页面监控的。
执行代码
@Aspectpublic class TraceAspect {private static Final String TAG='aop_page_trace';@After('execution(* android.app.Activity.onResume(.)) )')public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {Signature Signature=joinPoint.getSignature();System.out.println(TAG + '方面点执行开始上下文==' + (Context) joinPoint.getThis());System.out.println(TAG + '方面点执行开始类名' +signature.getDeclaringType().getCanonicalName());JAnalyticsInterface.onPageStart((Context) joinPoint.getThis(),signature.getDeclaringType().getCanonicalName());}@After('execution(* android.app.Activity. onPause(.)) ')public void onActivityMethodDestory(JoinPoint joinPoint) throws Throwable {Signature Signature=joinPoint.getSignature();JAnalyticsInterface.onPageEnd((Context) joinPoint.getThis(), signal.getDeclaringType().getCanonicalName( )) ;System.out.println(TAG +'Aspect点执行销毁上下文==' + (Context) joinPoint.getThis());System.out.println(TAG +'Aspect点执行销毁类名' +signature.getDeclaringType( ).getCanonicalName());}
执行通常指定方法的执行。没有将来的注释或权限限制,因此将返回值替换为* 以表示可以使用任何返回值。 on*代表函数名,下面的模型表示on后面可以连接任何内容,后面的(.)表示参数可以是任意值。
@After指的是在jPoint()之后执行。该通配符通常用于避免影响先前代码的执行。
2.与按钮点击和页面跳转相关的隐藏点
我们先来说说想法。 我这里使用了注释。第一步是自定义注释,第二步是在必要时引用当前注释。第三步是找到方面检查引用注释的切入点。第四步是在切点处添加必要的逻辑代码。
自定义注解的代码如下
/***点击时间嵌入点标注2019-10-30*/@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.FIELD})public @interface ClickAnnotate {int type() default 0; /To找到切入点后方便业务决策,我们根据需求定义了类型参数进行标记。监控点击或跳转的切面代码如下@Aspectpublic class ClickAspect {private staticfinal String TAG='aop_click' ; private String Intention=IntentionAty .class.getCanonicalName();@Pointcut('execution(@com.aoptest.annotation. ClickAnnotate * *(.))')public void clickP() {}@After('clickP() ')public void insertCodeBlock( JoinPoint joinPoint) throws Throwable {MethodSignature methodSignature=((MethodSignature) joinPoint.getSignature ()); Method method=methodSignature.getMethod();if (method.isAnnotationPresent(ClickAnnotate.class)) {ClickAnnotate clickAnnotate=Method.getAnnotation(ClickAnnotate.class);int type=clickAnnotate.type();switch (type) {case 常量。 AOP_HOME_MENNU://这个常量是自定义字符串menCode='';if (joinPoint.getArgs() !=null joinPoint.getArgs() .length 0) {if (joinPoint.getArgs()[0]instanceof String) {menCode=(String) joinPoint.getArgs()[0];}}//joinPoint.getArgs()是一个参数无论在哪里进入该方法,一定要注意数组越界问题if ( TextUtils.isEmpty(menCode) ) return;switch (menCode) {case 'MENU_HOME_HYGL':Log.i(TAG, 'insertCodeBlock: 点击了会员管理');break;case 'MENU_HOME_YXDD':Log.i(TAG, '按照我点击的顺序插入CodeBlock: ');break ;}break;}}} 此时,我们需要对方面做的所有事情都已完成。下一步是调用注释来指示代码需要剪切的位置。
/**** @param menuCode 菜单对应的代码*/@ClickAnnotate(type=Constants.AOP_HOME_MENNU) //这里的常量对应上面的方面private void setMenuJump(String menuCode) {Class className=null ;if ( menuCode.equals('MENU_HOME_YXDD')) {className=IntentionAty.class;}if (className !=null) {startActivity(new Intent(mContext, className));}}调用上述步骤后,会检查切面。调用@ClickAnnotate注解All方法,找到切入点,插入相关代码。由于我们在这里考虑了重用,因此我们在注释中定义了类型来区分它们。
总结
项目内。使用混淆时,请注意不要混淆注释。如果没有混淆,该方面将不会执行。考虑到这个需求后,我们发现AOP在显着降低代码耦合性、显着提高代码可维护性方面做得非常出色。有关如何编写AOP 相关注解和表达式的信息,请参阅AOP 常用注解和概念(https://www.codercto.com/a/23534.html)。相关概念已经解释到这里了,我就不照搬了。这里。