AndroidStudio 配置 AspectJ 環境實現AOP
昨天看了一段android配置aspectj實現AOP的直播視訊,就試著自己配置了一下,可能是因為我自己的AndroidStudio環境的問題,碰到了不少的坑(其實還是因為對gradle理解的不多),但總歸是配置好了,就分享一下。
試了兩種方式,不過專案下的build.gradle,沒什麼變化,直接看一下程式碼吧:
build.gradle(專案下)
buildscript {
ext {
//android appcompat支援庫版本
androidSupportVersion = '26.1.0'
//編譯的 SDK 版本,如API20
compileSdkVersion = 26
//構建工具的版本,其中包括了打包工具aapt、dx等,如API20對應的build-tool的版本就是20.0.0
buildToolsVersion = "26.0.2"
//相容的最低 SDK 版本
minSdkVersion = 15
//向前相容,儲存新舊兩種邏輯,並通過 if-else 方法來判斷執行哪種邏輯
targetSdkVersion = 26
//kotlin版本號
kotlin_version = '1.2.10'
kotlinVersion = "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
appcompatV7 = "com.android.support:appcompat-v7:$androidSupportVersion"
appcompatDesign = "com.android.support:design:$androidSupportVersion"
constraintLayout = 'com.android.support.constraint:constraint-layout:1.0.2'
}
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'org.aspectj:aspectjtools:1.8.13'
classpath 'org.aspectj:aspectjweaver:1.8.13'
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
看著一大堆,主要就是下面這幾行配置,其他的是我自己專案中用到的,根據自己需要配置就行。
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.13'
classpath 'org.aspectj:aspectjweaver:1.8.13'
}
}
repositories {
mavenCentral()
}
其實這幾行配置在app的build.gradle裡也是可以的,但是因為專案下的build.gradle裡已經有buildscript {}、allprojects {repositories{} },就配置在這裡了。
然後有兩種配置方式:
第一種
只有一個主Module app的情況下,配置app的build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'org.greenrobot.greendao'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "填入自己的applicationId"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
//Lambda配置
// jackOptions.enabled = true
// android.compileOptions.sourceCompatibility 1.8
buildConfigField "boolean", "LOG", "true"// 顯示Log
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//支援向量圖
vectorDrawables.useSupportLibrary = true
ndk {
//選擇要新增的對應cpu型別的.so庫。
abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64'
}
}
buildTypes {
release {
minifyEnabled false
buildConfigField "boolean", "LOG", "false"// 顯示Log
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
//Lambda配置
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
dataBinding {
enabled true
}
greendao {
schemaVersion 1//資料庫版本號
daoPackage 'com.test.qby.newtestapplication.greendao'//設定DaoMaster、DaoSession、Dao包名
targetGenDir 'src/main/java'//設定DaoMaster、DaoSession、Dao目錄
//targetGenDirTest:設定生成單元測試目錄
//generateTests:設定自動生成單元測試用例
}
lintOptions {
abortOnError true
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation rootProject.ext.kotlinVersion
implementation rootProject.ext.appcompatV7
implementation rootProject.ext.constraintLayout
compile rootProject.ext.appcompatDesign
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
compile 'jp.wasabeef:glide-transformations:3.0.1'
// If you want to use the GPU Filters
compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.4.1'
//騰訊bugly
compile 'com.tencent.bugly:crashreport:latest.release'
compile 'com.tencent.bugly:nativecrashreport:latest.release'
//retrofit
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.9.0'
//rxJava
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
// Because RxAndroid releases are few and far between, it is recommended you also
// explicitly depend on RxJava's latest version for bug fixes and new features.
compile 'io.reactivex.rxjava2:rxjava:2.1.8'
//greenDao
compile 'org.greenrobot:greendao:3.2.0'
//換膚功能
compile 'com.zhy:changeskin:4.0.2'
//AOP面向切面程式設計,加入這行就不用在libs下引入jar包了,不然要寫成compile file(libs/aspectjrt.jar)
compile 'org.aspectj:aspectjrt:1.8.13'
}
/*
//在專案下配置了,此處就不需要了
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.13'
classpath 'org.aspectj:aspectjweaver:1.8.13'
}
}
repositories {
mavenCentral()
}
*/
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.5",
"-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
}
}
}
}
這一個gradle主要的東西就是這些:
//AOP面向切面程式設計,加入這行就不用在libs下引入jar包了,不然要寫成compile file(libs/aspectjrt.jar)
compile 'org.aspectj:aspectjrt:1.8.13'
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.5",
"-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
}
}
}
}
下面那一堆是用命令在編譯最後做一些關聯的,具體的我也不懂,只管加上好了。
第二種
有多個module都需要用到aspectj,特別是元件開發的情況下,不可能每個module都配置一下,所以就需要新建一個aspectj的module作為專案的library。
app下build.gradle需要修改:
將
//AOP面向切面程式設計,加入這行就不用在libs下引入jar包了,不然要寫成compile file(libs/aspectjrt.jar)
compile 'org.aspectj:aspectjrt:1.8.13'
去掉,改為
implementation project(':aspectjlib')
不過上面這句在你新增module依賴的時候會自動生成。
新建library的build.gradle配置如下:
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation rootProject.ext.appcompatV7
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
//AOP
compile 'org.aspectj:aspectjrt:1.8.13'
}
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
android.libraryVariants.all { variant ->
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", android.bootClasspath.join(
File.pathSeparator)]
MessageHandler handler = new MessageHandler(true)
new Main().run(args, handler)
def log = project.logger
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:
case IMessage.INFO:
log.info message.message, message.thrown
break
case IMessage.DEBUG:
log.debug message.message, message.thrown
break
}
}
}
}
注意:下面那一堆跟app的gradle中的稍微有點區別,一個是module,一個是library,gradle中的東西不一樣。
兩種配置方式基本就是這樣了,使用方法我也是剛瞭解一點,記錄一下簡單的計算效能的用法吧
自定義註解類:
package com.test.qby.aspectjlib.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by qby on 2018/1/26 0026.
* 自定義註解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IFirstAnnotation {
String value();
}
@Target 註解目標,表示註解使用在什麼地方,這裡是METHOD方法;@Retention 保留策略,表示註解呼叫時機,這裡RUNTIME執行時
切面類
import android.widget.Toast;
import com.test.qby.aspectjlib.annotation.IFirstAnnotation;
import com.test.qby.newtestapplication.app.MyApplication;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
import java.lang.reflect.TypeVariable;
import java.util.Locale;
/**
* Created by qby on 2018/1/26 0026.
* 自定義註解行為
*/
@Aspect
public class MethodBehaviorAspect {
private static final String TAG = "aspect_aby";
@Pointcut("execution(@com.test.qby.aspectjlib.annotation.IFirstAnnotation * *(..))")
public void firstMethodAnnotationBehavior() {
}
@Pointcut("execution(* com.test.qby.newtestapplication.ui.MainActivity.aspectClick(android.view.View))")
public void secondMethodAnnotationBehavior() {
}
@Around("firstMethodAnnotationBehavior()")
public Object wavePointcutAround(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 類名
String className = methodSignature.getDeclaringType().getSimpleName();
// 方法名
String methodName = methodSignature.getName();
// 功能名
IFirstAnnotation behaviorTrace = methodSignature.getMethod()
.getAnnotation(IFirstAnnotation.class);
String value = behaviorTrace.value();
// String value = "點選";
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
Log.e(TAG, String.format("%s類中%s方法執行%s功能,耗時:%dms", className, methodName, value, duration));
Toast.makeText(MyApplication.getContext(), String.format(Locale.CHINESE, "%s類中%s方法執行%s功能,耗時:%dms", className, methodName, value, duration), Toast.LENGTH_SHORT).show();
return result;
}
}
@Aspect指定切面類;@Pointcut切入點;@Around是切入方式Advice的一種,表示在切入點前後插入程式碼,還有@Before、@After;execution執行時,表示根據Advice在執行方法內部程式碼前後插入程式碼,還有call呼叫時,表示根據Advice在呼叫方法前後插入程式碼。
頁面呼叫
@IFirstAnnotation("測試Aspect")
public void aspectClick(View view) {
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@IFirstAnnotation呼叫註解,()內部為在IFirstAnnotation中寫的value的值,去掉value()後此處去掉()
注意:在MethodBehaviorAspect 類中如果有用到Context,可直接使用joinPoint.getTarget()型別轉換成Context,這裡是由於專案使用了databinding,部分getTarget()獲取到的值不能強轉為Context,所以這裡用的MyApplication獲取的Context