Android 面向切面编程 AOP 解决连续点击打开重复页面问题

先介绍概念

比如我希望在所有页面启动的时候加一个埋点~
希望在所有按钮点击的时候加个快速重复点击的判断~等等
这样在项目中同一种类型的所有代码处,统一加入逻辑处理的方法,叫做 面向切面编程 AOP

而这些我们需要插入代码的具体位置,则叫做切点 Pointcut,比如我在某些类的某个方法中插入

项目中可以插入地方的类型,叫做连接点 Join Point,比如我可以在方法中插入,可以在变量取值时插入

插入的方式 Advice,可以让我们指定在切点前插入,还是在切点执行后插入等

这些后面都会具体介绍

Android实现AOP,可以使用的方案主要有两个

一个是大神的 https://github.com/JakeWharton/hugo

一个是沪江的 https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx

都是基于 aspectJ 的,所以也可以直接配置aspectJ,不过太麻烦~

我们以Hugo为例,采坑之旅现在开始~


先配置

项目 build.gradle

buildscript {

  repositories {

    mavenCentral()

  }

  dependencies {

    classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'

  }

}

app / build.gradle

apply plugin: 'com.jakewharton.hugo'

代码中可以设置开启

Hugo.setEnabled(true|false)

注意

1.如果有引用module,需要在module中添加以上配置和编写代码

2.不支持lambda

实践~

比如要解决 快速点击打开多页面 的问题

配置好后,开始编写代码~

@Aspect

public class FastClickBlockAspect {

    public static final String TAG = "FastClickBlockAspect";

    @Around("call(* android.content.Context.startActivity(..))")

    public void onStartBefore(ProceedingJoinPoint joinPoint) {

        try {

            if (!ViewUtils.isFastClick()) {

                joinPoint.proceed();

            }

        } catch (Throwable e) {

            e.printStackTrace();

        }

    }

}

这个类文件保存在依赖module(没有就在主app module中)中任意package下就行了。

不用任何配置或其他代码处理,不用修改原有代码~ 然后直接run项目,就可以了~

这样,代码中所有context.startActivity的地方就都会先判断是否是快速点击,然后再执行,达到防止重复打开页面的目标


解释下代码

@Aspect 标注AOP类,表示该类里面是处理切面代码的,固定写法

Advice 切点插入方式。表示在匹配的切点处,用什么方式去处理,一共有如下几个类型

  • @Around 环绕插入。参数为ProceedingJoinPoint,可以手动包裹代码后,在需要的条件中调用参数的方法 proceed() 表示执行目标方法
  • @Before 前置插入。在切点前执行
  • @After 后置插入。在切点后执行
  • @After returning。在返回值之后执行
  • @After throwing。在抛出异常后执行

注意: 只有Around参数是ProceedingJoinPoint,需要调用proceed执行方法,其他的都只是前后插入,不会影响原有代码的执行

所以埋点功能的话我们就可以使用after或before在原有方法前后执行埋点请求;

而防止连续跳转页面,就可以使用Around,然后在判断条件里手动 proceed 调用原方法

Join Point 连接点。表示我们可以插入代码的位置类型,和Pointcuts切点结合使用

  • Method call。方法被调用。结合切点写法:call(方法切点正则)
  • ** Method execution**。方法被执行。结合切点写法:execution(方法切点正则)
  • Constructor call。构造方法被调用。结合切点写法:call(构造方法切点正则)
  • Constructor execution。构造方法被执行。结合切点写法:execution(构造方法切点正则)
  • Field get。属性读取。结合切点写法:get(变量切点正则)
  • Field set。属性设置。结合切点写法:set(变量切点正则)
  • Pre-init。初始化前。结合切点写法:preinitialization(构造方法切点正则)
  • Init。初始化。结合切点写法:initialization(构造方法切点正则)
  • Static init。静态代码块初始化。结合切点写法:staticinitialization(对应代码切点正则)
  • Handler。异常处理。结合切点写法:handle(对应代码切点正则)
  • Advice execution。所有Advice执行。结合切点写法:adviceexecution()

最常用的是 method call 和 execution,一般系统类的方法直接用call,@Around(call(xxx))包裹处理;

如果是自定义方法,希望里面插入,就@Before(execution(xxx))

Poincuts 切点。是一段匹配规则,表示需要切入代码的地方,规则如下
@注解 访问权限 返回值类型 包名.方法名(方法参数)

  • @注解 可选。可以用来匹配指定注解的切点,也可以自定义个注解在需要特殊处理的地方标注
  • 访问权限 可选。就是 public private static 等,不加的话就是全匹配

