开发工具:Android Studio
参考链接:
1.一个流传广泛到不知道哪个是原版的博客
2.基于上面内容中第二种方式配置的具体说明
3.基于上述方法的测试代码库
4.一个插件 gradle-android-aspectj-plugin
5.另一个插件 gradle_plugin_android_aspectjx
6.Aspect Oriented Programming in Android
做安卓都知道 OOP 面向对象编程。它将一系列事物抽象化,把他们有公共的属性集合到一起,成为能高度概括某一类具体事物的概念。比如交通工具是一个抽象概念,而具体实现则有汽车、自行车等等,相信走在安卓开发路上的我们都很熟悉,但是在面向对象编程的过程中,我们遇到这样的问题:需要对某些事件进行统一的处理,比如统计埋点、权限控制、日志打印。显然,我们可以自定义一个类,然后在不同事件中分别调用指定方法,这样的思想在我们日常的编程中已经有了相当成熟的应用,但是终究还是不够智能和便捷。
于是乎,伟大的 AOP 出现了,解决了许许多多的问题,而今天,我们就来简单了解一下 AOP。
一、什么是AOP ?
AOP是Aspect Oriented Programming的缩写,也就是题目里说的面相切面编程。它通过预编译或者运行期动态代理实现程序功能的统一维护。AOP 是 OOP的延续,也是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑之间的耦合度降低,提高程序的可重用性。
还不是很了解的可以去稍微看一下,有点多,简单看看就好。百度链接
二、运行时AOP框架
Dexposed,这是是阿里巴巴无线事业部第一个重量级 Andorid 开源软件,基于 ROOT 社区著名开源项目Xposed 改造剥离了 ROOT 部分,演化为服务于所在应用自身的 AOP 框架。它支撑了阿里大部分 App 的在线分钟级客户端 bugfix 和线上调试能力。
但是,它在2015年貌似就已经停止更新了
但是,它在2015年貌似就已经停止更新了
但是,它在2015年貌似就已经停止更新了
看看github上的更新数据:
安卓5.0使用ART虚拟机后,这个库的支持就一直停留在 testing 再也没变过。前段时间,埋点数据和ios严重不符合,才发现了这个问题,为此重新整理一下。
三、预编译AOP框架 -- AspectJ
由于找不到合适的运行时框架,我们决定使用预编译的AOP框架 -- AspectJ。根据网上目前的资料来看,可以使用的主要有两种方法:
- 1.使用插件 gradle-android-aspectj-plugin
- 2.自己配置 Gradle,添加脚本。
这种情况下,为了快速开发,一般当然会选择先考察插件的可使用性。
(如果想看自己配置 Gradle 的方法,可以直接跳到后面看 3.4 ~):-)
根据资料,gradle-android-aspectj-plugin 这个插件是不能支持 data-binding 的,虽然我公司也用不到,但是总希望能找到一个更优的解决方案。于是乎,我在公众号中找到了需要的东西——根据上面的插件改良的插件。并且在原作者的Github上,这个插件也得到了推荐。
那么下面,就了解一下这个插件的使用:
3.1 使用插件的走一波效果
工程- gradle
buildscript {
...
dependencies {
...
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8'
}
}
app - gradle
apply plugin: 'android-aspectjx'
//更详细的使用作者在readme中已经举例写得很明白了,可以自行参考上面的链接
aspectjx {
//加入需要织入代码的第三方库
includeJarFilter '...xxx', '...xxx'
//排除不需要织入代码的第三方库
excludeJarFilter '...xxx'
}
dependencies{
...
compile 'org.aspectj:aspectjrt:1.8.9'
}
AspectTest
@Aspect
public class AspectTest {
private static final String TAG = "AspectTest";
// 复制代码需要注意修改下面的 com.arno.testaspectj 为你的包名
private static final String ACTI_ONCREATE = "execution(com.arno.testaspectj.MainActivity.onCreate(..))";
@Before(ACTI_ONCREATE )
public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
Log.d(TAG, "onActivityCreate: " + joinPoint.getSignature().toString());
}
}
主界面什么也没动,这样工程就应该可以跑起来了。
If you meet any problem,I recommend strongly to update your gradle first,clean your project and run it.
在进入 MainActivity 后,打开控制台,你就会看到我们对应的日志打印。这是最简单的实现,我们可以看到,在没有更改 MainActivity 中代码的情况下,我们成功截取了 onCreate 方法的调用,并在它调用之前打印了日志。
那么下面让我们看看使用 AspectJ 需要了解哪些元素。
3.2 AspectJ 相关词汇了解
- 横向切面:传统的面相对象编程,每个单元就是一个类,在单一功能上可以很好地实现,但是他们通常会和其他类共同或者相关联的需求。比如日志、埋点等,尽管每个主类都有不同的功能,但是在细节功能需求上,会出现很多相同的代码。这就是我们 AspectJ 的功能,将不同的类横向切面,将某些需要添加相同代码的切点集合到一起做处理,减少代码的冗余和类之间的耦合。
- Advice:注入到类文件当中的代码以及我们如何注入这些代码。前面例子中的 @Before 就是插入的方法,而日志打印就是具体的代码。
- Join Point:程序中的一个特定切点,可能是切入代码的目标
- Pointcut:看了下各种各样的说法,拿捏不准。但是根据用的时候的感受来看,应该是具体定义的 Join Point,是特殊的Join Point。
- Aspect:Aspect 是 Pointcut 和 advice 的结合
- Weaving:织入代码的过程
3.3 Advice - Before && After && Around
以上三种切入方式是最常用的,切入方式如英文单词的意思,非常好理解。我们以下面的代码为例,说明调用过程。
@xxx注解("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
Log.d(TAG, msg);
}
- Before 注解效果:
@Before ("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
Log.d(TAG, msg);
}
// -- 执行顺序
Log.d(TAG,msg)
MainActivity.onCreate(.)
- After 注解效果:
@After ("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(JoinPoint joinPoint) throws Throwable {
Log.d(TAG, msg);
}
// -- 执行顺序
MainActivity.onCreate(.)
Log.d(TAG,msg)
- Around 注解效果:
@After ("execution(com.arno.testaspectj.MainActivity.onCreate(..))")
public void onActivityCreate(ProceedingJoinPoint proceedingJoinPoint ) throws Throwable {
Log.d(TAG, msg);
proceedingJoinPoint.proceed();
Log.d(TAG,msg2);
}
// -- 执行顺序
Log.d(TAG,msg)
MainActivity.onCreate(.)
Log.d(TAG,msg2)
3.4 自己配置 Gradle 插件,添加脚本
根据博客的说法,我照搬了一套,放到我的 Gradle 文件中去,然后报红,以下文件无法导入。
import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
之后我升级了一下 Gradle 版本,编译成功。但是配置了一些切入点,却并不执行相关内容。于是上网找了些资料,发现不少人和我一样对着这个英文资料在配置 aspectj 。有人说,配置中的 java 版本改一下就可以了。
String[] args = ["-showWeaveInfo",
"-1.7", // <-- java 版本
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", plugin.project.android.bootClasspath.join(
File.pathSeparator)
]
然而没有用,这期间,我试了各种方法,包括但不仅限于:clean project,rebuild project,new project... 后来我在 stackoverflow 上找到了一个答案:不靠谱的链接
按照它做了,然而。呵呵 :-(
后面在 stackoverflow 上又有新发现,但是链接已经找不到了,总之就是不能用 = = ,白叨叨了我好久,有点费时间。
所以,乖乖用插件吧。
-------------------------------------- 分割线 ------------------------------------------------
07/26
使用过程中发现如下问题:
- 在对 android.app.Activity.onResume() 方法织入代码时,如果其子类中没有重写这个方法,那么是无法织入代码的。(也就是说,即使在onResume中不做任何操作,同样要override)。前文中,我一直默认使用 execution,但是如果使用 call,那么即使你重写了 onResume 也无法织入代码,由于织入代码的位置问题,必须要在代码中调用了这个方法,才能成功织入。
- 在对 com.xxx.xx.BaseActivity.onResume() 方法织入代码时,如果 BaseActivity 和其子类都重写了 onResume() 方法,那么织入的代码会被调用两次。
- 对组合的自定义 pointcut 进行代码织入的时候,joinPoint.getArgs() 获取的参数是范围较小的方法的参数。
- 组合自定义 pointcut 的时候,withincode + execution 无法成功织入代码。
5.无法切割对象,所有的代码织入都必须在界面的方法中。
如果有新发现,欢迎戳我戳我戳我! 谢谢