Android 架構(gòu)師之路17 AOP 面向切面編程

Android 架構(gòu)師之路 目錄

引言

相信很多做過Web的同學(xué)對AspectJ都不陌生,Spring的AOP就是基于它而來的括蝠。如果說平常我們隨便寫寫程序的時候斑胜,基本也不會用到它控淡,需要調(diào)試的話無非就是多加一個System.out.printfln()或者Log.d()嫌吠。但是由于基于面向?qū)ο蟮墓逃腥毕荩瑢?dǎo)致很多同模塊掺炭、同一水平上的工作要在許多類中重復(fù)出現(xiàn)辫诅。比如說:輸出日志,監(jiān)控方法執(zhí)行時間竹伸,修改程序運行時的參數(shù)等等這樣的事情泥栖,其實它們的代碼都是可以重用的。

如果在一個大型的項目當(dāng)中勋篓,使用手動修改源碼的方式來達到調(diào)試吧享、監(jiān)控的目的,第一譬嚣,需要插入許多重復(fù)代碼(打印日志钢颂,監(jiān)控方法執(zhí)行時間),代碼無法復(fù)用拜银;第二殊鞭,修改的成本太高,處處需要手動修改(分分鐘累死尼桶、眼花)操灿。

  • OOP: 面向?qū)ο蟀阉械氖挛锒籍?dāng)做對象看待,因此每一個對象都有自己的生命周期泵督,都是一個封裝的整體趾盐。每一個對象都有自己的一套垂直的系列方法和屬性,使得我們使用對象的時候不需要太多的關(guān)系它的內(nèi)部細節(jié)和實現(xiàn)過程小腊,只需要關(guān)注輸入和輸出救鲤,這跟我們的思維方式非常相近,極大的降低了我們的編寫代碼成本(而不像C那樣讓人頭痛V雀浴)本缠。但在現(xiàn)實世界中,并不是所有問題都能完美得劃分到模塊中入问。舉個最簡單而又常見的例子:現(xiàn)在想為每個模塊加上日志功能丹锹,要求模塊運行時候能輸出日志。在不知道AOP的情況下芬失,一般的處理都是:先設(shè)計一個日志輸出模塊楣黍,這個模塊提供日志輸出API,比如Android中的Log類麸折。然后锡凝,其他模塊需要輸出日志的時候調(diào)用Log類的幾個函數(shù),比如e(TAG,…)垢啼,w(TAG,…)窜锯,d(TAG,…)张肾,i(TAG,…)等。

  • AOP: OOP固然開啟另一個編程時代锚扎,但是久而久之也顯露了它的缺點吞瞪,最明顯的一點就是它無法橫向切割某一類方法、屬性驾孔,當(dāng)我們需要了解某一類方法芍秆、某一類屬性的信息時,就必須要在每一個類的方法里面(即便他們是同樣的方法翠勉,只因是不同的類所以不同)添加監(jiān)控代碼妖啥,在代碼量龐大的情況下,這是一個不可取的方法对碌。因此荆虱,AOP編產(chǎn)生了,基于AOP的編程可以讓我們橫向的切割某一類方法和屬性(不需要關(guān)心他是什么類別P嗝恰)怀读,AOP并不是與OOP對立的,而是為了彌補OOP的不足骑脱,因為有了AOP我們的調(diào)試和監(jiān)控就變得簡單清晰菜枷。

1.AspectJ介紹

1.1 AspectJ只是一個代碼編譯器

AspectJ 意思就是Java的Aspect,Java的AOP叁丧。它其實不是一個新的語言啤誊,它就是一個代碼編譯器(ajc,后面以此代替)歹袁,在Java編譯器的基礎(chǔ)上增加了一些它自己的關(guān)鍵字識別和編譯方法坷衍。因此寝优,ajc也可以編譯Java代碼条舔。它在編譯期將開發(fā)者編寫的Aspect程序編織到目標程序中,對目標程序作了重構(gòu)乏矾,目的就是建立目標程序與Aspect程序的連接(耦合孟抗,獲得對方的引用(獲得的是聲明類型,不是運行時類型)和上下文信息)钻心,從而達到AOP的目的(這里在編譯期還是修改了原來程序的代碼凄硼,但是是ajc替我們做的)。

