• 一AOP

    • 1 主要功能
    • 2 主要目标
    • 3 适用对象
    • 4 AOP与OOP的关系
  • 二Android中使用AspectJ
    • 1 Gradle 配置示
    • 2 基本概念
      • 21 切面Aspect
      • 22 连接点JoinPoint
      • 23 切点PointCut
      • 24 通知Advise
    • 3 执原
      • 31 BeforeAfterAfterThrowing插入示意图
      • 32 Around替换逻辑示意图
      • 33 代码分析
    • 4 AspectJ切面编写
      • 41 日志打印
      • 42 耗时监控
      • 43 异常处
      • 44 降级替代方案吐司
      • 45 其他的系统横切关注点问题
  • 三相关问题
    • 1 编织速度
    • 2 调试工具

一、AOP

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑
各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

1.1 主要功能

日志记录,性能统计,安全控制,事务处理,异常处理等等。

1.2 主要目标

将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变
这些行为的时候不影响业务逻辑的代码。

1.3 适用对象

比较大型的项目,而且迭代较快,使用OOP太消耗内力。
有日志、性能、安全、异常处理等横切关注点需求。

1.4 AOP与OOP的关系

OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。但是也有它的缺点,最明显的就是关注点聚焦时,面向对象无法简单的解决这个问题,一个关注点是面向所有而不是单一的类,不受类的边界的约束,因此OOP无法将关注点聚焦来解决,只能分散到各个类中。
AOP(面向切面编程)则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
AOP并不是与OOP对立的,而是为了弥补OOP的不足。OOP解决了竖向的问题,AOP则解决横向的问题。因为有了AOP我们的调试和监控就变得简单清晰。它们之间的关系如下图所示:

1.4.1 对比一——单一横切关注点

1.4.2 对比二——多横切关注点

结论:


二、Android中使用@AspectJ

AspectJ 意思就是Java的Aspect,Java的AOP。它其实不是一个新的语言,它的核心是ajc(编译器)\weaver(织入器)。

  • ajc编译器:基于Java编译器之上的,它是用来编译.aj文件,aspectj在Java编译器的基础上增加了一些它自己的关键字和方法。因此,ajc也可以编译Java代码。
  • weaver织入器:为了在java编译器上使用AspectJ而不依赖于Ajc编译器,aspectJ 5出现了@AspectJ,使用注释的方式编写AspectJ代码,可以在任何Java编译器上使用。

由于AndroidStudio默认是没有ajc编译器的,所以在Android中使用@AspectJ来编写(包括SpringAOP也是如此)。它在代码的编译期间扫描目标程序,根据切点(PointCut)匹配,将开发者编写的Aspect程序编织(Weave)到目标程序的.class文件中,对目标程序作了重构(重构单位是JoinPoint),目的就是建立目标程序与Aspect程序的连接(获得执行的对象、方法、参数等上下文信息),从而达到AOP的目的。

2.1 Gradle 配置示例

要引入AspectJ到Android工程中,最重要的就是两个包:

//在buildscript中添加该编织器,gradle构建时就会对class文件进行编织
classpath 'org.aspectj:aspectjweaver:1.8.9'
//在dependencies中添加该依赖,提供@AspectJ语法
compile 'org.aspectj:aspectjrt:1.8.9'

此外还有一个工具包,用于Gradle构建时进行打日志等操作:

//在buildscript中添加该工具包,在构建工程的时候执行一些任务:打日志等
classpath 'org.aspectj:aspectjtools:1.8.9'import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main//打印gradle日志
android.libraryVariants.all { variant ­>
LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)JavaCompile javaCompile = variant.javaCompilejavaCompile.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)]MessageHandler handler = new MessageHandler(true);new Main().run(args, handler)def log = project.loggerfor (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}
}
}

附:美团RoboAspectJ

buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath 'com.meituan.gradle:roboaspectj:0.9.2'
classpath 'jaop.gradle.plugin:gradle­plugin:1.0.2'
}
// Exclude the version that the android plugin depends on.
configurations.classpath.exclude group: 'com.android.tools.external.lombok'
}

配置参数

// AspectJ
aspectj {
disableWhenDebug true
javartNeeded true
// 排除不需要AOP扫描的包
exclude group: 'xxxx', module: 'xxxx'
compileOptions {
defaultJavaVersion = JavaVersion.VERSION_1_7
}
}

2.2 基本概念

2.2.1 切面——Aspect

实现了cross­cutting功能,是针对切面的模块。最常见的是logging模块、方法执行耗时模块,这样,程序按功能被分为好几层,如果按传统的继承的话,商业模型继承日志模块的话需要插入修改的地方太多,而通过创建一个切面就可以使用AOP来实现相同的功能了,我们可以针对不同的需求做出不同的切面。

2.2.2 连接点——JoinPoint

