Android 進(jìn)階 AOP的應(yīng)用 - AspectJ 的使用

在了解AOP直線需要先了解下注釋,可以先看下我的另一篇文章
android注解的使用

AOP(Aspect Oriented Programming)是面向切面編程钮追,AOP和我們平時接觸的OOP編程是不同的編程思想柿祈,OOP是面向?qū)ο缶幊塘荒辏岢氖菍⒐δ苣K化,對象化惩系。而AOP的思想則是提倡針對同一類問題統(tǒng)一處理刻诊,當(dāng)然防楷,我們在實(shí)際編程過程中,不可能單純的AOP或者OOP的思想來編程则涯,很多時候复局,可能會混合多種編程思想。

AOP的定義

把某一方面的一些功能提取出來與一批對象進(jìn)行隔離粟判,提取之后我們就可以對某個單方面的功能進(jìn)行編程亿昏。

AOP的套路

把眾多方法中的所有共有代碼全部抽取出來,放置到某個地方集中管理档礁,然后在具體運(yùn)行時角钩,再由容器動態(tài)織入這些共有代碼的話,最起碼可以解決兩個問題:
1.1 Android程序員在編寫具體的業(yè)務(wù)邏輯處理方法時呻澜,只需關(guān)心核心的業(yè)務(wù)邏輯處理递礼,既提高了工作效率,又使代碼變更簡潔優(yōu)雅羹幸。
1.2 在日后的維護(hù)中由于業(yè)務(wù)邏輯代碼與共有代碼分開存放脊髓,而且共有代碼是集中存放的,因此使維護(hù)工作變得簡單輕松栅受。
1.3 面向切面編程AOP技術(shù)就是為解決這個問題而誕生的将硝,切面就是橫切面,代表的是一個普遍存在的共有功能屏镊,例如依疼,日志切面、權(quán)限切面及事務(wù)切面等而芥。

AOP的作用

一般來說律罢,主要用于不想侵入原有代碼的場景中,例如SDK需要無侵入的在宿主中插入一些代碼棍丐,做日志埋點(diǎn)弟翘、性能監(jiān)控、動態(tài)權(quán)限控制骄酗、甚至是代碼調(diào)試等等。
AOP的使用目前在android方面通常都是用eclipse的AspectJ
另外一個比較成功的使用AOP的庫是Jake大神的Hugo:
Jake大神的Hugo