1.2 AspectJ是用來做AOP編程的

Cross-cutting concerns(橫切關(guān)注點): 盡管面向?qū)ο竽P椭写蠖鄶?shù)類會實現(xiàn)單一特定的功能捷沸,但通常也會開放一些通用的附屬功能給其他類摊沉。例如,我們希望在數(shù)據(jù)訪問層中的類中添加日志痒给,同時也希望當(dāng)UI層中一個線程進入或者退出調(diào)用一個方法時添加日志说墨。盡管每個類都有一個區(qū)別于其他類的主要功能骏全,但在代碼里,仍然經(jīng)常需要添加一些相同的附屬功能尼斧。

  • Advice(通知): 注入到class文件中的代碼姜贡。典型的 Advice 類型有 before、after 和 around棺棵,分別表示在目標方法執(zhí)行之前楼咳、執(zhí)行后和完全替代目標方法執(zhí)行的代碼。 除了在方法中注入代碼烛恤,也可能會對代碼做其他修改母怜,比如在一個class中增加字段或者接口。

  • Joint point(連接點): 程序中可能作為代碼注入目標的特定的點缚柏,例如一個方法調(diào)用或者方法入口糙申。

  • Pointcut(切入點): 告訴代碼注入工具,在何處注入一段特定代碼的表達式船惨。例如柜裸,在哪些 joint points 應(yīng)用一個特定的 Advice。切入點可以選擇唯一一個粱锐,比如執(zhí)行某一個方法疙挺,也可以有多個選擇,比如怜浅,標記了一個定義成@DebguTrace 的自定義注解的所有方法铐然。

  • Aspect(切面):Pointcut 和 Advice 的組合看做切面。例如恶座,我們在應(yīng)用中通過定義一個 pointcut 和給定恰當(dāng)?shù)腶dvice搀暑,添加一個日志切面。

  • Weaving(織入): 注入代碼(advices)到目標位置(joint points)的過程跨琳。

下面這張圖簡要總結(jié)了一下上述這些概念自点。



傳統(tǒng)編程:逐個插入驗證用戶模塊


傳統(tǒng)方案

AOP方案:關(guān)注點聚焦
AOP方案
1.3、為什么要用AspectJ脉让?
  • 非侵入式監(jiān)控: 支持編譯期和加載時代碼注入桂敛,可以在不修監(jiān)控目標的情況下監(jiān)控其運行,截獲某類方法溅潜,甚至可以修改其參數(shù)和運行軌跡术唬!
  • 易于使用: 它就是Java,只要會Java就可以用它滚澜。
  • 功能強大粗仓,可拓展性高: 它就是一個編譯器+一個庫,可以讓開發(fā)者最大限度的發(fā)揮,實現(xiàn)形形色色的AOP程序借浊!

2眶掌、下載AspectJ相關(guān)資源與build.gradle配置

2.1、下載地址

下載aspectj的地址http://www.eclipse.org/aspectj/downloads.php

2.2巴碗、解壓aspectj jar包得到aspectjrt.jar
2.3朴爬、build.gradle配置

參考build.gradle aspectJ 寫法 http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/
根目錄中build.gradle配置:

buildscript {
    
    repositories {
        mavenCentral()
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        classpath 'org.aspectj:aspectjtools:1.8.13'
        classpath 'org.aspectj:aspectjweaver:1.8.13'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

modules中build.gradle配置:

apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main


android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.haocai.aopdemo"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

final def log = project.logger
final def variants = project.android.applicationVariants

variants.all { variant ->
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return;
    }

    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)

        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler);
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break;
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break;
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    compile files('libs/aspectjrt.jar')
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
   // compile 'org.aspectj:aspectjrt:1.8.+'
}
注意:

