Android 面向切面編程 AOP 解決連續點擊打開重複頁面問題

先介紹概念java

好比我但願在全部頁面啓動的時候加一個埋點~ 但願在全部按鈕點擊的時候加個快速重複點擊的判斷~等等 這樣在項目中同一種類型的全部代碼處,統一加入邏輯處理的方法,叫作 面向切面編程 AOPandroid

而這些咱們須要插入代碼的具體位置,則叫作切點 Pointcut,好比我在某些類的某個方法中插入git

項目中能夠插入地方的類型,叫作鏈接點 Join Point,好比我能夠在方法中插入,能夠在變量取值時插入github

插入的方式 Advice,可讓咱們指定在切點前插入,仍是在切點執行後插入等sql

這些後面都會具體介紹編程

Android實現AOP,可使用的方案主要有兩個app

一個是大神的 github.com/JakeWharton…maven

一個是滬江的 github.com/HujiangTech…學習

都是基於 aspectJ 的,因此也能夠直接配置aspectJ,不過太麻煩~gradle

咱們以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();

        }

    }

}
複製代碼

到此簡單使用就結束啦~

相關文章
相關標籤/搜索