[aspectj下載地址](http://www.eclipse.org/downloads/download.php?file=/tools/aspectj/aspectj-1.8.10.jar
在網(wǎng)上還發(fā)現(xiàn)一個github的庫悦冀,據(jù)說竭誠了aspectj
https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx

AspectJ 之 Join Points介紹

Join Points在AspectJ中是關(guān)鍵的概念趋翻。Join Points可以看做是程序運(yùn)行時的一個執(zhí)行點(diǎn),比如:一個函數(shù)的調(diào)用可以看做是個Join Points,相當(dāng)于代碼切入點(diǎn)盒蟆。但在AspectJ中踏烙,只有下面幾種執(zhí)行點(diǎn)是認(rèn)為是Join Points:

Join Points 說明 實(shí)例
method call 函數(shù)調(diào)用 比如調(diào)用Log.e()师骗,這是一個個Join Point
method execution 函數(shù)執(zhí)行 比如Log.e()的執(zhí)行內(nèi)部,是一處Join Points讨惩。注意這里是函數(shù)內(nèi)部
constructor call 構(gòu)造函數(shù)調(diào)用 和method call 類似
constructor execution 構(gòu)造函數(shù)執(zhí)行 和method execution 類似
field get 獲取某個變量 比如讀取DemoActivity.debug成員
field set 設(shè)置某個變量 比如設(shè)置DemoActivity.debug成員
pre-initialization Object在構(gòu)造函數(shù)中做的一些工作辟癌。 -
initialization Object在構(gòu)造函數(shù)中做的工作。 -
static initialization 類初始化 比如類的static{}
handler 異常處理 比如try catch 中荐捻,對應(yīng)catch內(nèi)的執(zhí)行
advice execution 這個是AspectJ 的內(nèi)容 -

Pointcuts 介紹

一個程序會有多個Join Points,即使同一個函數(shù)黍少,也還分為call 和 execution 類型的Join Points,但并不是所有的Join Points 都是我們關(guān)心的处面,Pointcuts 就是提供一種使得開發(fā)者能夠值選擇所需的JoinPoints的方法厂置。

Advice介紹

Advice就是我們插入的代碼可以以何種方式插入,有Before 還有 After魂角、Around昵济。
下面看個例子:

    /**
     * 找到處理的切點(diǎn)
     * * *(..)  可以處理所有的方法
     */
    @Pointcut("execution(@com.liuy.architect_day02.CheckNet * *(..))")
    public void checkNetBehavior() {

    }

這里會分成好幾個部分野揪,我們依次來看:

  • @Before: Advice, 也就是具體的插入點(diǎn)
  • execution:處理Join Point的類型,例如call斯稳、execution
  • (* android.app.Activity.on(..)): 這個是最重要的表達(dá)式,第一個表示返回值平挑,表示返回值為任意類型游添,后面這個就是典型的包名路徑,其中可以包含 *來進(jìn)行通配唆涝,幾個 *沒有區(qū)別唇辨。同時這里可以通過&&廊酣、||、赏枚!來進(jìn)行條件組合亡驰。()代表這個方法的參數(shù)饿幅,你可以指定類型凡辱,例如android.os.Bundle,或者 (..) 這樣來代表任意類型栗恩、任意個數(shù)的參數(shù)。
  • public void checkNetBehavior: 實(shí)際切入的代碼乳乌。

Before 和 After 其實(shí)還是很好理解的,也就是在Pointcuts之前和之后汉操,插入代碼,那么Android呢磷瘤,從字面含義上來講,也就是在方法前后各插入代碼梭伐,他包含了 Before和 After 的全部功能仰担,代碼如下:

@(“execution(* com.example.andorid.MainActivity.testAOP()))”)
public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
    String key = proceedingJoinPoint.getSignature().toString();
    Log.d(TAG,”onActivityMethodAroundFirst:”+key);
    proceedingJoinPoint.proceed();
    Log.d(TAG,”onActivityMethodAroundSecond:”+key);
}

以上代碼中,proceedingJoinPoint.proceed()代表執(zhí)行原始的方法摔蓝,在這之前、之后拌滋,都可以進(jìn)行各種邏輯處理猜谚。

自定義Pointcuts

自定義Pointcuts可以讓我們更加精準(zhǔn)的切入一個或多個指定的切入點(diǎn)。
首先我們要定義一個注解類

@Target(ElementType.METHOD) // Target 放在哪個位置
@Retention(RetentionPolicy.RUNTIME)// RUNTIME 運(yùn)行時 xUtils  CLASS 代表編譯時期 ButterKnife   SOURCE 代表資源
public @interface CheckNet { // @interface 注解

}

在需要插入代碼的地方加入這個注解魏铅,例如在MainActivity中加入:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @CheckNet
    public void click(View view) {
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
    }

    @CheckNet
    public void postData() {

    }
}

最后創(chuàng)建切入代碼,我這里的例子是判斷網(wǎng)絡(luò)是否可用的

@Aspect
public class SectionAspect {

    /**
     * 找到處理的切點(diǎn)
     * * *(..)  可以處理所有的方法
     */
    @Pointcut("execution(@com.darren.architect_day02.CheckNet * *(..))")
    public void checkNetBehavior() {

    }