dependencies 不要忘記添加 compile files('libs/aspectjrt.jar') ,aspectjrt.jar就是上一步解壓得到的文件橡淆,放到libs文件夾下

3召噩、示例程序

創(chuàng)建注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface BehaviorTrace {
    String value();
    int type();
}
Aspect 類
/**
 * Created by Xionghu on 2018/1/23.
 * Desc: 切面
 * 你想要切下來的部分(代碼邏輯功能重復(fù)模塊)
 */
@Aspect
public class BehaviorAspect {
    private static final String TAG = "MainAspect";
    SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 根據(jù)切點 切成什么樣子
     *
     */
    @Pointcut("execution(@com.haocai.aopdemo.BehaviorTrace * *(..))")
    public void annoBehavior() {

    }
    /**
     * 切成什么樣子之后,怎么去處理
     *
     */

    @Around("annoBehavior()")
    public Object dealPoint(ProceedingJoinPoint point) throws Throwable{
        //方法執(zhí)行前
        MethodSignature methodSignature = (MethodSignature)point.getSignature();
        BehaviorTrace behaviorTrace = methodSignature.getMethod().getAnnotation(BehaviorTrace.class);
        String contentType = behaviorTrace.value();
        int type = behaviorTrace.type();
        Log.i(TAG,contentType+"使用時間:   "+simpleDateFormat.format(new Date()));
        long beagin=System.currentTimeMillis();
        //方法執(zhí)行時
        Object object = null;
        try{
            object = point.proceed();
        }catch (Exception e){
            e.printStackTrace();
        }

        //方法執(zhí)行完成
        Log.i(TAG,"消耗時間:"+(System.currentTimeMillis()-beagin)+"ms");
        return object;
    }
}

調(diào)用主程序

package com.haocai.aopdemo;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Main";
    SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);





    }
    /**
     * 搖一搖的模塊
     *
     * @param view
     */
    @BehaviorTrace(value = "搖一搖",type = 1)
    public  void mShake(View view)
    {
        //搖一搖的代碼邏輯
        {
            SystemClock.sleep(3000);
            Log.i(TAG," 搖到一個紅包");

        }
    }
    /**
     * 語音的模塊
     *
     * @param view
     */
    @BehaviorTrace(value = "語音:",type = 1)
    public  void mAudio(View view)
    {
        //語音代碼邏輯
        {
            SystemClock.sleep(3000);
            Log.i(TAG,"發(fā)語音:我要到一個紅包啦");
        }
    }
    /**
     * 打字模塊
     *
     * @param view
     */
    @BehaviorTrace(value = "打字:",type = 1)
    public  void mText(View view)
    {
        //打字模塊邏輯
        {
            SystemClock.sleep(3000);
            Log.i(TAG,"打字邏輯逸爵,我搖到了一個大紅包");

        }

    }


//    /**
//     * 搖一搖的模塊
//     *
//     * @param view
//     */
//    @BehaviorTrace(value = "搖一搖",type = 1)
//    public  void mShake(View view)
//    {
//            SystemClock.sleep(3000);
//            Log.i(TAG,"  搖到一個嫩模:  約不約");
//    }
//
//    /**
//     * 搖一搖的模塊
//     *
//     * @param view
//     */
//    public  void mShake(View view)
//    {
//
//        long beagin=System.currentTimeMillis();
//        Log.i(TAG,"搖一搖:  使用時間:   "+simpleDateFormat.format(new Date()));
//        //搖一搖的代碼邏輯
//        {
//            SystemClock.sleep(3000);
//
//            Log.i(TAG," 搖到一個紅包");
//
//        }
//        //事件統(tǒng)計邏輯
//        Log.i(TAG,"消耗時間:  "+(System.currentTimeMillis()-beagin)+"ms");
//
//    }

