介紹
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é)果如下:
三式矫、點(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é)果如下: