1.用户行为统计怎么做:
适时的推送、合适用户的内容、软件的性能检测和优化等在用户量到达一定程度的时候显得尤为重要。要做到这些首先就是要做用户行为统计和性能检测,例如我要统计登录接口的响应时间:
public void login(View view) {
long beginTime = System.currentTimeMillis();
SystemClock.sleep(500); //模拟登录
long duration = System.currentTimeMillis() - beginTime;
//保存到数据库
saveToDatabase();
}
显然,我们可以在调用登录功能之前记录下当前时间,方法运行结束前记录下响应时长,保存到数据库。然后在合适的时机上传服务器,完成统计;这样虽然能完成功能,但是缺点也很明显:
- 当要统计的方法很多的话这样做就会出现很多冗余的代码、难以维护。
- 登录功能又涉及到了统计功能、违反了单一职责原则,代码混乱。
所以我们的目标是:简化统计流程、将统计与业务逻辑分离。
2.什么是AOP:
- AOP全称 aspect oriented programing,即面向切片编程。指通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
- AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
比如有三个模块:登录、聊天、转账,现在要对各个模块的方法调用情况进行统计,OOP思想做法是设计一个统计模块,提供接口供这三个模块调用。这样每个模块都要调用统计模块的接口,如果接口有改动,需要在这三个模块中每次调用的地方修改,这样做的弊端有:代码冗余,逻辑不清晰,重构不方便,违背单一原则。运用AOP的思想做法是:将这几个模块切片,在特定的切入点进行hook,将共同的统计逻辑添加到模块中而不影响原有模块的独立性和原有的架构。
3.AspectJ:AOP在android中的应用:
1. 概括 :
AspectJ是Android AOP主流框架之一,它定义了AOP语法,有一个专门的编译器用来生成遵守Java字节编码规范的Class文件,在编译期注入代码。AspectJ的基本概念:
- Aspect:Aspect的声明类型Java中的类声明,在Aspect中会包含一些PointCut以及相应的Advice。
- Joint Point:表明在程序中明确定义的点,典型的包括方法调用、对类成员的访问以及异常处理程序块的执行等等。它自身还可以嵌套其他joint point。
- Pointcut:表示一组joint point,这些joint point通过逻辑关系或正则表达式等方式集中起来,它定义了Advice将要发生的地方。
- Advice:Advice定义了在pointcut里面的程序点具体要做的操作,它通过before、after和around来区别每个joint point之前、之后还是代替执行的代码。
2. AspectJ的基本使用 :
(1)新建项目,将aspectjtools-1.8.9.jar包复制到project/app/libs目录下;
(2)在app的build.gradle文件结尾加入以下代码:
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.8",
"-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;
}
}
}
}
(3)自定义注解类BehaviorTrace:
//定义注解
@Target(ElementType.METHOD) // 修饰的是方法
@Retention(RetentionPolicy.RUNTIME) // 编译时注解
public @interface BehaviorTrace {
String value(); //功能点名称
int type(); // 唯一确定功能点的值
}
(4)编写 Aspect:
@Aspect
public class BehaviorAspect {
//用正则表达的方法 表明带有BehaviorTrace注解的方法都属于这个切面
// * *(..)表示任意类的任意方法
//@com.example.aoptest.BehaviorTrace :被打上了BehaviorTrace注解
@Pointcut("execution(@com.example.aoptest.BehaviorTrace * *(..))")// 定义切点
public void methodWithBehaviorTraceAnnotation() {
}
@Around("methodWithBehaviorTraceAnnotation()")
public void weaveJoinPoint(ProceedingJoinPoint point) throws Throwable {
//方法执行前
MethodSignature signature = (MethodSignature) point.getSignature();
String className = signature.getDeclaringType().getSimpleName(); //获取类名
String methodName = signature.getName(); //获取方法名
//获取注解
BehaviorTrace behaviorTrace = signature.getMethod().getAnnotation(BehaviorTrace.class);
long beginTime = System.currentTimeMillis();
point.proceed();
long duration = System.currentTimeMillis() - beginTime;
Log.d("Jia", "weaveJoinPoint: " + className + "类中的方法:" + methodName + "; 功能:" + behaviorTrace.value() + "; 执行时间" + duration + "毫秒");
}
}
(5)在activity中的使用:
@BehaviorTrace(value = "登录",type = 1) //给登录方法添加注解
public void login(View view) {
SystemClock.sleep(50);
//保存到数据库
}
可以看到只要给需要统计的方法添加注释就可以了,统计的逻辑完全交给Aspect中完成, 运行程序、调用login方法,控制台输出如下:
相关项目地址:https://github.com/JiaLiangGitHub/AspectSample