1. 程式人生 > >Android AOP實現原理全解析

Android AOP實現原理全解析

     前天早晨在公交車上,知乎搜尋了下Android的最新技術,回答還是很多的,我們搞技術的,永遠不能落後,要隨時與市場保持同步,這樣才能跟上市場的步伐。有朋友提到了一個AOP的面向切面的程式設計技術,從這個名字上,大概就可以知道是幹什麼的,也有很多朋友舉例就是在日誌列印、許可權檢查等比較散的地方,使用AOP可以實現統一管理,還是非常方便的。百度一下,也有好多大神寫的關於AOP的詳細介紹:

     在AndroidStudio工具中開發的話,還需要編譯build.gradle角本,詳細情況可參考如下:

     我們本部落格的重點是瞭解清楚AOP的整個實現流程,是直接使用的別人的程式碼,程式碼也是在別人的部落格中直接下載的,地址如下:

     Activity就一個,執行時就直接呼叫TestMain.TestAll()日誌列印。我們先來看一下作者分module的用意。整個project分為aoplib、app、buildsrc、libinlib、testlib五個module,各module的意思應該也比較清楚,aoplib就是實現AOP功能的模組,app是本專案的啟動模組,buildsrc是用來構建專案的模組,libinlib中作者只提供了一個TestLog類,而且只有一個方法,目的是用來測試AOP功能的,最後一個testlib是作者實現自己意圖的模組,所有的測試類都是在這裡的。我們要分析的重點就是aoplib和testlib這兩個module了。

     我們從程式的執行過程來一步步分析,首先看一下MainActivity類的onCreate方法,請注意,作者為這個方法上加了一個@DebugLog註解,@DebugLog註解是自己實現的,實現程式碼如下,非常簡單:

package com.example.aoplib;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.CLASS;

@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(CLASS)
public @interface DebugLog {
}
     從這個註釋介面的定義上,我們可以看到,它的目標是使用在TYPE(介面、類、列舉、註解),METHOD(方法),CONSTRUCTOR(建構函式)三種類型上的,當前就是使用在MainActivity的onCreate方法上的。我們可以來看一下使用Aspect編譯後的MainActivity的class檔案:


     從這裡大家可以非常清楚的看到,MainActivity的onCreate方法已經被替換了,它是按照Aspect編譯規則生成的,我們可以再來看看其他帶有@DebugLog註解的類或者方法對應生成的class檔案,比如TestMethodClass類的class檔案,整個檔案程式碼如下:

public class TestMethodClass {
    public TestMethodClass() {
        (new Thread() {
            @DebugLog
            public void run() {
                JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this);
                Hugo var10000 = Hugo.aspectOf();
                Object[] var3 = new Object[]{this, var2};
                var10000.logAndExecute((new TestMethodClass$1$AjcClosure1(var3)).linkClosureAndJoinPoint(69648));
            }

            static {
                ajc$preClinit();
            }
        }).start();
    }

    @DebugLog
    public void spendTime1ms() {
        JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this);
        Hugo var10000 = Hugo.aspectOf();
        Object[] var3 = new Object[]{this, var2};
        var10000.logAndExecute((new TestMethodClass$AjcClosure1(var3)).linkClosureAndJoinPoint(69648));
    }

    @DebugLog
    public static void spendTime2ms() {
        JoinPoint var1 = Factory.makeJP(ajc$tjp_1, (Object)null, (Object)null);
        Hugo var10000 = Hugo.aspectOf();
        Object[] var2 = new Object[]{var1};
        var10000.logAndExecute((new TestMethodClass$AjcClosure3(var2)).linkClosureAndJoinPoint(65536));
    }

    @DebugLog
    public final void spendTime3ms() {
        JoinPoint var2 = Factory.makeJP(ajc$tjp_2, this, this);
        Hugo var10000 = Hugo.aspectOf();
        Object[] var3 = new Object[]{this, var2};
        var10000.logAndExecute((new TestMethodClass$AjcClosure5(var3)).linkClosureAndJoinPoint(69648));
    }

    static {
        ajc$preClinit();
    }
}
     在這個class檔案中,我們可以看到,run()、spendTime1ms()、spendTime2ms()、spendTime3ms()幾個方法全部都是這樣樣式,每個方法一共四句,第一句,構建一個切點JoinPoint,第二句呼叫Hugo.aspectOf()獲取我們自己定義的Aspect處理類,第三句構造引數陣列Object[],第四句呼叫當前類的相應方法。看到這裡我們基本就明白AOP的原理了,它就是利用我們自己實現的一個註解,將所有的切點集中在一個地方處理的,這樣,就可以把多個切點放在一起統一處理了,非常的方便!

     下面我們就來分析一個方法的執行過程,此專案中其他方法的實現是完全一樣的,我們就以TestMethodClass類的spendTime1ms()為例來展開我們的分析,在編譯完的class檔案中,首先構造一個JoinPoint切點,Factory.makeJP()方法的實現就是使用傳入的引數直接構造一個JoinPointImpl物件,第二句就是獲取當前的Aspect處理類物件Hugo,此類必須要帶有@Aspect註解,第三句就是構造方法執行的陣列物件,第四句執行Aspect處理類的入口方法logAndExecute,Hugo類的完整程式碼如下:

@Aspect
public class Hugo {
    @Pointcut("within(@com.example.aoplib.DebugLog *)")
    public void withinAnnotatedClass() {}

    @Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")
    public void methodInsideAnnotatedType() {}

    @Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")
    public void constructorInsideAnnotatedType() {}

    @Pointcut("execution(@com.example.aoplib.DebugLog * *(..)) || methodInsideAnnotatedType()")
    public void method() {}

    @Pointcut("execution(@com.example.aoplib.DebugLog *.new(..)) || constructorInsideAnnotatedType()")
    public void constructor() {}

    @Around("method() || constructor()")
    public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {
        enterMethod(joinPoint);

        long startNanos = System.nanoTime();
        Object result = joinPoint.proceed();
        long stopNanos = System.nanoTime();
        long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);

        exitMethod(joinPoint, result, lengthMillis);

        return result;
    }



    private static void enterMethod(JoinPoint joinPoint) {

        CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();

        Class<?> cls = codeSignature.getDeclaringType();
        String methodName = codeSignature.getName();
        String[] parameterNames = codeSignature.getParameterNames();
        Object[] parameterValues = joinPoint.getArgs();

        StringBuilder builder = new StringBuilder("\u21E2 ");
        builder.append(methodName).append('(');
        for (int i = 0; i < parameterValues.length; i++) {
            if (i > 0) {
                builder.append(", ");
            }
            builder.append(parameterNames[i]).append('=');
            builder.append(Strings.toString(parameterValues[i]));
        }
        builder.append(')');

        if (Looper.myLooper() != Looper.getMainLooper()) {
            builder.append(" [Thread:\"").append(Thread.currentThread().getName()).append("\"]");
        }

        Log.v(asTag(cls), builder.toString());

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            final String section = builder.toString().substring(2);
            Trace.beginSection(section);
        }
    }

    private static void exitMethod(JoinPoint joinPoint, Object result, long lengthMillis) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            Trace.endSection();
        }

        Signature signature = joinPoint.getSignature();

        Class<?> cls = signature.getDeclaringType();
        String methodName = signature.getName();
        boolean hasReturnType = signature instanceof MethodSignature
                && ((MethodSignature) signature).getReturnType() != void.class;

        StringBuilder builder = new StringBuilder("\u21E0 ")
                .append(methodName)
                .append(" [")
                .append(lengthMillis)
                .append("ms]");

        if (hasReturnType) {
            builder.append(" = ");
            builder.append(Strings.toString(result));
        }

        Log.v(asTag(cls), builder.toString());
    }

    private static String asTag(Class<?> cls) {
        if (cls.isAnonymousClass()) {
            return asTag(cls.getEnclosingClass());
        }
        return cls.getSimpleName();
    }
}
     可以看到,在logAndExecute方法的處理中就是列印了日誌而已,當然,我們也可以根據我們自己需求實現不同的功能,比如許可權檢查,如果許可權OK,就正常執行,如果不OK,則直接在這裡丟擲異常。

     好了,AOP的完整過程我們已經瞭解了,可以看出,整個執行過程還是比較簡單的,就是使用了一個註解把我們的目標切點集中到一起進行處理,最後我們來總結一下,如果我們自己的專案要實現AOP的切面程式設計,應該要有幾步:

     1:我們要使用Aspect,肯定就需要有相應的jar包了,jar包的資源非常多,大家可以在網上隨便下載。

     2:要定義我們的切點,相當於本例中的DebugLog註解,它的@Target可以根據自己的需求來實現。

     3:定義自己的Aspect處理類,此類必須帶有@Aspect註解,需要在它裡面定義切點函式,定義切點的處理函式,也就是本例中Hugo類的logAndExecute方法了,Hugo類中的其他兩個方法enterMethod、exitMethod只是為了實現日誌列印的目的而寫的,大家可以根據自己的需求具體實現。

     4:完成了上面兩步,我們的專案中的Aspect框架就算搭建好了,下面就是新增切點了,也就是我們要在哪裡切入的問題,可以通過新增我們自定義的註釋來實現。

     5:我們的所有功能都完成了,最後還一定要注意編譯的方式要使用Aspect編譯器,如果大家使用javac編譯器的話,那生成的class檔案中根本沒有Aspect的程式碼,肯定也就無法實現我們的意圖了。