Android中实现AOP切面编程

小伙伴们在学习java基础的时候肯定都都会学面向对象思想和三大特性。在OOP设计中有个单一职责原则,在很多时候都不会有问题,但是当很多模块都需要同一个功能的时候,这个时候还用OOP就会很麻烦。比如我们希望在各个模块中打印log,所以我们写了一个log类CustomLog类,然后在需要的地方写上一句log代码,但是这样对模块间侵入太深,极大增加了模块间的耦合。这个时候我们可以了解一下AOP切面编程。AOP是Aspect-Oriented Progreming的缩写,它提倡的是针对同一类问题的统一处理,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。基于此,AOP在Android中的应用就应运而生了。
基于AOP特性,我们自然而然能够想到切面编程在android开发中非常多的应用场景,例如无感埋点,因为埋点的代码都很通用,所以我们可以自定义 @HookMethod注解用于埋点。也可以自定义异步注解,将方法放在子线程中去执行。还可以用于编写安全气囊,保障APP的稳定性。
应公司进一步降低APP奔溃率要求,我们决定使用AOP切面编程来给应用增加一个贴心的保护o(╯□╰)o。

AspectJ

就好像OOP中的Java一样,一些先行者也搞了一套东西来支持AOP。目前用得比较火的就是AspectJ了。它其实不是一个新的语言,它就是一个代码编译器,在Java编译器的基础上增加了一些它自己的关键字识别和编译方法。因此,ajc也可以编译Java代码。当然,除了使用AspectJ特殊的关键字外,只要加上对应的AspectJ注解就好。AspectJ在Android Studio中的配置这里就不赘述了,有兴趣的小伙伴可以查看这里,我这篇文章也是参考自这里。

基础概念

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

PointCut 切入点:pointcut可以控制你把哪些advice应用于jointpoint上去,通常你使用pointcuts通过正则表达式来把明显的名字和模式进行匹配应用。决定了那个jointpoint会获得通知。分为call、execution、target、this、within等关键字(具体含义见第四节)

Advice 通知:advice是我们切面功能的实现,它是切点的真正执行的地方。比如像写日志到一个文件中,advice(包括:before、after、around等)在jointpoint处插入代码到应用程序中。我们来看一看原AspectJ程序和反编译过后的程序。看完下面的图我们就大概明白了AspectJ是如何达到监控源程序的信息了。

Joint Point 连接点:连接点是切面插入应用程序的地方,该点能被方法调用,而且也会被抛出意外。连接点是应用程序提供给切面插入的地方,在插入地建立AspectJ程序与源程序的连接。

Weaving 编织:主要是在编译期使用AJC将切面的代码注入到目标中, 并生成出代码混合过的.class的过程.

当我们希望执行切面编程的时候,首先要找到切入点。也就是说我们要找到被操作的对象,我们用PointCut来过滤,找到切入点。找到切入点后,我们需要确定切面编程执行时机,这个时候需要试用通知Advice,下面来详细看一下:

@Aspect//试用AspectJ编译
public class MMHSafeAspect {
     //环绕通知,在合适的时机手动执行被代理的方法
    //synthetic关键字表示由编译器创造的方法,故忽略
    @Around("execution(!synthetic * *(..)) && onSafe()")
    public Object doSafeMethod(final ProceedingJoinPoint joinPoint) throws Throwable {
        return safeMethod(joinPoint);
    }

    //找到需要切面编程的切入点
    //CrashSafe为自定义注解,即注释了CrashSafe的方法都会被安全气囊保护
    @Pointcut("@within(包名.CrashSafe)||@annotation(包名.CrashSafe)")
    public void onSafe() {
    }

    private Object safeMethod(final ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;
        try {
            //手动执行被代理的方法,并在这里try_catch,起到安全气囊的作用
            result = joinPoint.proceed(joinPoint.getArgs());
        } catch (Throwable e) {
            Log.e("MMHSafeAspect", "安全气囊拦截异常", e);
            if(null != AopApplicationContext.getInstacne()){
                AopApplicationContext.getInstacne().postBugly(e);
                if(BuildConfig.DEBUG){
                    BugTipActivity.startBugTip(Log.getStackTraceString(e));
                }
            }
        }
        return result;
    }
}

上面即安全气囊的核心代码,我们可以举一反三,自己建立一些基本数据结构,并且采用安全气囊保护。达到极大降低java层crash的目的。当然这只是增加用户体验的一种手段。我们不能用这个方法来逃避问题,所以应该在debug模式的时候暴露问题,在线上版本的时候才去启用安全气囊。回到主题,小伙伴们可能对Aspect,execution这些关键字觉得很陌生,别急,下面慢慢讲:

关键字

Aspect:这里要使用Aspect的编译器编译必须给类打上标注
execution: 顾名思义,它截获的是方法真正执行的代码区,Around方法块就是专门为它存在的。调用Around可以控制原方法的执行与否,可以选择执行也可以选择替换。与此类似的还有call, 同样,从名字可以看出,call截获的是方法的调用区,它并不截获代码真正的执行区域,它截获的是方法调用之前与调用之后(与before、after配合使用),在调用方法的前后插入JoinPoint和before、after通知。它截获的信息并没有execution那么多,它无法控制原来方法的执行与否,只是在方法调用前后插入切点,因此它比较适合做一些轻量的监控(方法调用耗时,方法的返回值等)。
Around:目标方法体被Around方法替换,原方法重新生成,名为XXX_aroundBody(),如果要调用原方法需要在AspectJ程序的Around方法体内调用joinPoint.proceed()还原方法执行,是这样达到替换原方法的目的。达到这个目的需要双方互相引用,桥梁便是Aspect类,目标程序插入了Aspect类所在的包获取引用。AspectJ通过在目标类里面加入Closure(闭包)类,该类构造函数包含了目标类实例、目标方法参数、JoinPoint对象等信息,同时该类作为切点原方法的执行代理,该闭包通过Aspect类调用Around方法传入Aspect程序。这样便达到了关联的目的,便可以在Aspect程序中监控和修改目标程序。
BeforeAfter: Before与After只是在方法被调用前和调用之后添加JoinPoint和通知方法(直接插入原程序方法体中),调用AspectJ程序定义的Advise方法,它并不替代原方法,是在方法call之前和之后做一个插入操作。After分为returnning和throwing两类,前者是在正常returning之后调用,后者是在throwing发生之后调用。默认的After是在finally处调用,因此它包含了前面的两种情况。
其他常见的关键字还有:
this :用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
within:用于匹配指定类型内的方法执行;

使用方式

1.结合自定义注解使用
上面的代码就是使用这个方法,这是个混合用法,可以在execution、call中使用注解,然后该注解标注在目标方法上就可以实现关联,并且截获。这样做缺点是入侵了源码,一些轻量的可以这么操作。优点是灵活
2.直接使用
也就是说直接在写死切入点,这样做的缺点是范围太广,假设我们把所有的activity中的所有方法都设为切入点,岂不是非常损耗性能?
所以我们可以结合业务需求选择自己合适的使用方式。下班了,有时间接着写

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

推荐阅读更多精彩内容