Android AspectJ 使用說(shuō)明

介紹

Android AOP

一班利、AOP是什么

Android AOP(Aspect-Oriented Programming),Android 面向切面編程眯娱,是一種編程范式铣墨,用于將程序中的跨多個(gè)點(diǎn)的功能(稱(chēng)為切面或方面)模塊化。在Android開(kāi)發(fā)中嗽上,AOP 可以幫助開(kāi)發(fā)者更好地組織和管理代碼次舌。

總的來(lái)說(shuō),Android AOP 是一種強(qiáng)大的編程范式兽愤,可以幫助開(kāi)發(fā)者更好地組織和管理 Android 應(yīng)用程序中的代碼彼念,提高代碼的可維護(hù)性和可擴(kuò)展性。

二浅萧、使用場(chǎng)景

1逐沙、記錄日志

AOP可以在不修改原有業(yè)務(wù)代碼的情況下,為應(yīng)用程序添加日志記錄功能洼畅。通過(guò)在特定的連接點(diǎn)插入日志記錄通知吩案,可以方便地追蹤程序的執(zhí)行過(guò)程,有助于故障排查和問(wèn)題定位帝簇。

2徘郭、監(jiān)控性能

AOP可用于監(jiān)控方法運(yùn)行時(shí)間,幫助開(kāi)發(fā)者了解程序的性能瓶頸丧肴。通過(guò)記錄方法的執(zhí)行時(shí)間残揉,可以對(duì)程序進(jìn)行優(yōu)化,提高運(yùn)行效率芋浮。

3抱环、權(quán)限控制

AOP可以實(shí)現(xiàn)細(xì)粒度的權(quán)限控制,確保只有具備相應(yīng)權(quán)限的用戶(hù)才能訪(fǎng)問(wèn)特定的資源或執(zhí)行特定的操作纸巷。這有助于保護(hù)系統(tǒng)的安全性镇草。

4、緩存優(yōu)化

AOP可以?xún)?yōu)化緩存的使用何暇,提高程序的響應(yīng)速度陶夜。例如,第一次調(diào)用查詢(xún)數(shù)據(jù)庫(kù)時(shí)裆站,將查詢(xún)結(jié)果放入內(nèi)存對(duì)象条辟;第二次調(diào)用時(shí)黔夭,直接從內(nèi)存對(duì)象返回結(jié)果,無(wú)需再次查詢(xún)數(shù)據(jù)庫(kù)羽嫡。

5本姥、事務(wù)管理

AOP可以方便地管理事務(wù),確保數(shù)據(jù)的完整性和一致性杭棵。通過(guò)在方法調(diào)用前后開(kāi)啟和提交事務(wù)婚惫,可以簡(jiǎn)化事務(wù)處理的代碼,降低出錯(cuò)的可能性魂爪。

三先舷、優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

  • 降低耦合度:通過(guò)將橫切關(guān)注點(diǎn)(cross-cutting concerns)抽象出來(lái),并在核心業(yè)務(wù)邏輯之外進(jìn)行處理滓侍,AOP可以降低業(yè)務(wù)邏輯各部分之間的耦合度蒋川,提高程序的可維護(hù)性和可擴(kuò)展性。
  • 提高開(kāi)發(fā)效率:AOP允許開(kāi)發(fā)者在不修改原有代碼的情況下添加新功能或修改現(xiàn)有功能撩笆,從而提高了開(kāi)發(fā)效率捺球。
  • 統(tǒng)一管理:AOP可以將分散在各個(gè)模塊中的公共行為集中到一個(gè)統(tǒng)一的地方進(jìn)行控制和管理,簡(jiǎn)化了代碼結(jié)構(gòu)夕冲,降低了維護(hù)成本氮兵。

缺點(diǎn)

  • 增加復(fù)雜性:引入AOP可能會(huì)增加代碼的復(fù)雜性,尤其是對(duì)于初學(xué)者或不熟悉AOP的開(kāi)發(fā)人員來(lái)說(shuō)歹鱼,可能需要一定的學(xué)習(xí)和適應(yīng)周期泣栈。
  • 難以調(diào)試:由于AOP將代碼分散到多個(gè)地方,導(dǎo)致跟蹤和調(diào)試變得更加復(fù)雜醉冤。當(dāng)出現(xiàn)問(wèn)題時(shí)秩霍,很難確定是AOP代碼還是核心業(yè)務(wù)邏輯引起的。
  • 運(yùn)行時(shí)性能開(kāi)銷(xiāo):AOP通常在運(yùn)行時(shí)通過(guò)代理或動(dòng)態(tài)字節(jié)碼生成來(lái)實(shí)現(xiàn)蚁阳,這些機(jī)制可能引入一定的運(yùn)行時(shí)性能開(kāi)銷(xiāo)铃绒,尤其是在系統(tǒng)需要處理大量方法攔截時(shí)。

