AspectJ In Android Studio

开发工具: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上的更新数据:

[alibaba](https://github.com/alibaba)/[dexposed](https://github.com/alibaba/dexposed)截图

安卓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上,这个插件也得到了推荐。

gradle-android-aspectj-plugin 首页

那么下面,就了解一下这个插件的使用:

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
使用过程中发现如下问题:

  1. 在对 android.app.Activity.onResume() 方法织入代码时,如果其子类中没有重写这个方法,那么是无法织入代码的。(也就是说,即使在onResume中不做任何操作,同样要override)。前文中,我一直默认使用 execution,但是如果使用 call,那么即使你重写了 onResume 也无法织入代码,由于织入代码的位置问题,必须要在代码中调用了这个方法,才能成功织入。
  2. 在对 com.xxx.xx.BaseActivity.onResume() 方法织入代码时,如果 BaseActivity 和其子类都重写了 onResume() 方法,那么织入的代码会被调用两次。
  3. 对组合的自定义 pointcut 进行代码织入的时候,joinPoint.getArgs() 获取的参数是范围较小的方法的参数。
  4. 组合自定义 pointcut 的时候,withincode + execution 无法成功织入代码。
    5.无法切割对象,所有的代码织入都必须在界面的方法中。

如果有新发现,欢迎戳我戳我戳我! 谢谢

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容