//    /**
//     * 語音的模塊
//     *
//     * @param view
//     */
//    public  void mAudio(View view)
//    {
//        long beagin=System.currentTimeMillis();
//        Log.i(TAG,"語音:  使用時間:   "+simpleDateFormat.format(new Date()));
//        //語音代碼邏輯
//        {
//            SystemClock.sleep(3000);
//
//            Log.i(TAG,"發(fā)語音:我要到一個紅包啦");
//
//        }
//       //事件統(tǒng)計邏輯
//        Log.i(TAG,"消耗時間:  "+(System.currentTimeMillis()-beagin)+"ms");
//    }
//
//    /**
//     * 打字模塊
//     *
//     * @param view
//     */
//    public  void mText(View view)
//    {
//        //統(tǒng)計用戶行為 的邏輯
//        Log.i(TAG,"文字:  使用時間:   "+simpleDateFormat.format(new Date()));
//        long beagin=System.currentTimeMillis();
//
//        //打字模塊邏輯
//        {
//            SystemClock.sleep(3000);
//            Log.i(TAG,"打字邏輯具滴,我搖到了一個大紅包");
//
//        }
//        //事件統(tǒng)計邏輯
//        Log.i(TAG,"消耗時間:  "+(System.currentTimeMillis()-beagin)+"ms");
//    }

}

注意:下面注釋部分為傳統(tǒng)寫法
運行結(jié)果
01-23 19:39:09.579 13051-13051/com.haocai.aopdemo I/MainAspect: 搖一搖使用時間:   2018-01-23 19:39:09
01-23 19:39:12.589 13051-13051/com.haocai.aopdemo I/Main:  搖到一個紅包
01-23 19:39:12.589 13051-13051/com.haocai.aopdemo I/MainAspect: 消耗時間:3001ms
01-23 19:39:12.589 13051-13051/com.haocai.aopdemo I/MainAspect: 語音:使用時間:   2018-01-23 19:39:12
01-23 19:39:15.599 13051-13051/com.haocai.aopdemo I/Main: 發(fā)語音:我要到一個紅包啦
01-23 19:39:15.599 13051-13051/com.haocai.aopdemo I/MainAspect: 消耗時間:3000ms
01-23 19:39:15.599 13051-13051/com.haocai.aopdemo I/MainAspect: 打字:使用時間:   2018-01-23 19:39:15
01-23 19:39:18.609 13051-13051/com.haocai.aopdemo I/Main: 打字邏輯,我搖到了一個大紅包
01-23 19:39:18.609 13051-13051/com.haocai.aopdemo I/MainAspect: 消耗時間:3000ms
源碼下載
Github:https://github.com/kpioneer123/AopDemo
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末师倔,一起剝皮案震驚了整個濱河市构韵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趋艘,老刑警劉巖疲恢,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瓷胧,居然都是意外死亡显拳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門搓萧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杂数,“玉大人,你說我怎么就攤上這事瘸洛∽嵋疲” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵反肋,是天一觀的道長那伐。 經(jīng)常有香客問我,道長囚玫,這世上最難降的妖魔是什么喧锦? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任读规,我火速辦了婚禮抓督,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘束亏。我一直安慰自己铃在,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著定铜,像睡著了一般阳液。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上揣炕,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天帘皿,我揣著相機與錄音,去河邊找鬼畸陡。 笑死鹰溜,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的丁恭。 我是一名探鬼主播曹动,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼牲览!你這毒婦竟也來了墓陈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤第献,失蹤者是張志新(化名)和其女友劉穎贡必,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庸毫,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡赊级,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了岔绸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片理逊。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盒揉,靈堂內(nèi)的尸體忽然破棺而出晋被,到底是詐尸還是另有隱情,我是刑警寧澤刚盈,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布羡洛,位于F島的核電站,受9級特大地震影響藕漱,放射性物質(zhì)發(fā)生泄漏欲侮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一肋联、第九天 我趴在偏房一處隱蔽的房頂上張望威蕉。 院中可真熱鬧,春花似錦橄仍、人聲如沸韧涨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虑粥。三九已至如孝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間娩贷,已是汗流浹背第晰。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留彬祖,地道東北人但荤。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像涧至,于是被迫代替她去往敵國和親腹躁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容