连接点是切面插入应用程序的地方,该点能被方法调用,而且也会被抛出意外。连接点是应用程序提供给切面插入的地方,可以添加新的方法。比如:我们的切点可以认为是findInfo(String)方法。
AspectJ将面向对象的程序执行流程看成是JoinPoint的执行链,每一个JoinPoint是一个单独的闭包,在执行的时候将上下文环境赋予闭包执行方法体逻辑。
下面列表上的是被AspectJ认为是joinpoint的:

2.2.3 切点——PointCut

切点的声明决定需要切割的JoinPoint的集合,就结果上来说,它是JoinPoint的一个实际子集合。
pointcut可以控制你把哪些advice应用于jointpoint上去,通常通过正则表达式来进行匹配应用,决定了那个jointpoint会获得通知。分为call、execution、target、this、within等关键字,含义下面会附图。
1.直接针对JoinPoint的选择
pointcuts中最常用的选择条件和Joinpoint的类型密切相关,比如图5:

2.间接针对JPoint的选择
除了根据前面提到的Signature信息来匹配JPoint外,AspectJ还提供其他一些选择方法来选择JPoint。比如某个类中的所有JPoint,每一个函数执行流程中所包含的JPoint。
特别强调,不论什么选择方法,最终都是为了找到目标的JPoint。
表2列出了一些常用的非JPoint选择方法:

3.匹配规则
(1)类型匹配语法
首先让我们来了解下AspectJ类型匹配的通配符:

*:匹配任何数量字符;
..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。

(2)匹配模式
call(<注解?> <修饰符?> <返回值类型> <类型声明?>.<方法名>(参数列表) <异常列表>?)

  • 精确匹配
//表示匹配 com.davidkuper.MainActivity类中所有被@Describe注解的public void方法。
@Pointcut("call(@Describe public void com.davidkuper.MainActivity.init(Context))")
public void pointCut(){}
  • 单一模糊匹配
//表示匹配 com.davidkuper.MainActivity类中所有被@Describe注解的public void方法。
@Pointcut("call(@Describe public void com.davidkuper.MainActivity.*(..)) ")
public void pointCut(){}
//表示匹配调用Toast及其子类调用的show方法,不论返回类型以及参数列表,并且该子类在以com.meituan或者com.sankuai开头的包名内
@Pointcut("call(* android.widget.Toast+.show(..)) && (within(com.meituan..*)|| within(com.sankuai..*))")
public void toastShow() {
}
  • 组合模糊匹配
//表示匹配任意Activity或者其子类的onStart方法执行,不论返回类型以及参数列表,且该类在com.meituan.hotel.roadmap包名内
@Pointcut("execution(* *..Activity+.onStart(..))&& within(com.meituan.hotel.roadmap.*)")
public void onStart(){}

(3)获取参数

  • 通过声明参数语法arg()显示获取参数
@Around(value = "execution(* BitmapFacade.picasso.init(java.lang.String,java.lang.String)) && args(arg1,arg2)"
public Object aroundArgs(String arg1,String arg2,ProceedingJoinPoint joinPoint){System.out.println("aspects arg = " + arg1.toString()+" " + arg2);Object resutObject = null;try {resutObject = joinPoint.proceed(new Object[]{arg1,arg2});} catch (Throwable e) {e.printStackTrace();}return resutObject;
}
  • 通过joinPoint.getArg()获取参数列表
@Around("execution(static * tBitmapFacade.picasso.init(..)) && !within(aspectj.*) ")
public void pointCutAround(ProceedingJoinPoint joinPoint){Object resutObject = null;try {//获取参数列表Object[] args = joinPoint.getArgs();resutObject = joinPoint.proceed(args);} catch (Throwable e) {e.printStackTrace();}return resutObject;
};

(4)异常匹配

/**
* 截获Exception及其子类报出的异常。
* @param e 异常参数
*/
@Pointcut("handler(java.lang.Exception+)&&args(e)")
public void handle(Exception e) {}

2.2.4 通知——Advise

advice是我们切面功能的实现,它是切点的真正执行的地方。比如像写日志到一个文件中,会在pointcut匹配到的连接点中插入advice(包括:before、after、around等)代码到应用程序中。
(1)@Before、@After

//所有实例方法调用截获
private static final String INSTANCE_METHOD_CALL =
"call(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)";
@Pointcut(INSTANCE_METHOD_CALL) public void instanceMethodCall() {
}
//实例方法调用前后Advice
@Before("instanceMethodCall()") public void beforInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "before instance call");
}
@After("instanceMethodCall()") public void afterInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "after instance call");
}

(2)@Around

//横切项目中所有Activity的子类,以Layout命名、以及它的子类的所有方法的执行
private static final String POINTCUT_METHOD =
"(execution(* android.app.Activity+.*(..)) ||execution(* *..Layout+.*(..)))&& within(com.meituan.hotel.roadmap.*)";
@Pointcut(POINTCUT_METHOD) public void methodAnnotated() {
}@Around("methodAnnotated()") public Object weaveJoinPoint(ProceedingJoinPoint joinPoint)throws Throwable{//调用原方法的执行。Object result = joinPoint.proceed();return result;
}