語(yǔ)法

一螺捐、Join Points

Join Points(連接點(diǎn))是指在程序執(zhí)行過(guò)程中可以插入切面邏輯的點(diǎn)颠悬。在Android環(huán)境下,這些Join Points也可以理解為應(yīng)用中各種可攔截的執(zhí)行點(diǎn)定血,以下是詳細(xì)介紹赔癌。

Join Point 說(shuō)明 Pointcuts語(yǔ)法
Method call 方法被調(diào)用 call(MethodPattern)
Method execution 方法執(zhí)行 execution(MethodPattern)
Constructor call 構(gòu)造函數(shù)被調(diào)用 call(ConstructorPattern)
Constructor execution 構(gòu)造函數(shù)執(zhí)行 execution(ConstructorPattern)
Field get 讀取屬性 get(FieldPattern)
Field set 寫(xiě)入屬性 set(FieldPattern)
Pre-initialization 與構(gòu)造函數(shù)有關(guān),很少用到 preinitialization(ConstructorPattern)
Initialization 與構(gòu)造函數(shù)有關(guān)澜沟,很少用到 initialization(ConstructorPattern)
Static initialization static 塊初始化 staticinitialization(TypePattern)
Handler 異常處理 handler(TypePattern)
Advice execution 所有 Advice 執(zhí)行 adviceexcution()

二灾票、Pointcuts

在 Android AspectJ中,Pointcut(切點(diǎn))是一個(gè)非常重要的概念茫虽,它用于定義哪些 Join Points(連接點(diǎn))應(yīng)該被攔截和處理刊苍。Join Points是程序執(zhí)行中的特定位置既们,如方法調(diào)用、異常拋出等正什,而Pointcut則是一個(gè)表達(dá)式啥纸,用于匹配這些 Join Points。

通過(guò)定義 Pointcut婴氮,AspectJ 能夠精確地控制哪些代碼應(yīng)該被增強(qiáng)(即添加額外的行為)斯棒。在 Android 項(xiàng)目中,你可以使用 AspectJ 來(lái)攔截和修改各種 Join Points主经,如 Activity 的生命周期方法荣暮、網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)操作等旨怠。

定義 Pointcut 時(shí)渠驼,你可以使用 AspectJ 提供的語(yǔ)法來(lái)指定匹配規(guī)則。這些規(guī)則可以基于方法簽名鉴腻、方法執(zhí)行參數(shù)、執(zhí)行者類(lèi)型等多種因素百揭。例如爽哎,你可以定義一個(gè) Pointcut 來(lái)匹配所有 Activity 類(lèi)的 onCreate 方法,或者匹配所有帶有特定注解的方法器一。

下面是一個(gè)簡(jiǎn)單的 AspectJ Pointcut 示例课锌,用于匹配所有 Activity 類(lèi)的 onCreate 方法。

@Pointcut("execution(* com.example.myapp.activity.*.onCreate(..))")  
public void onCreateMethod() {  
    // Pointcut定義祈秕,無(wú)需實(shí)現(xiàn)  
}

三渺贤、Advice

在Android AspectJ中,Advice(通知)是 AOP(面向切面編程)的一個(gè)核心概念请毛,它定義了在特定的 Join Points(連接點(diǎn))應(yīng)該執(zhí)行的操作志鞍。通過(guò) Advice,你可以在不修改原有業(yè)務(wù)代碼的情況下方仿,向這些 Join Points 添加額外的行為固棚,具體方法如下所示。

方法 說(shuō)明 描述
Before 前置通知 在Join Point(如方法調(diào)用)執(zhí)行之前執(zhí)行
After 返回后通知 在Join Point正常執(zhí)行完畢后執(zhí)行
AfterReturning 拋出異常后通知 在方法執(zhí)行后仙蚜,返回一個(gè)結(jié)果再執(zhí)行此洲,如果沒(méi)結(jié)果,用此修辭符修辭是不會(huì)執(zhí)行的
AfterThrowing 最終通知 在方法執(zhí)行過(guò)程中拋出異常后執(zhí)行委粉,也就是方法執(zhí)行過(guò)程中呜师,如果拋出異常后,才會(huì)執(zhí)行此切面方法
Around 環(huán)繞通知 它包圍了Join Point贾节,允許你在Join Point執(zhí)行前后添加代碼汁汗,甚至可以決定是否執(zhí)行Join Point

