Android面向AOP之AspectJ的使用篇

前言

AOP,它不是一門新語言,是一種面向切面的思想。它主要的作用是把一些具有相同屬性或者相同功能的代碼抽離出來形成一個切面,從而實現面向切面編程!而AspectJ就是基於Java語言實現AOP這種思想的一個框架。

Java之安裝AspectJ

AspectJ官網下載Jar包,然後在下載目錄執行下面的命令安裝

 java -jar aspectj-1.x.x.jar 

安裝完後如下圖
image.png

  • bin對應的是編譯命令,常用ajc.bat
  • doc 放的是一些文檔
  • lib裏面放的是一些AspectJ的一些庫。

知道大概得結構後,再來看看Android的使用方法。

Android之集成AspectJ

project之build.gradle

dependencies {
        classpath group:'org.aspectj',name:'aspectjtools',version:'1.9.1'
    }

app之build.gradle

import org.aspectj.tools.ajc.Main

//配置aspectJ
android.applicationVariants.all{
    //編譯Java代碼的任務
    JavaCompile javaCompile = it.javaCompile
    javaCompile.doLast {
        println "在編譯之後執行"
        //執行 aspectJ 修改字節碼的操作
        String[] args = [
                "-1.7",
                "-inpath",javaCompile.destinationDir.toString(),
                "-d",javaCompile.destinationDir.toString(),
                "-aspectpath",javaCompile.classpath.asPath,
                "-classpath",javaCompile.classpath.asPath,
                "-bootclasspath",project.android.bootClasspath.join(File.pathSeparator)
        ]

        new Main().runMain(args,false)
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation group:'aspectj',name:'aspectjrt',version:'1.5.4'
}

一個添加事務的例子

<Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="添加事務"
        android:onClick="hello"/>

public void hello(View view){
        Log.d(TAG,"hello aspectJ");
}

這是一個按鈕點擊事件,下面通過AOP對它進行添加事務
第一步

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Transaction {
}

第二步

@Aspect
public class TransactionAspect {
    private static final String TAG = "TransactionAspect";
    private static final String TRANSACTION_METHOD =
            "execution(@com.goach.myaspectj.annotation.Transaction * *(..))";
    @Pointcut(TRANSACTION_METHOD)
    public void transactionMethod(){}

    @Around("transactionMethod()")
    public void addTransaction(ProceedingJoinPoint joinPoint){
        Log.d(TAG,"開始事務....");
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        Log.d(TAG,"結束事務....");
    }
}

第三步

@Transaction//加上註解
public void hello(View view){
        Log.d(TAG,"hello aspectJ");
}

最後執行結果
image.png

這裏只要添加註解,沒動hello裏面的一行代碼就添加了事務。這就是一個簡單的AOP編程

在上面引出了個@Aspect,Join Points,@Pointcut, @Around這樣的概念,
- @Aspect 修飾一個類,這樣就會把這個類當一個Bean來處理
- Join Points 程序運行執行點,比如上面的execution就是其中的一種類型,還有Call類型等
- Pointcut 選出我們需要的Join Points,如

@Pointcut("execution(@com.goach.myaspectj.annotation.Transaction * *(..))")

execution指的是類型,com.goach.myaspectj.annotation.Transaction指的是包名+對應的註解名,第一個* 指的是返回值爲任意類型,第二個*指的是方法名爲任意類型或者是構造器的話使用,new代替,(..)指的是任意類型的參數。

  • Around advice的類型之一,還有beforeafter,而Around會替代原來的JPoint,如果需要執行原來的JPoint
try {
      joinPoint.proceed();
} catch (Throwable throwable) {
     throwable.printStackTrace();
}

還有一些其他用法:
- 獲取方法的註解

MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
Permission permission = method.getAnnotation(Permission.class);
  • 獲取當前得上下文
(Context) joinPoint.getTarget()
  • 結合RXJava做線程切換
implementation "io.reactivex.rxjava2:rxandroid:2.0.2"

同上面的定義方法,先定義一個異步註解

@Retention(RetentionPolicy.CLASS)
public @interface Async {
}

和一個同步註解

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Main {
}

再實現異步切面

@Aspect
public class AsyncAspect {
    /**
     * Around : doAync 替換,原本被 Async 聲明的方法
     */
    @Around("execution(@com.goach.permissions.annotation.Async void *(..))")
    public void doAsync(final ProceedingJoinPoint joinPoint){
        //切換線程
        Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) throws Exception {
                //子線程
                //執行原來的方法
                try{
                    joinPoint.proceed();
                }catch (Throwable throwable){
                    throwable.printStackTrace();
                }
            }
        }).subscribeOn(Schedulers.io()).subscribe();
    }
}

主線程的切面

@Aspect
public class MainAspect {
    @Around("execution(@com.goach.permissions.annotation.Main void * (..))")
    public void doMain(final ProceedingJoinPoint joinPoint){
        //保證在主線程
        if(Looper.myLooper()==Looper.getMainLooper()){
            try {
                joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            return;
        }
        //如果不在 切換到主線程
        Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) throws Exception {
                try{
                    joinPoint.proceed();
                }catch (Throwable throwable){
                    throwable.printStackTrace();
                }
            }
        }).subscribeOn(AndroidSchedulers.mainThread()).subscribe();
    }
}

最後測試使用

 @Async
    public void readFile(View view){
        Log.e("Main","讀取文件:"+Thread.currentThread().toString());
        try {
            Thread.sleep(3000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        showRestult();
    }
    @Async
    public void writeFile(View view){
        Log.e("Main","寫入文件:"+Thread.currentThread().toString());
        showRestult();
    }
    @Main
    public void showRestult(){
        Toast.makeText(this,"操作成功", Toast.LENGTH_SHORT).show();
    }

這樣就是一個線程切換的例子了。其實AspectJ還可以做動態權限處理,埋點統計等等之類的。

其他資料

AspectJ官網
Spring AOP 實現原理與 CGLIB 應用
Android中的AOP編程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章