(3)@AfterThrowing

/**
* 在异常抛出后,该操作优先于下一个切点的@Before()
* @param joinPoint
* @param e 异常参数
*/
@AfterThrowing(pointcut = "afterThrow()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e){
Log.e(TAG,joinPoint.getTarget().getClass().getSimpleName() + " afterThrowing() :" + e.toString());
}

2.3 执行原理

AspectJ是通过对目标工程的.class文件进行代码注入的方式将通知(Advise)插入到目标代码中。
第一步:根据pointCut切点规则匹配的joinPoint;
第二步:将Advise插入到目标JoinPoint中。
这样在程序运行时被重构的连接点将会回调Advise方法,就实现了AspectJ代码与目标代码之间的连接。

JoinPoint类包UML:

2.3.1 Before、After(AfterThrowing)插入示意图

Before和After的插入其实就是在匹配到的JoinPoint调用的前后插入我们编写的Before\After的Advise方法,以此来达到在目标JoinPoint执行之前先进入Advise方法,执行之后进入Advise方法。
如下图所示,目标JoinPoint为FuncB()方法,需要在他执行前后进行AOP截获:

2.3.2 Around替换逻辑示意图

总体来说,使用了代理+闭包的方式进行替换,将原方法体放置到新的函数中替换,通过一个单独的闭包拆分来执行,相当于对目标JoinPoint进行了一个代理。

2.3.3 代码分析

下面的Example作为目标源码,我们对它的printLog()方法进行替换、对getValue()方法调用前后插入Advise方法。

public class Example {String value = "value";public void printLog() {String str = getValue();}public String getValue() {return value;}
}

切面代码:

@Aspect
public class LogAspect{
//所有实例方法调用截获
private static final String INSTANCE_METHOD_CALL = "call(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)"
@Pointcut(INSTANCE_METHOD_CALL) public void instanceMethodCall() {
}//实例方法调用前后Advice
@Before("instanceMethodCall()") public void beforInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "before instance call");
}@After("instanceMethodCall()") public void afterInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "after instance call");
}//所有实例方法执行截获
private static final String INSTANCE_METHOD_EXECUTING = "execution(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)"
@Pointcut(INSTANCE_METHOD_EXECUTING) public void instanceMethodExecuting() {
}//实例方法执行Advice
@Around("instanceMethodExecuting()") public Object InstanceMethodExecutingAround(ProceedingJoinPoint joinPoint){Log.e(getClass().getSimpleName(),"InstanceMethodExecuting()");Object result = printLog(joinPoint, "instance executing");return result;}
}

反编译后的结果
网上给的反编译过程都是apktool——>dex2jar——>jd­gui,这个我用hotel_road_map的debug包试过,反编译出来的jar包里面只有几个系统类,不知道什么原因,其他的包又可以正常反编译。
推荐一个反编译工具:jadx(可以直接反编译apk)。

public class Example {private static final StaticPart ajc$tjp_0 = null;
private static final StaticPart ajc$tjp_1 = null;
private static final StaticPart ajc$tjp_2 = null;
String TAG = "Example";
String value = "value";
static {
ajc$preClinit();
}
//初始化连接点静态部分:方法名、参数列表、返回值、包路径等等。
private static void ajc$preClinit() {
Factory factory = new Factory("Example.java", Example.class);
ajc$tjp_0 = factory.makeSJP(JoinPoint.METHOD_CALL, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadmap.Ex");
ajc$tjp_1 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "printLog", "com.meituan.hotel.roadm");
ajc$tjp_2 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadm");
}//原方法的闭包,在Aspect切面中joinPoint.proceed()会调用该闭包
public class AjcClosure1 extends AroundClosure {public AjcClosure1(Object[] objArr) {super(objArr);
}public Object run(Object[] objArr) {Object[] objArr2 = this.state;Example.printLog_aroundBody0((Example) objArr2[0], (JoinPoint) objArr2[1]);return null;}
}
//原方法真正的方法体,在闭包中被调用static final void printLog_aroundBody0(Example ajc$this, JoinPoint joinPoint) {JoinPoint makeJP = Factory.makeJP(ajc$tjp_0, ajc$this, ajc$this);try {//@Before的Advise被插入了目标代码调用之前LogAspect.aspectOf().beforInstanceCall(makeJP);String str = ajc$this.getValue();} finally {//@After的Advise被插入到目标代码调用之后,通过Finally强制执行After逻辑,每一个BeforeLogAspect.aspectOf().afterInstanceCall(makeJP);}}//原来的printLog()方法被AspectJ给替换了,替换成为链接AspectJ和源代码的桥梁,真正的方法体被放在了新的方法中。public void printLog() {//连接点构造JoinPoint makeJP = Factory.makeJP(ajc$tjp_1, this, this);//将连接点与原方法的闭包连接,这样就可以在AspectJ的JoinPoint中通过joinPoint.proceed()调用闭包执行原方法。LogAspect.aspectOf().InstanceMethodExecutingAround(new AjcClosure1(new Object[]{this, makeJP}).linkClosureAndJoinPoint(
}public String getValue() {
JoinPoint makeJP = Factory.makeJP(ajc$tjp_2, this, this);return (String)LogAspect.InstanceMethodExecutingAround(new AjcClosure3(new Object[]{this, makeJP}).linkClosureAndJoinPoint();}
}

