在项目开发中,对 App 客户端重构后,发现用于统计用户行为的友盟统计代码和用户行为日志记录代码分散在各业务模块中,比如在某个模块,要想实现对用户的行为一和行为二进行统计,因此按照OOP面向对象编程思想,就需要把友盟统计的代码以强依赖的形式写入相应的模块中,这样会造成项目业务逻辑混乱,并且不利于对外提供SDK。因此,通过研究发现,在Android项目中,可以使用AOP面向切面编程思想,把项目中所有的友盟统计代码,从各个业务模块提取出来,统一放到一个模块里面,这样就可以避免我们提供的SDK中包含用户不需要的友盟SDK及其相关代码。

AOP

面向切面编程(AOP,Aspect-oriented programming):是一种可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的技术。AOP是OOP的延续,是软件开发中的一个热点,是函数式编程的一种衍生范型,将代码切入到类的指定方法、指定位置上的编程思想。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分,而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。举个简单的例子,对于“雇员”这样一个业务实体进行封装,自然是OOP/OOD的任务,我们可以为其建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中,若用AOP设计思想对“雇员”进行封装将无从谈起,同样,对于“权限检查”这一动作片断进行划分,则是AOP的目标领域,若通过OOD/OOP对一个动作进行封装,则有点不伦不类。

AOP编程的主要用途有:日志记录,行为统计,安全控制,事务处理,异常处理,系统统一的认证、权限管理等。可以使用AOP技术将这些代码从业务逻辑代码中划分出来,通过对这些行为的分离,可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

AOP编程的常见的使用场景:

  • 日志记录
  • 持久化
  • 行为监测
  • 数据验证
  • 缓存
  • ...

代码注入时机

代码注入主要利用了Java的反射和注解机制,根据注解时机的不同,主要分为运行时、加载时和编译时。

  • 运行时:你的代码对增强代码的需求很明确,比如,必须使用动态代理(这可以说并不是真正的代码注入)。
  • 加载时:当目标类被Dalvik或者ART加载的时候修改才会被执行。这是对Java字节码文件或者Android的dex文件进行的注入操作。
  • 编译时:在打包发布程序之前,通过向编译过程添加额外的步骤来修改被编译的类。

常见AOP编程库

在Java中,常见的面向切面编程的开源库有: AspectJ:和Java语言无缝衔接的面向切面的编程的扩展工具(可用于Android)。 Javassist for Android:一个移植到Android平台的非常知名的操纵字节码的java库。 DexMaker:用于在Dalvik VM编译时或运行时生成代码的基于java语言的一套API。 ASMDEX:一个字节码操作库(ASM),但它处理Android可执行文件(DEX字节码)。

Aspectj

AOP是一个概念,一个规范,本身并没有设定具体语言的实现,这实际上提供了非常广阔的扩展的能力。AspectJ是AOP的一个很悠久的实现,它能够和 Java 配合起来使用,除此之外还有ASMDex,不过最出名还是Aspectj。

AspectJ的使用核心就是它的编译器,它就做了一件事,将AspectJ的代码在编译期插入目标程序当中,运行时跟在其它地方没什么两样,因此要使用它最关键的就是使用它的编译器去编译代码ajc。ajc会构建目标程序与AspectJ代码的联系,在编译期将AspectJ代码插入被切出的PointCut中,达到AOP的目的。

要理解AspectJ,就需要理解AspectJ提出的几个新的概念。

##AspectJ概念 AspectJ向Java引入了一个新的概念:join point,它包括几个新的结构: pointcuts,advice,inter-type declarations 和 aspects。

  • Cross-cutting concerns:即使在面向对象编程中大多数类都是执行一个单一的、特定的功能,它们也有时候需要共享一些通用的辅助功能。
  • Advice:需要被注入到.class字节码文件的代码。通常有三种:before,after和around,分别是在目标方法执行前,执行后以及替换目标代码执行。除了代码注入外,你还可以做一些别的修改,例如添加成员变量和接口到一个类中。
  • Join point:程序中执行代码插入的点,例如方法调用时或者方法执行时。
  • Pointcut:告诉代码注入工具在哪里注入特定代码的表达式(即需要在哪些Joint point应用特定的Advice)。
  • Aspect: Aspect将pointcut和advice 联系在一起。例如,我们通过定义一个pointcut和给出一个准确的advice实现向我们的程序中添加一个打印日志功能的aspect。

执行的流程:一个连接点是程序流中指定的一点。切点收集特定的连接点集合和在这些点中的值。一个通知是当一个连接点到达时执行的代码,这些都是AspectJ的动态部分。其实连接点就好比是程序中的一条一条的语句,而切点就是特定一条语句处设置的一个断点,它收集了断点处程序栈的信息,而通知就是在这个断点前后想要加入的程序代码。AspectJ中也有许多不同种类的类型间声明,这就允许程序员修改程序的静态结构、名称、类的成员以及类之间的关系。AspectJ中的方面是横切关注点的模块单元。它们的行为与Java语言中的类很像,但是方面还封装了切点、通知以及类型间声明。

