本文主要介紹AspectJ入門,實(shí)現(xiàn)Android簡單的AOP編程,完成線程切換和Log日志的輸出。
另外AspectJ還可以實(shí)現(xiàn)方法的執(zhí)行時(shí)間,日志的收集記錄,登陸校驗(yàn)等功能。入門之后這些都不難。
Gradle配置
- 配置Project Gradle
- 引入
aspectjtools
在Project Gradle中配置classpath
- 引入
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0-alpha17'
classpath 'org.aspectj:aspectjtools:1.9.1‘
}
- 配置Moudle gradle
- 添加依賴
dependencies {
//AspectJ
implementation 'org.aspectj:aspectjrt:1.9.1'
//rxandroid,用于切換線程
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
}
- 調(diào)用AspectJ編譯器
import org.aspectj.tools.ajc.Main
project.android.applicationVariants.all {
JavaCompile compile = it.javaCompile
compile.doLast {
String[] args = [
//java版本
"-1.8",
//aspect處理的源文件
"-inpath", compile.destinationDir.toString(),
//aspect的輸出目錄
"-d", compile.destinationDir.toString(),
//aspect編譯器的classpath
"-aspectpath", compile.classpath.asPath,
//java程序類的查找路徑
"-classpath",compile.classpath.asPath,
//覆蓋引導(dǎo)類的位置,
"-bootclasspath",project.android.bootClasspath.join(File.separator)
]
//調(diào)用執(zhí)行aspectJ編譯器
new Main().runMain(args,false)
}
}
代碼
定義注解
- 切換到主線程的注解
MThread
package com.example.hi.aspectjdemo.anotations;
import ...
/**
* 切換到主線程
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface MThread {
}
- 切換到子線程的注解
SThread
package com.example.hi.aspectjdemo.anotations;
import ...
/**
* 切換到子線程
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface SThread {
}
- 打印方法的參數(shù)與返回值的注解
LogPR
package com.example.hi.aspectjdemo.anotations;
import ...
/**
* 打印方法中的參數(shù)與返回值
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface LogPR {
}
注解的調(diào)用
- 切換到主線程的方法的實(shí)現(xiàn)
package com.example.hi.aspectjdemo.anotations;
import ...
//類上添加注解@Aspect,以便AspectJ識(shí)別
@Aspect
public class MThreadImpl {
//切點(diǎn)方法 注意Pointcut的寫法: 執(zhí)行被MThread注解的方法,方法的返回值為void,*表示方法名任意,(..)表示參數(shù)任意
@Pointcut("execution(@com.example.hi.aspectjdemo.anotations.MThread void *(..))")
public void mainThreadMethod() {//方法的名稱自定義
}
//Around為包裹方法,另外還有Before及After,參數(shù)為自己前面定義的方法名
@Around("mainThreadMethod()")
public void execute(final ProceedingJoinPoint joinPoint) {//方法的名稱可自定義,返回值與切點(diǎn)方法的返回值應(yīng)一致
Observable.create(new ObservableOnSubscribe<Object>() {
@Override
public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
try {
joinPoint.proceed();//執(zhí)行方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}).subscribeOn(AndroidSchedulers.mainThread()).subscribe();
}
}
- 切換到子線程的實(shí)現(xiàn)
package com.example.hi.aspectjdemo.anotations;
import ...
@Aspect
public class SThreadImpl {
@Pointcut("execution(@com.example.hi.aspectjdemo.anotations.SThread void *(..))")
public void subThreadMethod(){
}
@Around("subThreadMethod()")
public void execute(final ProceedingJoinPoint joinPoint){
Observable.create(new ObservableOnSubscribe<Object>() {
@Override
public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}).subscribeOn(Schedulers.newThread()).subscribe();
}
}
- 打印返回值的實(shí)現(xiàn)
package com.example.hi.aspectjdemo.anotations;
import...
@Aspect
public class LogPRImpl {
@Pointcut("execution(@com.example.hi.aspectjdemo.anotations.LogPR * *(..))")
public void logMethod() {
}
@Around("logMethod()")
public Object execute(ProceedingJoinPoint joinPoint) {
Object result=null;
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
Object target = joinPoint.getTarget();
String arg = "";
for (int i = 0; i < args.length; i++) {
if (i == 0)
arg = args[i].toString();
else
arg = arg + "," + args[i].toString();
}
Log.i(methodName, "args: "+arg);
try {
result= joinPoint.proceed();
if (result != null)
Log.i("result:", "result=" + result.toString());
else
Log.i("result","沒有返回值");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}
注解的引用
寫一個(gè)Acitivity,在其中調(diào)用一個(gè)方法mSleep,要求切換到子線程,線程睡眠一段時(shí)間,睡眠結(jié)束后,執(zhí)行showToast切換到主線程,彈出Toast。調(diào)用另一個(gè)方法getResult,傳遞2個(gè)參數(shù),打印出2個(gè)參數(shù)和執(zhí)行的結(jié)果
package com.example.hi.aspectjdemo;
import ...
public class AspectJDemoActivity extends AppCompatActivity {
private static final String TAG = "AspectJDemoActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSleep();
getResult(1,2);
}
@SThread
private void mSleep() {
Log.i(TAG, "mSleep: " + Thread.currentThread().getName());
try {
Thread.sleep(3000);
Log.i(TAG, "mSleep--> finish: " + Thread.currentThread().getName());
} catch (InterruptedException e) {
Log.i(TAG, "線程結(jié)束: "+e.getMessage());
}
showToast();
}
@MThread
private void showToast() {
Log.i(TAG, "showToast: " + Thread.currentThread().getName());
Toast.makeText(this, "來自主線程的消息,子線程睡眠結(jié)束", Toast.LENGTH_SHORT).show();
}
@LogPR
private int getResult(int i, int j) {
Log.i(TAG, "getResult: " + Thread.currentThread().getName());
return i + j;
}
}
結(jié)果:
線程切換.PNG

參數(shù)打印.png
如上圖所示,我們實(shí)現(xiàn)了切面編程,實(shí)現(xiàn)了一個(gè)注解實(shí)現(xiàn)子、主線程切換和參數(shù)及執(zhí)行結(jié)果的打印
幾個(gè)坑
- 定義Pointcut及執(zhí)行方法
如果你真實(shí)的方法有返回值,在定義Pointcut的返回值時(shí)一定不要void,@Around注解的執(zhí)行方法也不能返回void,否則不能正確的生成需要的class文件,導(dǎo)致執(zhí)行結(jié)果與預(yù)期不符合。
例如前面例子中LogPRImpl的execute方法,如果返回void,那么就無法正確執(zhí)行了! - 關(guān)于內(nèi)存泄漏
由于涉及到線程的切換,所以當(dāng)Activity結(jié)束時(shí),子線程可能會(huì)導(dǎo)致內(nèi)存泄漏,所以要想辦法拿到線程,當(dāng)Activity銷毀時(shí),不需要的線程也要結(jié)束。 - 關(guān)于切換線程執(zhí)行方法的返回值
目前還沒有找到優(yōu)雅的方法。
簡單處理Activity銷毀時(shí)使子線程結(jié)束
定義一個(gè)DisposableHandler,把*Impl中的Disposable存儲(chǔ)起來,當(dāng)Activity結(jié)束時(shí),調(diào)用Disposable.dispose();
package com.example.hi.aspectjdemo;
import ...
public class DisposableHandler {
static HashMap<Activity,Set<Disposable>> map=new HashMap<>();
public static void addNewDisposable(Activity activity,Disposable disposable){
Set<Disposable> disposables = map.get(activity);
if(disposables==null){
disposables=new HashSet<>();
map.put(activity,disposables);
}
disposables.add(disposable);
}
public static void dispose(Activity activity){
Set<Disposable> disposables = map.get(activity);
if(disposables==null){
return;
}
Iterator<Disposable> iterator = disposables.iterator();
while (iterator.hasNext()){
Disposable next = iterator.next();
if(!next.isDisposed()){
next.dispose();
}
}
}
}
在Impl中調(diào)用addNewDisposable
...
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Around("subThreadMethod()")
public void execute(final ProceedingJoinPoint joinPoint) {
Object target = joinPoint.getTarget();//目標(biāo)類,如果時(shí)Activity,則獲取到原始方法所在的Activity實(shí)例
Disposable disposable = Observable.create(new ObservableOnSubscribe<Object>() {
@Override
public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}).subscribeOn(Schedulers.newThread()).subscribe();
if (target instanceof Activity) {
//保存Disposable
DisposableHandler.addNewDisposable((Activity) target, disposable);
}
}
...
當(dāng)Activity銷毀時(shí)
...
@Override
protected void onDestroy() {
super.onDestroy();
DisposableHandler.dispose(this);
}
...
新手入門,難免有疏漏錯(cuò)誤,歡迎指正!