后面返回值、包名什么的,支持通配符 * .. + 等

  • * 表示匹配任意内容。 比如
    包名中使用。java.*.Date 可以表示 java.sql.Date也可以表示java.utils.Date
    单独使用。返回值如果是 * 表示任意类型返回
    拼接使用。*Dialog 表示匹配任意 XXDialog内容
  • .. 表示匹配任意类型任意数量内容。比如
    包名中使用。com..Utils 表示java任意包以及子包下的 Utils类
    参数中使用。(..)表示匹配任意类型任意数量的参数,也可以(String, ..) 指定第一个,其他的不定
  • + 表示子类。比如
    java..*Model+,表示在java任意包或子包下以Model结尾类的子类

所以翻译下我们之前代码的核心方法部分

@Around("call(* android.content.Context.startActivity(..))")

就是在系统context.startActivity方法调用(call)的时候,环绕插入代码(@Around),

方法内处理具体实现,判断是否是快速点击,如果非快速点击才正常执行ProceedingJoinPoint.proceed()

但代码还有些问题,就是 Context.startActivity并不能包含所有的情况,

还有Activity.startActivity,以及 startActivityForResult等没有覆盖到~ 这里就可以用我们新学习的姿势解决,修改如下

@Aspect

public class FastClickBlockAspect {

    public static final String TAG = "FastClickBlockAspect";

    @Around("call(* android..*.startActivity*(..))")

    public void onStartBefore(ProceedingJoinPoint joinPoint) {

        try {

            if (!ViewUtils.isFastClick()) {

                joinPoint.proceed();

            }

        } catch (Throwable e) {

            e.printStackTrace();

        }

    }

}

方法内不变,修改了匹配切点的规则

@Around("call(* android..*.startActivity*(..))")

解释下就是,在 android.任意包或子包.. 下,的任意类*(可以是Activity、Context或Fragment),

调用该类的方法 startActivity*(包括startActivity方法和startActivityForResult方法)时,进行自定义处理

继续优化,如果希望在打开FragmentDialog的时候,也要防止重复显示,那怎么办,

这时通配符不能包含两个区别较大的切点规则了,我们可以申明多个切点,然后用逻辑符号拼接起来

切点申明很简单,直接用 @Pointcut 申明一个空方法,@Pointcut后也可以直接加上 连接点(切点规则)

多个方法对应多个切点,最后在需要处理的主方法内 @Around(切点规则方法1 || 切点规则方法2) 这样逻辑拼接起来

代码如下

@Aspect

public class FastClickBlockAspect {

    public static final String TAG = "FastClickBlockAspect";

    @Pointcut("execution(* com.archex.core.base.BaseDialogFragment.show(..))")

    public void showBaseDialogFragment() {}

    @Pointcut("call(* android..*.startActivity*(..))")

    public void startActivity() {}

    @Around("showBaseDialogFragment() || startActivity()")

    public void onStartBefore(ProceedingJoinPoint joinPoint) {

        try {

            if (!ViewUtils.isFastClick()) {

                joinPoint.proceed();

            }

        } catch (Throwable e) {

            e.printStackTrace();

        }

    }

}

到此简单使用结束啦~

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

推荐阅读更多精彩内容

  • 生活中,工作中,大家都喜欢那些雷厉风行,干脆利索的人,并且通常会给这种人一种定位叫做:执行力很强。很多人不仅仅喜欢...
    武哥a阅读 301评论 0 0
  • 【转】vim 中代码的折叠和打开 折叠打开与折合选取了折叠方式后,我们就可以对某些代码实施我们需要的折叠了。如果使...
    七点水Plus阅读 383评论 0 0
  • 从来都抱有期待,期待明天如我所想,期待我爱你你也爱我,期待父母健康,期待所有的人正直可爱明辨是非,每一次期待落空,...
    周末保持距离阅读 222评论 0 0
  • 北溟广场。 如今这座因为狩猎战空旷下来的广场,却是因为交流会的到来,再度变成了整个北苍灵院无数视线聚焦的地方。 这...
    混沌天书阅读 163评论 0 0
  • 《秦时明月今犹在》 寒冰 秦时明月今犹在 先人已逝屍骨寒 古今多少辛酸事 不堪回首战犹...
    eceff7a5c042寒冰阅读 1,505评论 0 3