AOP 概念
AOP 是 Aspect Oriented Programming 的縮寫,意爲 面向切面編程,通過預編譯和運行期動態代理實現程序功能的統一維護的一種技術。利用 AOP 可以實現對代碼的業務邏輯進行隔離,降低各功能間的耦合度。
使用場景: 需求是在類的每個方法中代碼執行之前添加一句日誌打印,在沒有使用 AOP 的情況下,就需要在每個方法中手動添加日誌打印,使用了 AOP ,就可以將打印日誌代碼在編譯期間插入方法中,我們維護時二者的邏輯是分開的。
Android AOP 的實現方式
AOP 和 OOP 一樣只是一種編程思想,它的實現方式主要有以下幾種:
- APT :AnnotationProcessor 在編譯時生成 Java 文件。
- AspectJ : 在將 .java 文件編譯爲 .class 文件時進行代碼的注入。
- Javassist : 對編譯好的 class 字節碼文件進行操作。
![未命名文件 (1)](/Users/xing/Downloads/未命名文件 (1).png)
AOP 術語
JoinPoint:連接點,程序執行過程中明確的點(被攔截的方法,字段,構造器)
Pointcut : 切點,用來描述 JoinPoint 連接點的表達式。比如描述調用 Animal.fly() 方法的地方,則寫成
call(* Animal.fly(**))
Advice : 增強,表示在 Pointcut 裏面定義的程序點具體要做的操作,通過 before,after,around 來區分是在 Jointpoint 之前,之後還是代替執行的代碼。
Aspect :切面,類似 Java 中的類聲明,Pointcut 和 Advice 合在一起稱作 Aspect。
JoinPoint | 說明 | Pointcut 語法 |
---|---|---|
method call | 函數被調用 | call(MethodSignature) |
method execution | 函數執行內部 | execution(MethodSignature) |
constructor call | 構造函數被調用 | call(MethodSignature) |
constructor execution | 構造函數執行內部 | execution(ConstructorSignature) |
field get | 讀成員變量 | get(FieldSignature) |
field set | 寫成員變量 | set(FieldSignature) |
static initialization | static 塊初始化 | staticinitialization(TypeSignature) |
handler | 異常處理 | handler(TypeSignature)只能與 @Before()配合使用 |
advice execution | advice 執行 | adviceexecution |
Advice | 說明 |
---|---|
@Before(Pointcut) | 執行 JoinPoint 之前 |
@After(Pointcut) | 執行 JoinPoint 之後 |
@AfterReturning | @AfterReturning(pointcut=-“xx”,returning=“returnValue”) |
@AfterThrowing | @AfterThrowing(pointcut=“xx”,throwing=“throwable”) |
@Around(Pointcut) | 替代原來的代碼,如果要執行原來的代碼,需要使用 ProceedingJoinPoint.proceed() |
AOP 表達式 & 通配符
execution 基本語法:
execution(<修飾符模式>?<返回類型模式><方法名模式>(<參數模式>)<異常模式>?)
除了返回類型模式,方法名模式,參數模式外,其他項都是可選的。
- execution(public * *(…)) : 匹配目標類所有 public 方法, 第一個 * 代表返回類型,第二個 * 代表方法名,而 … 代表任意方法參數。
- execution(* *To(…)) : 匹配目標類所有以 To 結尾的方法,第一個 * 代表任意返回類型,而 *To 代表任意以 To 結尾的方法。
- execution(* com.xing.MainActivity.*(…)) : 匹配 MainActivity 中所有的方法,第一個 * 代表任意返回類型,第二個 * 代表方法名任意。
- execution(*com.xing.demo.MainPresenter+.*(…)) : 匹配 MainPresenter 接口及其所有實現類的方法。
- execution(* com.xing.*(…)) : 匹配 com.xing 包下所有類的所有方法。
- execution(* com.xing…*(…)) : 匹配 com.xing 包,子孫包下所有類的所有方法。
- execution(* com…*.*Dao.find*(…)) : 匹配 com 包,子孫包下以 Dao 結尾的類中所有以 find 爲前綴的方法。
Android AspectJ 實現 AOP
Android 中使用 AspectJ 步驟:
(1) 項目根目錄 build.gradle 配置 classpath
buildscript {
repositories {
google()
jcenter()
mavenCentral() // AspectJ 需要 maven
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'org.aspectj:aspectjtools:1.8.9'
// classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
(2) app build.gradle 添加依賴和 task
apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.xing.aspectjsample"
minSdkVersion 15
targetSdkVersion 28
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(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'org.aspectj:aspectjrt:1.8.9'
}
AspectJ 實戰
實戰1 - Activity 生命週期函數中添加 log 打印。
/**
* 定義切面
*/
@Aspect
public class TraceAspect {
/**
* 定義切點,攔截 MainActivity 中所有以 on 爲前綴的方法
*/
@Pointcut("execution(* com.xing.aspectjsample.MainActivity.on*(..))")
public void onLifecycleLog(){}
/**
* 在所有以 on 爲前綴的方法執行前添加 log.e 打印
*/
@Before("onLifecycleLog()")
public void handleLifecycleLog(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
Log.e("MainActivity", name + "-------->>>>>" + joinPoint);
}
}
public class MainActivity extends AppCompatActivity {
private static final String TAG = "TraceAspect";
private int count = 12;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int count = getCount();
}
private int getCount() {
return count;
}
@SingleClick
public void click(View view) {
Log.e(TAG, "click: ");
Toast.makeText(this, "the button is clicked", Toast.LENGTH_SHORT).show();
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onRestart() {
super.onRestart();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
實戰2 - 防止按鈕重複點擊
防止按鈕重複點擊思路是:兩次點擊的時間間隔小於指定值,不產生點擊作用。
定義註解 SingleClick
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SingleClick {
long value() default 1000;
}
定義切面類,攔截類中標註了 SingleClick 註解,參數爲 View 的方法進行增強處理。
@Aspect
public class SingleClickAspect {
private static final String TAG = "SingleClickAspect";
private long lastClickTime;
/**
* 定義切點,標記切點爲所有被 @SingleClick 註解修飾的方法
*/
@Pointcut("execution(@com.xing.aspectjsample.SingleClick * *(..))")
void singleClickAnnotated() {
}
/**
* 定義一個 Advice,包裹切點方法
*
* @param joinPoint
*/
@Around("singleClickAnnotated()")
public void handleSingleClick(ProceedingJoinPoint joinPoint) throws Throwable {
// 獲取方法參數
View view = null;
for (Object arg : joinPoint.getArgs()) {
if (arg instanceof View) {
view = (View) arg;
break;
}
}
if (view == null) {
return;
}
// SingleClick 註解只修飾在方法上
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 只處理有 SingleClick 註解修飾的方法
if (!method.isAnnotationPresent(SingleClick.class)) {
return;
}
// 獲取到 SingleClick 註解對象
SingleClick singleClick = method.getAnnotation(SingleClick.class);
// 獲取註解值
long value = singleClick.value();
long currentTime = System.currentTimeMillis();
if (currentTime - lastClickTime > value) {
lastClickTime = currentTime;
joinPoint.proceed();
}
}
}
Activity 中進行引用:
@SingleClick
public void click(View view) {
Log.e(TAG, "click: ");
Toast.makeText(this, "the button is clicked", Toast.LENGTH_SHORT).show();
}