具體使用

一趟紊、集成 AspectJ

第一步,項(xiàng)目 build.gradle 添加依賴(lài)

這里我們采用原生方式進(jìn)行依賴(lài)碰酝,如果需要集成插件霎匈,可以使用AspectJX,注意該插件已經(jīng)很久未維護(hù)送爸。

所以我們采用原生依賴(lài)方式铛嘱,在項(xiàng)目的 build.gradle 添加 classpath 依賴(lài)。

buildscript {
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.9.6'
    }
}
plugins {
id 'com.android.application' version '8.2.2' apply false
}

第二步袭厂,App build.gradle 添加依賴(lài)

修改 app 的 build.gradle 文件墨吓,引入 AspectJ 編譯支持的插件,并配置相應(yīng)的任務(wù)來(lái)織入切面纹磺。

dependencies {

    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.9.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

    implementation 'org.aspectj:aspectjrt:1.9.6'
}

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 ->
    // 注意這里控制debug下生效帖烘,可以自行控制是否生效
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return
    }

    JavaCompile javaCompile = variant.javaCompileProvider.get()
    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
            }
        }
    }
}

二、開(kāi)始使用

編寫(xiě)測(cè)試代碼橄杨,我們選擇切入點(diǎn)是 Activity 的 onCreate 生命周期方法秘症,并在方法執(zhí)行前后進(jìn)行打印執(zhí)行日志。

package com.example.myapplication;

import android.util.Log;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AspectUtil {
    private static final String TAG = AspectUtil.class.getSimpleName();
    @Before("execution(* android.app.Activity+.onCreate(..))")
    public void beforeCreate(JoinPoint joinPoint) {
        Log.d(TAG, "activity create before");
    }

    @After("execution(* android.app.Activity+.onCreate(..))")
    public void afterCreate() {
        Log.d(TAG, "activity create after");
    }
}


執(zhí)行結(jié)果如下:

image.png

三式矫、點(diǎn)擊事件攔截

實(shí)際場(chǎng)景中我們有防抖的需求乡摹,這時(shí)我們可以使用如下這種方式進(jìn)行處理。

package com.example.myapplication;

import android.util.Log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class InterruptClickUtil {
    private static final String TAG = "InterruptClick";
    private static final Long MAX = 500L;
    private Long mLastTime = 0L;

    @Around("execution(* android.view.View.OnClickListener.onClick(..))")
    public void clickEvent(ProceedingJoinPoint joinPoint) throws Throwable {
        if (System.currentTimeMillis() - mLastTime >= MAX) {
            mLastTime = System.currentTimeMillis();
            joinPoint.proceed();
        } else {
            Log.i(TAG, "repeated clicks");
        }
    }
}


package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "InterruptClick";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.tv);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG, "onClick");
            }
        });
    }
}


執(zhí)行結(jié)果如下:

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末采转,一起剝皮案震驚了整個(gè)濱河市聪廉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌故慈,老刑警劉巖板熊,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異察绷,居然都是意外死亡干签,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)克婶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)筒严,“玉大人,你說(shuō)我怎么就攤上這事情萤⊙纪埽” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵筋岛,是天一觀的道長(zhǎng)娶视。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么肪获? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任寝凌,我火速辦了婚禮,結(jié)果婚禮上孝赫,老公的妹妹穿的比我還像新娘较木。我一直安慰自己,他們只是感情好青柄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布伐债。 她就那樣靜靜地躺著,像睡著了一般致开。 火紅的嫁衣襯著肌膚如雪峰锁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天双戳,我揣著相機(jī)與錄音虹蒋,去河邊找鬼。 笑死飒货,一個(gè)胖子當(dāng)著我的面吹牛魄衅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播膏斤,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼徐绑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了莫辨?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤毅访,失蹤者是張志新(化名)和其女友劉穎沮榜,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體喻粹,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蟆融,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了守呜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片型酥。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖查乒,靈堂內(nèi)的尸體忽然破棺而出弥喉,到底是詐尸還是另有隱情,我是刑警寧澤玛迄,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布由境,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏虏杰。R本人自食惡果不足惜讥蟆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纺阔。 院中可真熱鬧瘸彤,春花似錦、人聲如沸笛钝。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)婆翔。三九已至拯杠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啃奴,已是汗流浹背潭陪。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留最蕾,地道東北人依溯。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瘟则,于是被迫代替她去往敵國(guó)和親黎炉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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