正常情况下,我们会把一个简单的示例应用拆分成两个 modules,第一个包含我们的 Android App 代码,第二个是一个 Android Library 工程,使用 AspectJ 织入代码(代码注入)。其工程结构图如下:

Android集成AspectJ

集成AspectJ主要有两种方式: 1,插件的方式:网上有人在github上提供了集成的插件gradle-android-aspectj-plugin。这种方式配置简单方便,但经测试无法兼容databinding框架。

2,Gradle配置的方式:配置有点麻烦,不过国外一个大牛在build文件中添加了一些脚本,虽然有点难懂,但可以在AS中使用。文章出处:https://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/

下面讲讲如何在Android项目中集成AspectJ。 1,首先,新建一个AS原工程,然后再创建一个module(Android Library) 。

由于aspectj编译时需要用到ajc编译器,为了使 Aspectj能在Android上运行,将aspect模块的代码注入app中,需要使用gradle插件完成编译。

2,在gintonic中添加AspectJ依赖,同时编写build脚本,添加任务,使得IDE使用ajc作为编译器编译代码,然后把该Module添加至主工程Module中。

import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Mainbuildscript {repositories {mavenCentral()}dependencies {classpath 'com.android.tools.build:gradle:2.1.0'classpath 'org.aspectj:aspectjtools:1.8.1'}
}apply plugin: 'com.android.library'repositories {mavenCentral()
}dependencies {compile 'org.aspectj:aspectjrt:1.8.1'
}android {compileSdkVersion 21buildToolsVersion '21.1.2'lintOptions {abortOnError false}
}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", plugin.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;}}}
}
复制代码

3,然后在主build.gradle(Module:app)中添加也要添加AspectJ依赖,同时编写build脚本,添加任务,目的就是为了建立两者的通信,使得IDE使用ajc编译代码。

apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Mainbuildscript {repositories {mavenCentral()}dependencies {classpath 'org.aspectj:aspectjtools:1.8.1'}
}
repositories {mavenCentral()
}android {compileSdkVersion 21buildToolsVersion '21.1.2'defaultConfig {applicationId 'com.example.myaspectjapplication'minSdkVersion 15targetSdkVersion 21}lintOptions {abortOnError true}
}final def log = project.logger
final def variants = project.android.applicationVariantsvariants.all { variant ->if (!variant.buildType.isDebuggable()) {log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")return;}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)]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.thrownbreak;case IMessage.WARNING:log.warn message.message, message.thrownbreak;case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}}
}
dependencies {compile fileTree(include: ['*.jar'], dir: 'libs')androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {exclude group: 'com.android.support', module: 'support-annotations'})//compile 'com.android.support:appcompat-v7:25.3.1'//compile 'com.android.support.constraint:constraint-layout:1.0.2'testCompile 'junit:junit:4.12'compile project(':gintonic')compile 'org.aspectj:aspectjrt:1.8.1'
}
复制代码

App主模块与其他库工程中的groovy构建语句唯一的差别是获取"-bootclasspath"的方法不同,主模块中配置是project.android.bootClasspath.join(File.pathSeparator),而在库工程中则是plugin.getAndroidBuilder().getBootClasspath(true).join(File.pathSeparator)

需要注意的是,由于不同版本的gradle在获取编译时获取类的路径等信息Api不同,所以以上groovy配置语句仅在Gradle Version高于3.3的版本上生效。

4,在Module(gintonic)中新建一个名为”TraceAspect”类,用于进行测试。

@Aspect
public class TraceAspect {//ydc startprivate static final String TAG = "ydc";@Before("execution(* android.app.Activity.on**(..))")public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {String key = joinPoint.getSignature().toString();Log.d(TAG, "onActivityMethodBefore: " + key+"\n"+joinPoint.getThis());}}
复制代码

然后我们新建一个测试页面LinearLayoutTestActivity类,代码如下:

public class LinearLayoutTestActivity extends Activity {private LinearLayout myLinearLayout;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_linear_layout_test);myLinearLayout = (LinearLayout) findViewById(R.id.linearLayoutOne);myLinearLayout.invalidate();}
}
复制代码

然后我们运行项目,很神奇的事情出现了,LinearLayoutTestActivity中的onCreate(Bundle savedInstanceState)方法被TraceAspect类监控了,不仅截取到了LinearLayoutTestActivity类信息和方法及方法参数。通过反编译apk,可以看一下相关的代码。

可以发现,在onCreate执行之前,插入了一些AspectJ的代码,并且调用了TraceAspect中的 onActivityMethodBefore(JoinPoint joinPoint)方法。

参考:AOP编程之AspectJ实战实现数据埋点

AspectJ实现Android端非侵入式埋点