    /**
     * 處理切面
     */
    @Around("checkNetBehavior()")
    public Object checkNet(ProceedingJoinPoint joinPoint) throws Throwable {
        Log.e("TAG", "checkNet");
        // 做埋點(diǎn)  日志上傳  權(quán)限檢測(我寫的,RxPermission , easyPermission) 網(wǎng)絡(luò)檢測
        // 網(wǎng)絡(luò)檢測
        // 1.獲取 CheckNet 注解  NDK  圖片壓縮  C++ 調(diào)用Java 方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        CheckNet checkNet = signature.getMethod().getAnnotation(CheckNet.class);
        if (checkNet != null) {
            // 2.判斷有沒有網(wǎng)絡(luò)  怎么樣獲取 context?
            Object object = joinPoint.getThis();// View Activity Fragment 览芳; getThis() 當(dāng)前切點(diǎn)方法所在的類
            Context context = getContext(object);
            if (context != null) {
                if (!isNetworkAvailable(context)) {
                    // 3.沒有網(wǎng)絡(luò)不要往下執(zhí)行
                    Toast.makeText(context,"請檢查您的網(wǎng)絡(luò)",Toast.LENGTH_LONG).show();
                    return null;
                }
            }
        }
        return joinPoint.proceed();
    }

    /**
     * 通過對象獲取上下文
     *
     * @param object
     * @return
     */
    private Context getContext(Object object) {
        if (object instanceof Activity) {
            return (Activity) object;
        } else if (object instanceof Fragment) {
            Fragment fragment = (Fragment) object;
            return fragment.getActivity();
        } else if (object instanceof View) {
            View view = (View) object;
            return view.getContext();
        }
        return null;
    }

