Android中如何實現(xiàn)面向切面編程-創(chuàng)新互聯(lián)

這篇“Android中如何實現(xiàn)面向切面編程”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Android中如何實現(xiàn)面向切面編程”文章吧。

創(chuàng)新互聯(lián)建站專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站制作、成都網(wǎng)站設(shè)計、達(dá)日網(wǎng)絡(luò)推廣、微信小程序開發(fā)、達(dá)日網(wǎng)絡(luò)營銷、達(dá)日企業(yè)策劃、達(dá)日品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們大的嘉獎;創(chuàng)新互聯(lián)建站為所有大學(xué)生創(chuàng)業(yè)者提供達(dá)日建站搭建服務(wù),24小時服務(wù)熱線:028-86922220,官方網(wǎng)址:muchs.cn

1、AOP的概念

如果你用java做過后臺開發(fā),那么你一定知道AOP這個概念。如果不知道也無妨,套用百度百科的介紹,也能讓你明白這玩意是干什么的:

AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。AOP是OOP的延續(xù),是軟件開發(fā)中的一個熱點,也是Spring框架中的一個重要內(nèi)容,是函數(shù)式編程的一種衍生范型。利用AOP可以對業(yè)務(wù)邏輯的各個部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發(fā)的效率。


2、項目場景

項目開發(fā)過程中,可能會有這樣的需求,需要我們在方法執(zhí)行完成后,記錄日志(后臺開發(fā)中比較常見~),或是計算這個方法的執(zhí)行時間,在不使用AOP的情況下,我們可以在方法最后調(diào)用另一個專門記錄日志的方法,或是在方法體的首尾分別獲取時間,然后通過計算時間差來計算整個方法執(zhí)行所消耗的時間,這樣也可以完成需求。那如果不只一個方法要這么玩怎么辦?每個方法都寫上一段相同的代碼嗎?后期處理邏輯變了要怎么辦?最后老板說這功能不要了我們還得一個個刪除?

很明顯,這是不可能的,我們不僅僅是代碼的搬運工,我們還是有思考能力的軟件開發(fā)工程師。這么low的做法絕對不干,這種問題我們完全可以用AOP來解決,不就是在方法前和方法后插入一段代碼嗎?AOP分分鐘搞定。

3、AOP的實現(xiàn)方式

要注意了,AOP僅僅只是個概念,實現(xiàn)它的方式(工具和庫)有以下幾種:

  1. AspectJ: 一個 JavaTM 語言的面向切面編程的無縫擴(kuò)展(適用Android)。

  2. Javassist for Android: 用于字節(jié)碼操作的知名 java 類庫 Javassist 的 Android 平臺移植版。

  3. DexMaker: Dalvik 虛擬機(jī)上,在編譯期或者運行時生成代碼的 Java API。

  4. ASMDEX: 一個類似 ASM 的字節(jié)碼操作庫,運行在Android平臺,操作Dex字節(jié)碼。

本篇的主角就是AspectJ,下面就來看看AspectJ方式的AOP如何在Android開發(fā)中進(jìn)行使用吧。

二、AspectJ的引入

對于eclipse與Android Studio的引入是不一樣的,本篇只介紹Android Studio如何引入AspectJ,eclipse請自行百度。Android Studio需要在app模塊的build.gradle文件中引入,總共分為3個步驟:

1)添加核心依賴

dependencies {
  ...
  compile 'org.aspectj:aspectjrt:1.8.9'
}

2)編寫gradle編譯腳本

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'org.aspectj:aspectjtools:1.8.9'
    classpath 'org.aspectj:aspectjweaver:1.8.9'
  }
}

AspectJ需要依賴maven倉庫。


3)添加gradle任務(wù)

dependencies {
  ...
}
// 貼上面那段沒用的代碼是為了說明:下面的任務(wù)代碼與dependencies同級

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
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;
      }
    }
  }
}

直接粘貼到build.gradle文件的末尾即可,不要嵌套在別的指令中。


三、AOP的基本知識

在使用AspectJ之前,還是需要先介紹下AOP的基本知識,熟悉的看官可以跳過這部分。