美团移动性能监控

面向切面编程AspectJ在Android埋点的实践相关推荐

  1. 【AOP 面向切面编程】Android Studio 使用 AspectJ 监控方法运行原理分析

    文章目录 一.查看使用 AspectJ 后生成的 Class 字节码类 二.AspectJ 的本质 一.查看使用 AspectJ 后生成的 Class 字节码类 在 Android Studio 中查 ...

  2. 【AOP 面向切面编程】Android Studio 中配置 AspectJ ( 下载并配置AS中 jar 包 | 配置 Gradle 和 Gradle 插件版本 | 配置 Gradle 构建脚本 )

    文章目录 一.AspectJ 下载 二.拷贝 aspectjrt.jar 到 Android Studio 三.配置 Gradle 和 Gradle 插件版本 四.配置 Gradle 构建脚本 一.A ...

  3. Android AOP 面向切面编程

    一.AOP即面向切面编程 AOP 是 Aspect Oriented Programming 的缩写,译为面向切面编程.用我们最常用的 OOP 来对比理解: 纵向关系 OOP,横向角度 AOP 举个例 ...

  4. 【AOP 面向切面编程】AOP 简介 ( AspectJ 简介 | AspectJ 下载 )

    文章目录 一.AOP 简介 二.AspectJ 简介 三.AspectJ 下载 一.AOP 简介 AOP 是 Aspect Oriented Programming 的缩写 , 面向切面编程 ; 利用 ...

  5. Android 面向切面编程(AOP)

    概念: AOP: Aspect Oriented Programming 面向切面编程. AOP是个概念,AspectJ 是它的一个具体实现.和Java配合使用. AspectJ:核心是他的编译器(a ...

  6. AOP面向切面编程在Android开发中的应用

    面向切面编程-Aspect Oriented Programming,简称AOP,通过预编译的方式和运行期间动态代理实现程序功能的统一维护的一种技术. AOP能够做什么呢? 性能检测 权限验证 释放资 ...

  7. 【字节码插桩】Android 打包流程 | Android 中的字节码操作方式 | AOP 面向切面编程 | APT 编译时技术

    文章目录 一.Android 中的 Java 源码打包流程 1.Java 源码打包流程 2.字符串常量池 二.Android 中的字节码操作方式 一.Android 中的 Java 源码打包流程 Ja ...

  8. Spring→面向切面编程AOP、相关概念、通知Advice类型、配置切面切入点通知、AOP相关API、AOP代理类ProxyFactoryBean、AOP注解@AspectJ

    面向切面编程AOP CGLib AOP相关概念 Advice类型 Spring实现AOP Spring配置切面aspect 配置切入点pointcut 配置通知advice 配置通知参数 调用新的父类 ...

  9. AOP面向切面编程简单使用

    一.AOP介绍 1.AOP是什么? AOP即为面向切面编程,和Java的OOP面向对象编程一样,都是一种编程思想,一些先行者已经为面向切面编程开发了一套语言来支持. 2.AOP的作用是什么? 通过预编 ...

最新文章

  1. 使用Protobuf文件一键生成Java类
  2. Node.js:路由
  3. pythonflask configlist.py_flask源码阅读系列一config模块
  4. 浅谈 Java Printing
  5. Jquery封装(学习)01
  6. GitHub防黑客新措施:弃用账密验证Git操作,改用token或SSH密钥,今晚0点执行
  7. MyBatis拦截器原理探究
  8. android进出动画有白屏,Android启动白屏原因及解决方案
  9. java-transaction事件
  10. React开发(178):ant design table基础用法
  11. Android Studio------------重要提醒
  12. php 栈实现历史记录后退,栈:如何实现浏览器的前进和后退功能
  13. 咱中国女人太贪钱? 其实真不怨她们
  14. python---基础知识回顾(十)进程和线程(协程gevent:线程在I/O请求上的优化)...
  15. 【绘图】Origin关闭加速模式(speed mode)
  16. Duality-Gated Mutual Condition Network for RGBT Tracking
  17. 斐波那契数列与数列求和
  18. 中国石油大学 现代远程教育入学指南
  19. Linux进程管理、防火墙
  20. matlab的空间坐标轴,空间三维坐标轴旋转

热门文章

  1. Ajax中的JSON
  2. 小程序入门到精通(三):学小程序必备技术基础-flex布局
  3. JS-分支结构(单向-双向-多向-嵌套)
  4. 7-47 对称排序 (25 分)
  5. linux命令栏下访问oracle,linux下远程连接oracle数据库
  6. pygame和python的区别_用Python和Pygame写游戏-从入门到精通(22)
  7. 计算机鼠标不灵活怎么办,电脑鼠标不灵敏怎么调 玩LOL鼠标经常失灵怎么办
  8. favicon.ico是什么?
  9. 如何查看 mysql 的视图?
  10. SPOJ - SUBLEX 【后缀自动机】