    /**
     * 檢查當(dāng)前網(wǎng)絡(luò)是否可用
     *
     * @return
     */
    private static boolean isNetworkAvailable(Context context) {
        // 獲取手機(jī)所有連接管理對象(包括對wi-fi,net等連接的管理)
        ConnectivityManager connectivityManager = (ConnectivityManager)
                context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivityManager != null) {
            // 獲取NetworkInfo對象
            NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo();

            if (networkInfo != null && networkInfo.length > 0) {
                for (int i = 0; i < networkInfo.length; i++) {
                    // 判斷當(dāng)前網(wǎng)絡(luò)狀態(tài)是否為連接狀態(tài)
                    if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}

execution語法

語法結(jié)構(gòu):execution([修飾符] 返回值類型 方法名(參數(shù))⌒崩选[異常模式]) 實(shí)例
execution(public .(..)) 所有的public方法
execution(* hello(..)) 所有的hello()方法
execution(String hello(..)) 所有返回值為String的hello方法沧竟。
execution(* hello(String)) 所有參數(shù)為String類型的hello()
execution(* hello(String..)) 至少有一個參數(shù),且第一個參數(shù)類型為String的hello方法
execution(* com.aspect..*(..)) 所有com.aspect包杈笔,以及子孫包下的所有方法
execution(* com...Dao.find*(..)) com包下的所有一Dao結(jié)尾的類的一find開頭的方法

call和execution的區(qū)別

call為調(diào)用糕非,而execution為執(zhí)行敦第。

3 public class HelloWorld {
4   public static void main(int i){
5       System.out.println("in the main method  i = " + i);
6   }
7   
8   public static void main(String[] args) {
9       main(5);
10  }

我們攔截了參數(shù)為:int的main方法店量【铣剩 這里用到了一內(nèi)置的對象:thisJoinPoint,他表示當(dāng)前jionPoint. 跟我們在java中的this 其實(shí)是差不多的,如果你不明白蚁吝,那么你多運(yùn)行一下,好好體會一下怀伦。getSourceLocation()表示源代碼的位置:

public aspect HelloAspect {
 
    pointcut HelloWorldPointCut() : call(* main(int));
    
    
    
   before() : HelloWorldPointCut(){
      System.out.println("Entering : " + thisJoinPoint.getSourceLocation());
    }
}

我們運(yùn)行一下HelloWorld.java山林。
打印結(jié)果為

Entering : HelloWorld.java:9
in the main method  i = 5

9是行號
接下來我們把call 換成execution打印

public aspect HelloAspect {
 
    pointcut HelloWorldPointCut() : execution(* main(int));
    
    
    
   before() : HelloWorldPointCut(){
      System.out.println("Entering : " + thisJoinPoint.getSourceLocation());
    }
}

Entering : HelloWorld.java:4
in the main method  i = 5

結(jié)果為4

從上面的結(jié)果可以看出,call是調(diào)用的地方桑孩,execution是執(zhí)行的地方框冀。
thisJoinPoint.getSourceLocation() 這段代碼將會在我們以后的Demo中經(jīng)常用到。這是一個跟蹤調(diào)試的好辦法明也。

within 和 withincode

within

還是上面的例子,如果別的類中也有main方法绣硝?應(yīng)該怎么辦帆吻?你首先想到的肯定是修改pointcut,指定到我們的HelloWorld類猜煮。 這當(dāng)然是可以的淑蔚,假設(shè):現(xiàn)在還有5個類愕撰,也有main方法醋寝,也需要攔截带迟。那你這解決辦法肯定就不行了。(當(dāng)然你也可以用 || 來組合他們)仓犬。這個時候就用到了我們的within了。代碼如下

    pointcut HelloWorldPointCut() : execution(* main(..)) && !within(HelloAspectDemo);
    
   before() : HelloWorldPointCut(){
      System.out.println("Entering : " + thisJoinPoint.getSourceLocation());

withincode

withincode與within相似窘面,不過withcode()接受的signature是方法叽躯,而不是類。用法点骑,意思都差不多,只不過是使用場合不同鲸鹦。

public class MainActivity extends AppCompatActivity {
    final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        register1();
        register2();
        register3();
    }

    public void registerTest() {
        Log.e(TAG, "execute registerTest");
    }

    public void register1(){
        registerTest();
    }

    public void register2(){
        registerTest();
    }

    public void register3(){
        registerTest();
    }
}

以上三個register方法都調(diào)用了registerTest()方法跷跪,如果這個時候想調(diào)用register3的registerTest()的方法,需要如下操作

@Pointcut("(call(* *..registerTest()))&&withincode(* *..register2())")
public void invokeregisterTestInregister2() {
}

@Before("invokeregisterTestInregister2()")
public void beforeInvokeregisterTestInregister2(JoinPoint joinPoint) throws Throwable {
    Log.e(TAG, "method:" + getMethodName(joinPoint).getName());
}

private MethodSignature getMethodName(JoinPoint joinPoint) {
    if (joinPoint == null) return null;
    return (MethodSignature) joinPoint.getSignature();
}

輸出如下

execute registerTest
method:registerTest
execute registerTest
execute registerTest
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末葛菇,一起剝皮案震驚了整個濱河市橡羞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌莺债,老刑警劉巖签夭,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異措拇,居然都是意外死亡慎宾,警方通過查閱死者的電腦和手機(jī)浅悉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門券犁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人粘衬,你說我怎么就攤上這事。” “怎么了等缀?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵尺迂,是天一觀的道長。 經(jīng)常有香客問我噪裕,道長,這世上最難降的妖魔是什么召衔? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任祭陷,我火速辦了婚禮,結(jié)果婚禮上兵志,老公的妹妹穿的比我還像新娘。我一直安慰自己悠栓,他們只是感情好按价,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著腥沽,像睡著了一般鸠蚪。 火紅的嫁衣襯著肌膚如雪师溅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天墓臭,我揣著相機(jī)與錄音妖谴,去河邊找鬼。 笑死嗡载,一個胖子當(dāng)著我的面吹牛仍稀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播技潘,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼铲掐!你這毒婦竟也來了值桩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤斯入,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后刻两,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滴某,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年户誓,在試婚紗的時候發(fā)現(xiàn)自己被綠了幕侠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡悼潭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舰褪,到底是詐尸還是另有隱情,我是刑警寧澤占拍,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布晃酒,位于F島的核電站,受9級特大地震影響贝次,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浊闪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一搁宾、第九天 我趴在偏房一處隱蔽的房頂上張望倔幼。 院中可真熱鬧,春花似錦损同、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至伶贰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間黍衙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工位仁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人障癌。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓涛浙,卻偏偏與公主長得像康辑,于是被迫代替她去往敵國和親轿亮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355