1、AOP術(shù)語

  1. 通知、增強(qiáng)處理(Advice):就是你想要的功能,也就是上面說的日志、耗時計算等。

  2. 連接點(JoinPoint):允許你通知(Advice)的地方,那可就真多了,基本每個方法的前、后(兩者都有也行),或拋出異常是時都可以是連接點(spring只支持方法連接點)。AspectJ還可以讓你在構(gòu)造器或?qū)傩宰⑷霑r都行,不過一般情況下不會這么做,只要記住,和方法有關(guān)的前前后后都是連接點。

  3. 切入點(Pointcut):上面說的連接點的基礎(chǔ)上,來定義切入點,你的一個類里,有15個方法,那就有十幾個連接點了對吧,但是你并不想在所有方法附件都使用通知(使用叫織入,下面再說),你只是想讓其中幾個,在調(diào)用這幾個方法之前、之后或者拋出異常時干點什么,那么就用切入點來定義這幾個方法,讓切點來篩選連接點,選中那幾個你想要的方法。

  4. 切面(Aspect):切面是通知和切入點的結(jié)合?,F(xiàn)在發(fā)現(xiàn)了吧,沒連接點什么事,連接點就是為了讓你好理解切點搞出來的,明白這個概念就行了。通知說明了干什么和什么時候干(什么時候通過before,after,around等AOP注解就能知道),而切入點說明了在哪干(指定到底是哪個方法),這就是一個完整的切面定義。

  5. 織入(weaving) 把切面應(yīng)用到目標(biāo)對象來創(chuàng)建新的代理對象的過程。

  6. 引入(introduction) 允許我們向現(xiàn)有的類添加新方法屬性。這不就是把切面(也就是新方法屬性:通知定義的)用到目標(biāo)類中嗎

  7. 目標(biāo)(target) 引入中所提到的目標(biāo)類,也就是要被通知的對象,也就是真正的業(yè)務(wù)邏輯,他可以在毫不知情的情況下,被咋們織入切面。二自己專注于業(yè)務(wù)本身的邏輯。

  8. 代理(proxy) 怎么實現(xiàn)整套AOP機(jī)制的,都是通過代理,這個一會兒給細(xì)說。

  9. 目標(biāo)對象 – 項目原始的Java組件。

  10. AOP代理  – 由AOP框架生成java對象。

  11. AOP代理方法 = advice + 目標(biāo)對象的方法。

2、AOP注解與使用

  1. @Aspect:聲明切面,標(biāo)記類

  2. @Pointcut(切點表達(dá)式):定義切點,標(biāo)記方法

  3. @Before(切點表達(dá)式):前置通知,切點之前執(zhí)行

  4. @Around(切點表達(dá)式):環(huán)繞通知,切點前后執(zhí)行

  5. @After(切點表達(dá)式):后置通知,切點之后執(zhí)行

  6. @AfterReturning(切點表達(dá)式):返回通知,切點方法返回結(jié)果之后執(zhí)行

  7. @AfterThrowing(切點表達(dá)式):異常通知,切點拋出異常時執(zhí)行

  8. @Pointcut、@Before、@Around、@After、@AfterReturning、@AfterThrowing需要在切面類中使用,即在使用@Aspect的類中。

1)切點表達(dá)式是什么?

這就是切點表達(dá)式:execution (* com.lqr..*.*(..))。切點表達(dá)式的組成如下:

execution(<修飾符模式>? <返回類型模式> <方法名模式>(<參數(shù)模式>) <異常模式>?)
除了返回類型模式、方法名模式和參數(shù)模式外,其它項都是可選的。

修飾符模式指的是public、private、protected,異常模式指的是NullPointException等。


對于切點表達(dá)式的理解不是本篇重點,下面列出幾個例子說明一下就好了:

@Before("execution(public * *(..))")
public void before(JoinPoint point) {
  System.out.println("CSDN_LQR");
}

匹配所有public方法,在方法執(zhí)行之前打印"CSDN_LQR"。


@Around("execution(* *to(..))")
public void around(ProceedingJoinPoint joinPoint) {
  System.out.println("CSDN");
  joinPoint.proceed();
  System.out.println("LQR");
}

匹配所有以"to"結(jié)尾的方法,在方法執(zhí)行之前打印"CSDN",在方法執(zhí)行之后打印"LQR"。


@After("execution(* com.lqr..*to(..))")
public void after(JoinPoint point) {
  System.out.println("CSDN_LQR");
}

匹配com.lqr包下及其子包中以"to"結(jié)尾的方法,在方法執(zhí)行之后打印"CSDN_LQR"。


@AfterReturning("execution(int com.lqr.*(..))")
public void afterReturning(JoinPoint point, Object returnValue) {
  System.out.println("CSDN_LQR");
}

