先介紹概念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();
}
}
}
複製代碼
到此簡單使用就結束啦~