Before、After(AfterThrowing)插入分析

Before\After的插入调用比较简单,通过PointCut定位匹配到JoinPoint之后,将我们编写的Before\After的切面方法直接插入到目标JoinPoint前后即可。这样就可以改变原有的代码调用轨
迹,在目标方法调用前后增加我们自己的AOP方法。

//原方法真正的方法体,在闭包中被调用
static final void printLog_aroundBody0(Example ajc$this, JoinPoint joinPoint) {
JoinPoint makeJP = Factory.makeJP(ajc$tjp_0, ajc$this, ajc$this);
try {
//@Before的Advise被插入了目标代码调用之前
LogAspect.aspectOf().beforInstanceCall(makeJP);
String str = ajc$this.getValue();
} finally {
//@After的Advise被插入到目标代码调用之后,通过Finally强制执行After逻辑,每一个Before
LogAspect.aspectOf().afterInstanceCall(makeJP);
}
}

Around替换代码分析
JoinPoint为printLog()方法,是被Around替换的,反编译后的部分代码如下:
首先,在静态初始化的时候,通过Factory会为每一个JPoint先构造出静态部分信息StaticPart。

//初始化连接点静态部分:方法名、参数列表、返回值、包路径等等。
private static void ajc$preClinit() {Factory factory = new Factory("Example.java", Example.class);ajc$tjp_0 = factory.makeSJP(JoinPoint.METHOD_CALL, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadmap.Exa");ajc$tjp_1 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "printLog", "com.meituan.hotel.roadma");ajc$tjp_2 = factory.makeSJP(JoinPoint.METHOD_EXECUTION, factory.makeMethodSig("1", "getValue", "com.meituan.hotel.roadma");
}
public JoinPoint.StaticPart makeSJP(String kind, String modifiers, String methodName, String declaringType, String paramTypes,St
//构造方法签名实例,其中存储着方法的静态信息
Signature sig = this.makeMethodSig(modifiers, methodName, declaringType, paramTypes, paramNames, "", returnType);
return new JoinPointImpl.StaticPartImpl(count++, kind, sig, makeSourceLoc(l, ­1));
}

其次,将printLog方法体替换,以XXX_aroundBodyN(args)命名,原方法体被替换如下:

//原来的printLog()方法被AspectJ给替换了,替换成为链接AspectJ和源代码的桥梁,真正的方法体被放在了新的方法中。
public void printLog() {
//连接点构造
JoinPoint makeJP = Factory.makeJP(ajc$tjp_1, this, this);
//将连接点与原方法的闭包连接,这样就可以在AspectJ的JoinPoint中通过joinPoint.proceed()调用闭包执行原方法。
LogAspect.aspectOf().InstanceMethodExecutingAround(new AjcClosure1(new Object[]{this, makeJP}).linkClosureAndJoinPoint(
}

AroundClosure闭包,将运行时对象和当前连接点JP对象传入其中,调用linkClosureAndJoinPoint()进行两端的绑定,这样在Around中就可以通过ProceedingJoinPoint.proceed()调用AroundClosure,进而调用目标方法。

public abstract class AroundClosure {protected Object[] state;protected Object[] preInitializationState;public AroundClosure() {}public AroundClosure(Object[] state) {this.state = state;}public ProceedingJoinPoint linkClosureAndJoinPoint() {//获取执行链接点,默认数组最后一个是连接点ProceedingJoinPoint jp = (ProceedingJoinPoint)state[state.length­1];//设置执行时闭包jp.set$AroundClosure(this);return jp;}
}

JoinPointImpl,包括一个JoinPoint的静态部分和实例部分:

class JoinPointImpl implements ProceedingJoinPoint {//JP静态部分static class StaticPartImpl implements    JoinPoint.StaticPart {String kind;Signature signature;SourceLocation sourceLocation;private int id;//省略}Object _this;Object target;Object[] args;org.aspectj.lang.JoinPoint.StaticPart staticPart;//省略....// To proceed we need a closure to proceed onprivate AroundClosure arc;public void set$AroundClosure(AroundClosure arc) {this.arc = arc;}//通过proceed()调用闭包arc的run方法,并且传入JP的执行状态:参数列表等。进而调用原方法体执行public Object proceed() throws Throwable {//when called from a before advice, but be a no­opif (arc == null)return null;elsereturn arc.run(arc.getState());}//省略....
}

2.4 AspectJ切面编写

2.4.1 日志打印

(1)追踪某些特定方法的调用日志,统计调用的频率
(2)关注某类方法的日志
(3)全局日志的打印
AOP示例代码:

/**
* Created by malingyi on 2017/3/22.
*/
/**
* 日志打印。(分静态调用、静态执行、实例调用、实例执行四类日志)
*/
@Aspect public class LogAspect {//所有静态方法调用截获
private static final String STATIC_METHOD_CALL =
"call(static * com.meituan.hotel.roadmap..*.*(..))";
@Pointcut(STATIC_METHOD_CALL) public void staticMethodCutting() {
}
@Before("staticMethodCutting()") public void beforStaticCall(JoinPoint joinPoint) {
printLog(joinPoint, "before static call");
}
@After("staticMethodCutting()") public void afterStaticCall(JoinPoint joinPoint) {
printLog(joinPoint, "after static call");
}
//所有实例方法调用截获
private static final String INSTANCE_METHOD_CALL =
"call(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)";
@Pointcut(INSTANCE_METHOD_CALL) public void instanceMethodCall() {
}
//实例方法调用前后Advice
@Before("instanceMethodCall()") public void beforInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "before instance call");
}
@After("instanceMethodCall()") public void afterInstanceCall(JoinPoint joinPoint) {
printLog(joinPoint, "after instance call");
}
//所有静态方法执行截获
private static final String STATIC_METHOD_EXECUTING =
"execution(static * com.meituan.hotel.roadmap..*.*(..)) && !within(com.example.monitor.*)";
@Pointcut(STATIC_METHOD_EXECUTING) public void staticExecutionCutting() {
}
//所有实例方法执行截获
private static final String INSTANCE_METHOD_EXECUTING =
"execution(!static * com.meituan.hotel.roadmap..*.*(..))&&target(Object)&& !within(com.example.monitor.*)";
@Pointcut(INSTANCE_METHOD_EXECUTING) public void instanceMethodExecuting() {
}
//静态方法执行Advice
@Around("staticExecutionCutting()") public Object staticMethodExecuting(
ProceedingJoinPoint joinPoint) {
Log.e(getClass().getSimpleName(), "staticMethodExecuting()");
Object result = printLog(joinPoint, "static executing");
return result;
}
//实例方法执行Advice
@Around("instanceMethodExecuting()") public Object InstanceMethodExecuting(
ProceedingJoinPoint joinPoint) {
Log.e(getClass().getSimpleName(), "InstanceMethodExecuting()");
Object result = printLog(joinPoint, "instance executing");
return result;
}
/**
* 日志打印和统计
* @param joinPoint
* @param describe
* @return
*/
private Object printLog(JoinPoint joinPoint, String describe) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
try {
if (joinPoint instanceof ProceedingJoinPoint) {
return ((ProceedingJoinPoint) joinPoint).proceed(joinPoint.getArgs());
}
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally {
Log.e(getClass().getSimpleName(), describe + " : " + signature.toLongString());
}
return null;
}
}

结果:

04­24 02:39:51.388 3081­3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04­24 02:39:51.388 3081­3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : public int
com.meituan.hotel.roadmap.MainActivity.ViewPagerFragmentAdapter.getCount()
04­24 02:39:51.418 3081­3171/com.meituan.hotel.roadmap E/Surface: getSlotFromBufferLocked: unknown buffer: 0xf2ca76e0
04­24 02:39:51.667 3081­3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04­24 02:39:51.667 3081­3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04­24 02:39:51.667 3081­3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : public void
com.meituan.hotel.roadmap.RoadMapApplication.2.onActivityStopped(android.app.Activity)
04­24 02:39:51.667 3081­3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : protected void com.meituan.hotel.roadmap.BaseActivity.onStop()
04­24 02:39:51.667 3081­3081/com.meituan.hotel.roadmap E/ContentValues: HotelDetailActivity 停留时间: 4657.063 ms
04­24 02:39:51.668 3081­3081/com.meituan.hotel.roadmap E/LogAspect: InstanceMethodExecuting()
04­24 02:39:51.668 3081­3081/com.meituan.hotel.roadmap E/LogAspect: instance executing : public void
com.meituan.hotel.roadmap.RoadMapApplication.2.onActivityDestroyed(android.app.Activity)

2.4.2 耗时监控

示例代码1:
步骤:
1、编写AspectJ的语法,横切需要关注的切点
2、在其执行前后增加计时器
3、输出时间日志,进行统计分析

/**
* 时间监控
*/
@Aspect public class TimeMonitorAspect {//横切项目中所有Activity的子类,以Layout命名、以及它的子类的所有方法的执行
private static final String POINTCUT_METHOD =
"(execution(* android.app.Activity+.*(..)) ||execution(* *..Layout+.*(..)))&& within(com.meituan.hotel.roadmap.*)"
@Pointcut(POINTCUT_METHOD) public void methodAnnotated() {
}
/**
* 截获原方法的执行,添加计时器,监控单个方法的耗时
* @throws Throwable
*/
@Around("methodAnnotated()") public Object weaveJoinPoint(ProceedingJoinPoint joinPoint)
throws Throwable {
//初始化计时器
final StopWatch stopWatch = new StopWatch();
//开始监听
stopWatch.start();
//调用原方法的执行。
Object result = joinPoint.proceed();
//监听结束
stopWatch.stop();
//日志打印
printLog(joinPoint, stopWatch);
return result;
}
private void printLog(JoinPoint joinPoint, StopWatch stopWatch) {
//获取方法信息对象
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className;
//获取当前对象,通过反射获取类别详细信息
className = joinPoint.getThis().getClass().getName();
String methodName = methodSignature.getName();
String msg = buildLogMessage(methodName, stopWatch.getTotalTime(1));
//日志存储、打印
TimeMonitorLog.log(new MethodMsg(className, msg, (long) stopWatch.getTotalTime(1)));
// TimeMonitorLog.writeToSDCard(new Path()); //日志存储
// TimeMonitorLog.ReadIn(new Path()); //日志读取
}/**
* 创建一个日志信息
* @param methodName 方法名
* @param methodDuration 执行时间
*/
private static String buildLogMessage(String methodName, double methodDuration) {
StringBuilder message = new StringBuilder();
message.append(methodName);
message.append(" ­­> ");
message.append("[");
message.append(methodDuration);
if (StopWatch.Accuracy == 1) {
message.append("ms");
} else {
message.append("mic");
}
message.append("] \n");
return message.toString();
}
}

结果:

03­27 04:31:35.681 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.BaseActivity onStop ­­> [0.286ms]
03­27 04:31:39.040 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.HotelDetailActivity getLayoutId ­­> [0.003ms]
03­27 04:31:39.047 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.BaseActivity onCreate ­­> [7.217ms]
03­27 04:31:39.048 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.HotelDetailActivity onCreate ­­> [7.972ms]
03­27 04:31:39.050 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: class com.meituan.hotel.roadmap.BaseActivity onStart ­­> [0.276ms]

示例代码2——监控Activity页面的停留时间
步骤:
1、编写横切项目中Activity的onStart()、onStop()的切点语法
2、然后在onStart()切点执行之前启动计时器,将其与该页面对象存入Map中进行绑定
3、在onStop()切点执行完毕之后,从通过该对象从Map中获取计时器,然后结束计时,输出日志。

/**
* 时间监控
*/
@Aspect public class TimeMonitorAspect {private static final String TAG = "TimeMonitorAspect";
//存放<页面对象,计时器>
private HashMap<Object,StopWatch> map = new HashMap<>();
/**
* 横切页面的onStart和onStop方法,监控两个方法之间的耗时
*/
@Pointcut("execution(* *..Activity+.onStart(..))&&this(java.lang.Object)&& within(com.meituan.hotel.roadmap.*)")
public void onStart(){}
@Pointcut("execution(* *..Activity+.onStop(..))&&this(java.lang.Object)&& within(com.meituan.hotel.roadmap.*)")
public void onStop(){}
@Pointcut("onStart() && !cflowbelow(onStart())")
public void realOnStart(){}
@Pointcut("onStop() && !cflowbelow(onStop())")
public void realOnStop(){}
/**
* 在onCreate()调用时,开启该页面的计时器,将计时器存入HashMap<Object,StopWatch>中。
* @param joinPoint
* @return
*/
@Around("realOnStart()")
public Object AroundOnStart(ProceedingJoinPoint joinPoint){
Object result = null;
Object target = joinPoint.getTarget();
StopWatch stopWatch = new StopWatch();
if (target != null){
map.put(target,stopWatch);
}
try {
stopWatch.start();
result = joinPoint.proceed(joinPoint.getArgs());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
/**
* 在onStop()结束时,从HashMap<Object,StopWatch>中获取该计时器,停止该页面的计时器,并将时间打印出来。
* @param joinPoint
* @return
*/
@Around("realOnStop()")
public Object AroundOnStop(ProceedingJoinPoint joinPoint){
Object result = null;
Object target = joinPoint.getTarget();
StopWatch stopWatch = null;
if (target != null){
stopWatch = map.get(target);
}
try {
result = joinPoint.proceed(joinPoint.getArgs());
if (stopWatch != null){
stopWatch.stop();
//打印日志
Log.e(TAG,joinPoint.getTarget().getClass().getSimpleName() + " 停留时间: "+ stopWatch.getTotalTimeMillis() + " ms"
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}

结果:

03­27 05:01:16.629 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: HotelDetailActivity 停留时间: 4170.12 ms
03­27 04:31:39.465 27210­27210/com.meituan.hotel.roadmap E/TimeMonitorAspect: MainActivity 停留时间: 4112.293 ms


2.4.3 异常处理

示例代码1——截获谋类异常

/**
* 异常处理
*/
@Aspect
public class ExceptionHandleAspect {private static final String TAG = "ExceptionHandleAspect";
/**
* 截获空指针异常
* @param e
*/
@Pointcut("handler(java.lang.NullPointerException)&&args(e)")
public void handle(NullPointerException e){
}
/**
* 在catch代码执行之前做一些处理
* @param joinPoint
* @param e 异常参数
*/
@Before(value = "handle(e)",argNames = "e")
public void handleBefore(JoinPoint joinPoint,NullPointerException e){
Log.e(TAG,joinPoint.getSignature().toLongString()+" handleBefore() :"+e.toString());
//汇总处理
}
}

结果:

03­27 06:46:48.700 11618­11618/com.meituan.hotel.roadmap E/ExceptionHandleAspect: MainActivity afterThrowing() :java.lang.NullPointerException: null

项目中所有的NullPointerException的catch()方法执行之前都会被截获。

示例代码2——截获指定方法的异常

/**
* 异常处理
*/
@Aspect
public class ExceptionHandleAspect {private static final String TAG = "ExceptionHandleAspect";
/**
* 截获某一个方法抛出的异常
*/
@Pointcut("call(* com.meituan.hotel.roadmap.*.initTabLayout(..))")
public void afterThrow(){}
/**
* 在异常抛出后,该操作优先于下一个切点的@Before()
* @param joinPoint
* @param e 异常参数
*/
@AfterThrowing(pointcut = "afterThrow()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e){
Log.e(TAG,joinPoint.getTarget().getClass().getSimpleName() + " afterThrowing() :" + e.toString());
}
}

结果:

03­27 06:46:48.700 11618­11618/com.meituan.hotel.roadmap E/ExceptionHandleAspect: MainActivity afterThrowing() :java.lang.NullPointerException: null

在MainActivity上调用initTabLayout()方法时抛出一个空指针,抛出后被截获了。

2.4.4 降级替代方案——吐司

在Android 5.0以后,有些手机关闭通知权限会导致Toast通知无法显示。有些项目在这之前就已经有很多Toast的使用了,那么这个时候就需要在Toast代码前后做权限验证,然后再使用备用方法替代。

使用AOP就可以将这种重复的代码聚焦在一处进行处理。类似于这样的,在代码工程迭代过程中,会大量重复用到的降级替代都可以使用AOP的思想。
步骤:
1、自定义一个Toast的View,该View不依赖于Notification通知权限。
2、截获项目中调用Toast的.show()方法。

@Aspect
public class ToastAspect {private static final String TAG = "toastAspect";
@Pointcut("call(* android.widget.Toast+.show(..)) && (within(com.meituan..*)|| within(com.sankuai..*))")
public void toastShow() {
}
@Pointcut("toastShow() && !cflowbelow(toastShow())")
public void realToastShow() {
}
@Around("realToastShow()")
public void toastShow(ProceedingJoinPoint point) {
try {
Toast toast = (Toast) point.getTarget();
Context context = (Context) getValue(toast, "mContext");
//如果当前没有context意味着可能页面被回收,或者的版本在19以上且通知可用,执行系统的Toast方案交给系统处理
if (context == null || Build.VERSION.SDK_INT >= 19 && NotificationManagerCompat.from(context).areNotificationsEnabl
//use system function
point.proceed(point.getArgs());
} else {//如果context存在,并且通知不可用,则使用自定义的Toast
//Toast params
int mDuration = toast.getDuration();
View mNextView = toast.getView();
int mGravity = toast.getGravity();
int mX = toast.getXOffset();
int mY = toast.getYOffset();
float mHorizontalMargin = toast.getHorizontalMargin();
float mVerticalMargin = toast.getVerticalMargin();
new MToast(context instanceof Application ? context : context.getApplicationContext())
.setDuration(mDuration)
.setView(mNextView)
.setGravity(mGravity, mX, mY)
.setMargin(mHorizontalMargin, mVerticalMargin).show();
}
} catch (Throwable exception) {
//ignore
}
}
// TODO: 2016/12/14 toast.cancel() can't be work with MToast
/**
* 通过字段名从对象或对象的父类中得到字段的值
*
* @param object 对象实例
* @param fieldName 字段名
* @return 字段对应的值
* @throws Exception
*/
public static Object getValue(Object object, String fieldName) throws Exception {
if (object == null || TextUtils.isEmpty(fieldName)) {
return null;
}
Field field = null;
Class<?> clazz = object.getClass();
for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object);
} catch (Exception e) {
//ignore
}
}
return null;
}
}

2.4.5 其他的系统横切关注点问题

使用特点:

  • 关注点具有普遍性需求,代码散乱分布在工程各处,可以抽出共同的代码。
  • 访问控制,例如:字段、方法的访问前做一些验证,访问之后做一些处理。
  • 代码约束,例如:限制某些方法只能在特定的地方使用,否则在编译期间抛出Error错误或者Warning。
  • 项目中需要临时插入一些方法、逻辑,但是不希望影响到原工程,易插易拔。

三、相关问题

3.1 编织速度

(1)尽量使用精确的匹配规则,降低匹配时间。
(2)排除不需要扫描的包。

// AspectJ
aspectj {
disableWhenDebug true
javartNeeded true
// 支付方排除
exclude group: 'com.sankuai.pay', module: 'buymodel'
exclude group: 'com.meituan.android.cashier', module: 'library'
// 第三方异常包排除
exclude group: 'com.dianping.nova.common', module: 'push'
exclude group: 'org.freemarker', module: 'freemarker'
compileOptions {
defaultJavaVersion = JavaVersion.VERSION_1_7
}
}

(3)硬件配置升级。

3.2 调试工具

Eclipse和Intellij 12支持AJDT调试工具,但是目前AndroidStudio并不支持,只能在Gradle构建时查看日志。
(1)切点:

(2)目标代码:

Gradle构建日志:

AOP之@AspectJ技术原理详解相关推荐

  1. Web端即时通讯技术原理详解

    Web端即时通讯技术原理详解 前言 在web端的IM即时通讯应用,由于浏览器的兼容性以及其固有的"客户端请求服务器处理并响应"的C/S通信模型,造成了要在浏览器中实现一个兼容性较好 ...

  2. Android热修复技术原理详解(最新最全版本)

    本文框架 什么是热修复? 热修复框架分类 技术原理及特点 Tinker框架解析 各框架对比图 总结   通过阅读本文,你会对热修复技术有更深的认知,本文会列出各类框架的优缺点以及技术原理,文章末尾简单 ...

  3. 新手入门:史上最全Web端即时通讯技术原理详解

    前言 有关IM(InstantMessaging)聊天应用(如:微信,QQ).消息推送技术(如:现今移动端APP标配的消息推送模块)等即时通讯应用场景下,大多数都是桌面应用程序或者native应用较为 ...

  4. Java技术原理详解

    一.Java 运行原理  1.高级语言运行过程  在程序真正运行在CPU上之前,必须要让OS的kernel理解我们在编辑器或者IDE里根据每种语言的语法规则敲入的源代码,kernel才能做出相关的调度 ...

  5. 新手入门贴:史上最全Web端即时通讯技术原理详解

    前言 有关IM(InstantMessaging)聊天应用(如:微信,QQ).消息推送技术(如:现今移动端APP标配的消息推送模块)等即时通讯应用场景下,大多数都是桌面应用程序或者native应用较为 ...

  6. 北斗GPS同步时钟(授时系统)技术原理详解

    1. 前言 由计算机网络系统组成的 分布式系统 ,若想协调一致进行: IT 行业的 " 整点开拍 " . " 秒杀 " . "Leader 选举 &q ...

  7. 【android】插件化技术原理详解

      作为移动端的黑科技,插件化技术一直受大厂的青睐.插件化技术有减少宿主Apk体积,可以独立更新,模块化开发等优点,让宿主APP极具扩展性.那么,现在就来聊聊其中的技术实现,国际惯例,先上效果图 这篇 ...

  8. 软件定义网络 (SDN)技术原理详解

    一.SDN相关概念 1.大二层网络 互联网时代,用户的访问称之为南北向流量,而数据中心之间的数据传递成为东西向流量. 很多情况下,需要不同的数据中心之间进行数据访问,数据同步.而去同步这些流量要求对这 ...

  9. 【VLAN技术原理详解与实战】图文并茂,让你看一遍就理解VLAN

    目录 VLAN技术是什么? 关于VLAN 交换机的链路类型 交换机的接口类型 VLAN配置具体流程

最新文章

  1. vm 软件现在地址加速版
  2. VB.NET 从main函数里启动窗口
  3. 事件监听一直报错Cannot set property 'display' of undefined
  4. orgman set触发的pricing set创建逻辑
  5. Binder远程转本地
  6. birt报表表格边框_选开源报表开发还是商用?2020报表软件产品整理
  7. 很多人在销售过程中,喜欢考虑用一些话术
  8. 一个简单的json解析器
  9. SQL分类,DDL,DML,DCL
  10. cmd编译java代码
  11. Topaz Gigapixel AI打开软件闪退及加载图片闪退 解决办法
  12. 论文的格式: 以Wiley为案例
  13. 计算机无法读光盘,win10系统无法读取光盘如何解决 win10系统读取光盘失败的解决方法...
  14. 《缠中说禅》炒股经典理论
  15. php死循浏览器卡死,Win10 Edge浏览器假死解决方案
  16. Can‘t find bundle for base name XXXX, locale zh_CN解决方法
  17. 基于ENVI下的土地利用信息提取(一)
  18. 【MySQL连接】MySQLdb安装与使用
  19. 论文参考文献格式自动生成
  20. alios things开发板_AliOS Things这个操作系统怎么样?

热门文章

  1. 面试热点|二叉树那点事儿(一)
  2. 早高峰共享单车潮汐点的群智优化Baseline
  3. Java语言编写扑克牌小游戏
  4. ae导出gif插件_AE导出GIF动画的几种方式
  5. 计算机专业群名有内涵,微信群起名高端有内涵
  6. Spark On Yarn基本原理及部署
  7. python高斯求和1-500
  8. 水火既济:百尺竿头;火水未济:更进一步
  9. 获取分钟数为5的倍数的时刻
  10. 八大排序 —— 详细图文讲解