匹配com.lqr包下所有返回類型是int的方法,在方法返回結(jié)果之后打印"CSDN_LQR"。


@AfterThrowing(value = "execution(* com.lqr..*(..))", throwing = "ex")
public void afterThrowing(Throwable ex) {
  System.out.println("ex = " + ex.getMessage());
}

匹配com.lqr包及其子包中的所有方法,當(dāng)方法拋出異常時,打印"ex = 報錯信息"。


2)@Pointcut的使用

@Pointcut是專門用來定義切點的,讓切點表達(dá)式可以復(fù)用。

你可能需要在切點執(zhí)行之前和切點報出異常時做些動作(如:出錯時記錄日志),可以這么做:

@Before("execution(* com.lqr..*(..))")
public void before(JoinPoint point) {
  System.out.println("CSDN_LQR");
}

@AfterThrowing(value = "execution(* com.lqr..*(..))", throwing = "ex")
public void afterThrowing(Throwable ex) {
  System.out.println("記錄日志");
}

可以看到,表達(dá)式是一樣的,那要怎么重用這個表達(dá)式呢?這就需要用到@Pointcut注解了,@Pointcut注解是注解在一個空方法上的,如:

@Pointcut("execution(* com.lqr..*(..))")
public void pointcut() {}

這時,"pointcut()"就等價于"execution(* com.lqr..*(..))",那么上面的代碼就可以這么改了:

@Before("pointcut()")
public void before(JoinPoint point) {
  System.out.println("CSDN_LQR");
}

@AfterThrowing(value = "pointcut()", throwing = "ex")
public void afterThrowing(Throwable ex) {
  System.out.println("記錄日志");
}

四、實戰(zhàn)

經(jīng)過上面的學(xué)習(xí),下面是時候?qū)崙?zhàn)一下了,這里我們來一個簡單的例子。

1、切點

這是界面上一個按鈕的點擊事件,就是一個簡單的方法而已,我們拿它來試刀。

public void test(View view) {
  System.out.println("Hello, I am CSDN_LQR");
}

2、切面類

要織入一段代碼到目標(biāo)類方法的前前后后,必須要有一個切面類,下面就是切面類的代碼:

@Aspect
public class TestAnnoAspect {

  @Pointcut("execution(* com.lqr.androidaopdemo.MainActivity.test(..))")
  public void pointcut() {

  }  

  @Before("pointcut()")
  public void before(JoinPoint point) {
    System.out.println("@Before");
  }

  @Around("pointcut()")
  public void around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("@Around");
  }

  @After("pointcut()")
  public void after(JoinPoint point) {
    System.out.println("@After");
  }

  @AfterReturning("pointcut()")
  public void afterReturning(JoinPoint point, Object returnValue) {
    System.out.println("@AfterReturning");
  }

  @AfterThrowing(value = "pointcut()", throwing = "ex")
  public void afterThrowing(Throwable ex) {
    System.out.println("@afterThrowing");
    System.out.println("ex = " + ex.getMessage());
  }
}

3、各通知的執(zhí)行結(jié)果

先來試試看,這幾個注解的執(zhí)行結(jié)果如何。

Android中如何實現(xiàn)面向切面編程

不對啊,按鈕的點擊事件中有打印"Hello, I am CSDN_LQR"的,這里沒有,怎么肥事?

這里因為@Around環(huán)繞通知會攔截原方法內(nèi)容的執(zhí)行,我們需要手動放行才可以。代碼修改如下:

@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
  System.out.println("@Around");
  joinPoint.proceed();// 目標(biāo)方法執(zhí)行完畢
}

Android中如何實現(xiàn)面向切面編程

也不對啊,少了一個@AfterThrowing通知。這個通知只有在切點拋出異常時才會執(zhí)行,我們可以讓代碼出現(xiàn)一個簡單的運行時異常:

public void test(View view) {
  System.out.println("Hello, I am CSDN_LQR");
  int a = 1 / 0;
}

Android中如何實現(xiàn)面向切面編程

這下@AfterThrowing通知確實被調(diào)用了,而且也打印出了錯誤信息(divide by zero)。但@AfterReturning通知反而不執(zhí)行了,原因很簡單,都拋出異常了,切點肯定是不能返回結(jié)果的。也就是說:@AfterThrowing通知與@AfterReturning通知是沖突的,在同個切點上不可能同時出現(xiàn)。

4、方法耗時計算的實現(xiàn)

因為@Around是環(huán)繞通知,可以在切點的前后分別執(zhí)行一些操作,AspectJ為了能肯定操作是在切點前還是在切點后,所以在@Around通知中需要手動執(zhí)行joinPoint.proceed()來確定切點已經(jīng)執(zhí)行,故在joinPoint.proceed()之前的代碼會在切點執(zhí)行前執(zhí)行,在joinPoint.proceed()之后的代碼會切點執(zhí)行后執(zhí)行。于是,方法耗時計算的實現(xiàn)就是這么簡單:

@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
  long beginTime = SystemClock.currentThreadTimeMillis();
  joinPoint.proceed();
  long endTime = SystemClock.currentThreadTimeMillis();
  long dx = endTime - beginTime;
  System.out.println("耗時:" + dx + "ms");
}

5、JoinPoint的作用

發(fā)現(xiàn)沒有,上面所有的通知都會至少攜帶一個JointPoint參數(shù),這個參數(shù)包含了切點的所有信息,下面就結(jié)合按鈕的點擊事件方法test()來解釋joinPoint能獲取到的方法信息有哪些:

MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String name = signature.getName(); // 方法名:test
Method method = signature.getMethod(); // 方法:public void com.lqr.androidaopdemo.MainActivity.test(android.view.View)
Class returnType = signature.getReturnType(); // 返回值類型:void
Class declaringType = signature.getDeclaringType(); // 方法所在類名:MainActivity
String[] parameterNames = signature.getParameterNames(); // 參數(shù)名:view
Class[] parameterTypes = signature.getParameterTypes(); // 參數(shù)類型:View

6、注解切點

前面的切點表達(dá)式結(jié)構(gòu)是這樣的:

execution(<修飾符模式>? <返回類型模式> <方法名模式>(<參數(shù)模式>) <異常模式>?)


但實際上,上面的切點表達(dá)式結(jié)構(gòu)并不完整,應(yīng)該是這樣的:

execution(<@注解類型模式>? <修飾符模式>? <返回類型模式> <方法名模式>(<參數(shù)模式>) <異常模式>?)


這就意味著,切點可以用注解來標(biāo)記了。

1)自定義注解

如果用注解來標(biāo)記切點,一般會使用自定義注解,方便我們拓展。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnoTrace {
  String value();
  int type();
}

@Target(ElementType.METHOD):表示該注解只能注解在方法上。如果想類和方法都可以用,那可以這么寫:@Target({ElementType.METHOD,ElementType.TYPE}),依此類推。


@Retention(RetentionPolicy.RUNTIME):表示該注解在程序運行時是可見的(還有SOURCE、CLASS分別指定注解對于那個級別是可見的,一般都是用RUNTIME)。


其中的value和type是自己拓展的屬性,方便存儲一些額外的信息。

2)使用自定義注解標(biāo)記切點

這個自定義注解只能注解在方法上(構(gòu)造方法除外,構(gòu)造方法也叫構(gòu)造器,需要使用ElementType.CONSTRUCTOR),像平常使用其它注解一樣使用它即可:

@TestAnnoTrace(value = "lqr_test", type = 1)
public void test(View view) {
  System.out.println("Hello, I am CSDN_LQR");
}

3)注解的切點表達(dá)式

既然用注解來標(biāo)記切點,那么切點表達(dá)式肯定是有所不同的,要這么寫:

@Pointcut("execution(@com.lqr.androidaopdemo.TestAnnoTrace * *(..))")
public void pointcut() {}

切點表達(dá)式使用注解,一定是@+注解全路徑,如:@com.lqr.androidaopdemo.TestAnnoTrace。


親測可用 ,不貼圖了。

4)獲取注解屬性值

上面在編寫自定義注解時就聲明了兩個屬性,分別是value和type,而且在使用該注解時也都為之賦值了,那怎么在通知中獲取這兩個屬性值呢?還記得JoinPoint這個參數(shù)吧,它就可以獲取到注解中的屬性值,如下所示:

MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 通過Method對象得到切點上的注解
TestAnnoTrace annotation = method.getAnnotation(TestAnnoTrace.class);
String value = annotation.value();
int type = annotation.type();

以上就是關(guān)于“Android中如何實現(xiàn)面向切面編程”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

文章題目:Android中如何實現(xiàn)面向切面編程-創(chuàng)新互聯(lián)
分享地址:http://muchs.cn/article44/deidhe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、動態(tài)網(wǎng)站、小程序開發(fā)網(wǎng)站改版、域名注冊網(wǎng)站營銷

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名