Android面向切面編程(AOP)

WHY

如果說OOP(面向對象的程序設計)的主要思想是將問題歸分到相應的對象(類)中去實現,再把相關類模塊化使得模塊內聚、模塊間低耦。正是因為高度的模塊化使得每個模塊處理類似需求的時候,很容易造成冗余,分散的情況

AOP舉個栗子

那么AOP面向切面編程(Aspect-Oriented Programming)就是把涉及到眾多模塊的某一類問題進行統一管理,因為AOP是方法論,你可以用任何的方式按照這個思想去實現。
那么問題來了:怎么樣不需要顯示的去修改原來的業務邏輯,最少的改動插入代碼,去優雅的實現AOP切面。

WHO

AOP從實現原理上可以分為運行時AOP編譯時AOP,對于Android來講運行時AOP的實現主要是hook某些關鍵方法,編譯時AOP主要是在Apk打包過程中對java,class,dex文件進行掃描更改。
Android主流的aop 框架和工具類庫:

  • AspectJ: 一個 JavaTM 語言的面向切面編程的無縫擴展
  • Javassist for Android: 用于字節碼操作的知名 java 類庫 Javassist 的 Android 平臺移植版。
  • DexMaker: Dalvik 虛擬機上,在編譯期或者運行時生成代碼的 Java API。
  • ASMDEX: 一個類似 ASM 的字節碼操作庫,運行在Android平臺,操作Dex字節碼。
  • APT: 通過Android-apt的Gradle插件(官方),以及jre原生代碼在編譯器生成Java文件

本文主要探究一下通過預編譯方式和運行期動態代理在Android中的AOP實現方式,包括***APT,AspectJ ***


其實APT,AspectJ,Javassist正好對應的是Android不通編譯期,進行代碼或者字節碼操作的,因為實現的方式,解決的問題也有不同

APT,AspectJ,Javassist的運行操作時機

其實通俗來說,
APT就是單獨用一個Java的module來生成app內的java代碼
AspectJ就是提供了很多橫切關注點來在編譯class文件的時候動態來修改你的java的代碼

HOW

APT

代表框架:DataBinding,Dagger2, ButterKnife, EventBus3 、AndroidAnnotation等
注解處理器 Java5 中叫APT(Annotation Processing Tool),在Java6開始,規范化為 Pluggable Annotation Processing。Apt應該是這其中我們最常見到的了,難度也最低。定義編譯期的注解,再通過繼承Proccesor實現代碼生成邏輯,實現了編譯期生成代碼的邏輯。

Talk is cheap. Show me your code .

下面我來用APT實現一個簡單的全局路由來演示一下APT的工作原理

  • 1.配置
    使用官方gradle插件(如果之前有使用框架用到了android-apt插件的最好去掉,原作者不再維護了,而且兩個插件兼容會有問題)
工程中的module依賴.png

appmodule的gradle配置,工程依賴apt入口

dependencies {
    annotationProcessor project(':apt')
    compile project(':apt-lib')
}

創建一個apt-libmodule用來提供路由需要的注解
gradle配置

apply plugin: 'java'
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
// 解決build警告:編碼GBK的不可映射字符
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

全局路由注解類

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Rounter {
    String value();
}

路由參數注解

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface Extra {
    String value();
}

創建一個aptmodule用來負責編譯期生成java代碼
gradle配置

apply plugin: 'java'
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
dependencies {
    compile 'com.google.auto.service:auto-service:1.0-rc2'
    compile 'com.squareup:javapoet:1.7.0'
    compile project(':lib')
}
//  解決build警告:編碼GBK的不可映射字符
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

com.google.auto.service:auto-service:1.0-rc2
谷歌提供的Java 生成源代碼庫
com.squareup:javapoet:1.7.0
提供了各種 API 讓你用各種姿勢去生成 Java 代碼文件

  • 2.AnnotationProcessor
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)//java版本支持
@SupportedAnnotationTypes({//標注注解處理器支持的注解類型
        "com.app.annotation.apt.Rounter"})
public class AnnotationProcessor extends AbstractProcessor {
    public Filer mFiler; //文件相關的輔助類
    public Elements mElements; //元素相關的輔助類
    public Messager mMessager; //日志相關的輔助類

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        mFiler = processingEnv.getFiler();
        mElements = processingEnv.getElementUtils();
        mMessager = processingEnv.getMessager();
        new RouterProcessor().process(roundEnv, this);
        return true;
    }
}

@AutoService(Processor.class)指定了AnnotationProcessor類為Processor的入口process方法相當于java的main()方法
動態生成路由類TRouterRouterProcessor

public class RouterProcessor implements IProcessor {
    @Override
    public void process(RoundEnvironment roundEnv, AnnotationProcessor mAbstractProcessor) {
        String CLASS_NAME = "TRouter";
        TypeSpec.Builder tb = classBuilder(CLASS_NAME).addModifiers(PUBLIC, FINAL).addJavadoc("@ 全局路由器 此類由apt自動生成");

        FieldSpec extraField = FieldSpec.builder(ParameterizedTypeName.get(DataMap.class), "mCurActivityExtra")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .build();
        tb.addField(extraField);

        MethodSpec.Builder methodBuilder1 = MethodSpec.methodBuilder("go")
                .addJavadoc("@此方法由apt自動生成")
                .addModifiers(PUBLIC, STATIC)
                .addParameter(String.class, "name").addParameter(DataMap.class, "extra")
                .addParameter(ClassName.get("android.view", "View"), "view");

        MethodSpec.Builder methodBuilder2 = MethodSpec.methodBuilder("bind")
                .addJavadoc("@此方法由apt自動生成")
                .addModifiers(PUBLIC, STATIC)
                .addParameter(ClassName.get("android.app", "Activity"), "mContext");

        List<ClassName> mList = new ArrayList<>();
        CodeBlock.Builder blockBuilderGo = CodeBlock.builder();
        CodeBlock.Builder blockBuilderBind = CodeBlock.builder();
        ClassName appClassName = ClassName.get("com.wingjay.jianshi.global", "ActivityStackManager");
        blockBuilderGo.addStatement("mCurActivityExtra=extra");
        blockBuilderGo.addStatement("Activity mContext=$T.getInstance().getTopActivity()", appClassName);
        blockBuilderGo.beginControlFlow(" switch (name)");//括號開始

        blockBuilderBind.add(" if (mCurActivityExtra == null) return;\n");
        blockBuilderBind.beginControlFlow(" switch (mContext.getClass().getSimpleName())");//括號開始

        List<RouterActivityModel> mRouterActivityModels = new ArrayList<>();
        try {
            for (TypeElement element : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Rounter.class))) {
                ClassName currentType = ClassName.get(element);
                if (mList.contains(currentType)) continue;
                mList.add(currentType);
                RouterActivityModel mRouterActivityModel = new RouterActivityModel();
                mRouterActivityModel.setElement(element);
                mRouterActivityModel.setActionName(element.getAnnotation(Rounter.class).value());
                List<Element> mExtraElements = new ArrayList<>();
                List<String> mExtraElementKeys = new ArrayList<>();
                for (Element childElement : element.getEnclosedElements()) {
                    SceneTransition mSceneTransitionAnnotation = childElement.getAnnotation(SceneTransition.class);
                    if (mSceneTransitionAnnotation != null) {
                        mRouterActivityModel.setSceneTransitionElementName(mSceneTransitionAnnotation.value());
                        mRouterActivityModel.setSceneTransitionElement(childElement);
                    }
                    Extra mExtraAnnotation = childElement.getAnnotation(Extra.class);
                    if (mExtraAnnotation != null) {
                        mExtraElementKeys.add(mExtraAnnotation.value());
                        mExtraElements.add(childElement);
                    }
                }
                mRouterActivityModel.setExtraElementKeys(mExtraElementKeys);
                mRouterActivityModel.setExtraElements(mExtraElements);
                boolean isNeedBind = (mExtraElementKeys != null && mExtraElementKeys.size() > 0
                        || mRouterActivityModel.getSceneTransitionElement() != null);
                mRouterActivityModel.setNeedBind(isNeedBind);
                mRouterActivityModels.add(mRouterActivityModel);
            }
            ClassName mActivityCompatName = ClassName.get("android.support.v4.app", "ActivityCompat");
            ClassName mIntentClassName = ClassName.get("android.content", "Intent");
            ClassName mActivityOptionsCompatName = ClassName.get("android.support.v4.app", "ActivityOptionsCompat");
            for (RouterActivityModel item : mRouterActivityModels) {
                blockBuilderGo.add("case $S: \n", item.getActionName());//1
                if (item.isNeedBind())
                    blockBuilderBind.add("case $S: \n", item.getElement().getSimpleName());//1
                if (item.getExtraElements() != null && item.getExtraElements().size() > 0) {
                    for (int i = 0; i < item.getExtraElements().size(); i++) {
                        Element mFiled = item.getExtraElements().get(i);
                        blockBuilderBind.add("(($T)mContext)." +//1
                                        "$L" +//2
                                        "= ($T) " +//3
                                        "mCurActivityExtra.get(" +//4
                                        "$S);\n",//5
                                item.getElement(),//1
                                mFiled,//2
                                mFiled,//3
                                item.getExtraElementKeys().get(i)//5
                        );//5
                    }
                }
                if (item.getSceneTransitionElement() != null) {
                    blockBuilderGo.add("$L.startActivity(mContext," +//2
                                    "\nnew $L(mContext," +//3
                                    "\n$L.class)," +//4
                                    "\n$T.makeSceneTransitionAnimation(" +//5
                                    "\nmContext,view," +//6
                                    "\n$S).toBundle());", //7
                            mActivityCompatName,//2
                            mIntentClassName,//3
                            item.getElement(),//4
                            mActivityOptionsCompatName,//5
                            item.getSceneTransitionElementName());//6

                    blockBuilderBind.add(
                            "$T.setTransitionName(" +//2
                                    "(($T)mContext).mViewBinding." +//3
                                    "$L, " +//4
                                    "$S);\n",//5
                            ClassName.get("android.support.v4.view", "ViewCompat"),//2
                            item.getElement(),//3
                            item.getSceneTransitionElement(),//4
                            item.getSceneTransitionElementName());//5
                } else {
                    blockBuilderGo.add("mContext.startActivity(" +//2
                                    "\nnew $L(mContext," +//3
                                    "\n$L.class));", //7
                            mIntentClassName,//3
                            item.getElement()//4
                    );
                }
                blockBuilderGo.addStatement("\nbreak");//1
                if (item.isNeedBind()) blockBuilderBind.addStatement("break");//1
            }
            blockBuilderGo.addStatement("default: break");
            blockBuilderGo.endControlFlow();
            methodBuilder1.addCode(blockBuilderGo.build());
            blockBuilderBind.addStatement("default: break");
            blockBuilderBind.endControlFlow();
            methodBuilder2.addCode(blockBuilderBind.build());

            tb.addMethod(methodBuilder1.build());
            tb.addMethod(methodBuilder2.build());

            //增加go(action)和go(action,extra):兩個重載方法
            tb.addMethod(MethodSpec.methodBuilder("go")
                    .addJavadoc("@此方法由apt自動生成")
                    .addModifiers(PUBLIC, STATIC)
                    .addParameter(String.class, "name")
                    .addParameter(DataMap.class, "extra")
                    .addCode("go(name,extra,null);\n").build());

            tb.addMethod(MethodSpec.methodBuilder("go")
                    .addJavadoc("@此方法由apt自動生成")
                    .addModifiers(PUBLIC, STATIC)
                    .addParameter(String.class, "name")
                    .addCode("go(name,null,null);\n").build());

            tb.addMethod(MethodSpec.methodBuilder("getMap")
                    .returns(DataMap.class)
                    .addJavadoc("@此方法由apt自動生成")
                    .addModifiers(PUBLIC, STATIC)
                    .addCode("return new DataMap();\n").build());

            JavaFile javaFile = JavaFile.builder(AptConstants.PackageName, tb.build()).build();// 生成源代碼
            javaFile.writeTo(mAbstractProcessor.mFiler);// 在 app module/build/generated/source/apt 生成一份源代碼
        } catch (FilerException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

看結尾javaFile.writeTo(mAbstractProcessor.mFiler),
OK這個類就是使用了com.squareup:javapoet提供的一些很方便寫.java文件的類和方法來生成最終的TRounter.java

  • 3.使用路由
    下面就可以在原來的項目中使用路由了
@Rounter(RounterConstants.MAIN)
public class MainActivity extends BaseActivity {
...
}
@Rounter(RounterConstants.EDIT)
public class EditActivity extends BaseActivity {
    @Extra(DataMap.DATA_HEAD)
    public String diaryUUID;
...
}

路由跳轉

       TRouter.go(RounterConstants.MAIN);

       TRouter.go(RounterConstants.EDIT, TRouter.getMap().addHead(diary.getUuid()));

那么生成的這個TRounter在哪呢,可以在\build\generated\source\apt\debug中相應的包名下查看apt生成的代碼,如下

/**
 * @ 全局路由器 此類由apt自動生成 */
public final class TRouter {
  public static DataMap mCurActivityExtra;

  /**
   * @此方法由apt自動生成 */
  public static void go(String name, DataMap extra, View view) {
    mCurActivityExtra=extra;
    Activity mContext=ActivityStackManager.getInstance().getTopActivity();
     switch (name) {
      case "rounter_diary": 
      mContext.startActivity(
      new android.content.Intent(mContext,
      com.wingjay.jianshi.ui.DiaryListActivity.class));
          break;
      case "rounter_edit": 
      mContext.startActivity(
      new android.content.Intent(mContext,
      com.wingjay.jianshi.ui.EditActivity.class));
          break;
      case "rounter_main": 
      mContext.startActivity(
      new android.content.Intent(mContext,
      com.wingjay.jianshi.ui.MainActivity.class));
          break;
      case "rounter_setting": 
      mContext.startActivity(
      new android.content.Intent(mContext,
      com.wingjay.jianshi.ui.SettingActivity.class));
          break;
      case "rounter_signup": 
      mContext.startActivity(
      new android.content.Intent(mContext,
      com.wingjay.jianshi.ui.SignupActivity.class));
          break;
      case "rounter_view": 
      mContext.startActivity(
      new android.content.Intent(mContext,
      com.wingjay.jianshi.ui.ViewActivity.class));
          break;
      default: break;
    }
  }

  /**
   * @此方法由apt自動生成 */
  public static void bind(Activity mContext) {
     if (mCurActivityExtra == null) return;
     switch (mContext.getClass().getSimpleName()) {
      case "EditActivity": 
      ((EditActivity)mContext).diaryUUID= (String) mCurActivityExtra.get("data_head");
      break;
      case "ViewActivity": 
      ((ViewActivity)mContext).diaryUuid= (String) mCurActivityExtra.get("data_head");
      break;
      default: break;
    }
  }

  /**
   * @此方法由apt自動生成 */
  public static void go(String name, DataMap extra) {
    go(name,extra,null);
  }

  /**
   * @此方法由apt自動生成 */
  public static void go(String name) {
    go(name,null,null);
  }

  /**
   * @此方法由apt自動生成 */
  public static DataMap getMap() {
    return new DataMap();
  }
}

好了如上就簡單可以用APT實現一個簡單的全局路由了,如果不是開發框架的團隊的話,我認為APT在搭建團隊工程的框架方面還是有很大的利用場景的,比方說綁定model和View和present,比方說生成一些約定的接口或者方法


End of APT


AspectJ

代表框架:我就只知道JakeWharton/hugo了 = =||

如果說APT還很難體現AOP的概念的話,那么AspectJ就是先行者特別為AOP開發的一套語言支持了。它是一種幾乎和Java完全一樣的語言,或者說AspectJ應該就是一種擴展Java。當然,除了使用AspectJ特殊的語言外,AspectJ還支持原生的Java,只要加上對應的AspectJ注解就好。
AspectJ現在托管于Eclipse項目中
|【這是官方網站】
|【這是參考文檔】
|【這是接口文檔】

主要術語

  • Cross-cutting concerns(橫切關注點): 盡管面向對象模型中大多數類會實現單一特定的功能,但通常也會開放一些通用的附屬功能給其他類。例如,我們希望在數據訪問層中的類中添加日志,同時也希望當UI層中一個線程進入或者退出調用一個方法時添加日志。盡管每個類都有一個區別于其他類的主要功能,但在代碼里,仍然經常需要添加一些相同的附屬功能。
  • Advice(通知): 注入到class文件中的代碼。典型的 Advice 類型有 before、after 和 around,分別表示在目標方法執行之前、執行后和完全替代目標方法執行的代碼。 除了在方法中注入代碼,也可能會對代碼做其他修改,比如在一個class中增加字段或者接口。
  • Joint point(連接點): 程序中可能作為代碼注入目標的特定的點,例如一個方法調用或者方法入口。
  • Pointcut(切入點): 告訴代碼注入工具,在何處注入一段特定代碼的表達式。例如,在哪些 joint points 應用一個特定的 Advice。切入點可以選擇唯一一個,比如執行某一個方法,也可以有多個選擇,比如,標記了一個定義成@DebguTrace 的自定義注解的所有方法。
  • Aspect(切面): Pointcut 和 Advice 的組合看做切面。例如,我們在應用中通過定義一個 pointcut 和給定恰當的advice,添加一個日志切面。
  • Weaving(織入): 注入代碼(advices)到目標位置(joint points)的過程。
    整體原理就如下圖:
    30689-55846998f4f5b4ce.png
  • 其實我的理解就是AspectJ提供了各種各樣的切入點(關注點/代碼插入點),在生成class文件的時候可以動態替換你原來的代碼或者插入你想要的代碼

傳送門:想更多了解AspectJ語法的

舉幾個栗子

  • 性能監控切片
  • 用戶登錄切片
  • 防止頻繁點擊
  • 用戶行為路徑

環境配置
項目的Gradle

  dependencies {
    classpath 'com.android.tools.build:gradle:0.12.+'
    classpath 'org.aspectj:aspectjtools:1.8.1'
  }

app的Gradle

import com.app.plugin.AspectjPlugin
dependencies {
    compile 'org.aspectj:aspectjrt:1.8.9'
 }

性能監控切片
性能監控的注解

@Retention(RetentionPolicy.CLASS)//這個注解周期聲明在 class 文件上
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})//可以注解構造函數和方法
public @interface MonitorLog {
}

切片實現類,如果看不懂的可以去上面看下語法

@Aspect
public class MonitorAspect {

    @Pointcut("execution(@com.zzj.jianshi.aspect.annotation.MonitorLog * *(..))")//方法切入點
    public void methodAnnotated() {
    }

    @Pointcut("execution(@com.zzj.jianshi.aspect.annotation.MonitorLog *.new(..))")//構造器切入點
    public void constructorAnnotated() {
    }

    @Around("methodAnnotated() || constructorAnnotated()")//在連接點進行方法替換
    public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        if (!isMonitorOpened()) {//后臺下發監控開關
            return joinPoint.proceed();
        }

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String className = methodSignature.getDeclaringType().getSimpleName();
        String methodName = methodSignature.getName();
        long startTime = System.nanoTime();
        Object result = joinPoint.proceed();//執行原方法
        StringBuilder keyBuilder = new StringBuilder();
        keyBuilder.append(methodName + ":");
        for (Object obj : joinPoint.getArgs()) {
            if (obj instanceof String) keyBuilder.append((String) obj);
            else if (obj instanceof Class) keyBuilder.append(((Class) obj).getSimpleName());
        }
        String key = keyBuilder.toString();
        Timber.e("MonitorAspect --->: " + className + "." + key + joinPoint.getArgs().toString() +
                " --->:" + "[" + (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) + "ms]");// 記錄時間差
        return result;
    }
}

業務代碼中,在耗時操作或者關鍵方法中添加注解,如下:

    @Override
    @MonitorLog
    protected void onStart() {

可以看下日志輸出

06-08 15:49:02.457 729-729/? E/MonitorAspect: MonitorAspect --->: JianShiApplication.initLog:[Ljava.lang.Object;@c567935 --->:[2ms]
06-08 15:49:02.465 729-729/? E/MonitorAspect: MonitorAspect --->: JianShiApplication.onCreate:[Ljava.lang.Object;@3a1d85d --->:[53ms]
06-08 15:49:03.684 729-729/com.wingjay.android.jianshi E/MonitorAspect: MonitorAspect --->: MainActivity.onCreate:[Ljava.lang.Object;@64c8c14 --->:[369ms]
06-08 15:49:03.816 729-729/com.wingjay.android.jianshi E/MonitorAspect: MonitorAspect --->: MainActivity.onStart:[Ljava.lang.Object;@970c60a --->:[130ms]
06-08 15:50:20.288 729-729/com.wingjay.android.jianshi E/MonitorAspect: MonitorAspect --->: MainActivity.onStart:[Ljava.lang.Object;@efb02be --->:[2ms]

用戶登錄切片

@Aspect
public class CheckLoginAspect {

    @Pointcut("execution(@com.app.annotation.aspect.CheckLogin * *(..))")//方法切入點
    public void methodAnnotated() {
    }

    @Around("methodAnnotated()")//在連接點進行方法替換
    public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        if (null == SpUtil.getUser()) {
            Snackbar.make(App.getAppContext().getCurActivity().getWindow().getDecorView(), "請先登錄!", Snackbar.LENGTH_LONG)
                    .setAction("登錄", new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            TRouter.go(C.LOGIN);//跳轉到登錄頁面
                        }
                    }).show();
            return;
        }
        joinPoint.proceed();//執行原方法
    }
}

防止頻繁點擊

@Aspect
public class SingleClickAspect {
    static int TIME_TAG = R.id.click_time;
    public static final int MIN_CLICK_DELAY_TIME = 600;

    @Pointcut("execution(@com.app.annotation.aspect.SingleClick * *(..))")//方法切入點
    public void methodAnnotated() {
    }

    @Around("methodAnnotated()")//在連接點進行方法替換
    public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        View view = null;
        for (Object arg : joinPoint.getArgs())
            if (arg instanceof View) view = (View) arg;
        if (view != null) {
            Object tag = view.getTag(TIME_TAG);
            long lastClickTime = ((tag != null) ? (long) tag : 0);
            LogUtils.showLog("SingleClickAspect", "lastClickTime:" + lastClickTime);
            long currentTime = Calendar.getInstance().getTimeInMillis();
            if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {//過濾掉600毫秒內的連續點擊
                view.setTag(TIME_TAG, currentTime);
                LogUtils.showLog("SingleClickAspect", "currentTime:" + currentTime);
                joinPoint.proceed();//執行原方法
            }
        }
    }
}

OK,用戶行為路徑什么的我就沒有實現了,是用來裝逼的

那么問題來了,說好的AspectJ執行之后修改的class代碼去哪里了?
我們來看\build\intermediates\classes下面對應的class的原來代碼變了么?

源代碼

        @Override
    protected void onPause() {
        super.onPause();
        Timber.e("befor testAspect() ");
        testAspect();
        Timber.e("after testAspect() ");
    }

    @MonitorLog
    public void testAspect() {
        Timber.e("testAspect() inn start");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Timber.e("testAspect() inn end");
    }

\build\intermediates\classes下面

    protected void onPause() {
        super.onPause();
        Timber.e("testAspect() befor", new Object[0]);
        this.testAspect();
        Timber.e("testAspect() after", new Object[0]);
    }

    @MonitorLog
    public void testAspect() {
        JoinPoint var2 = Factory.makeJP(ajc$tjp_2, this, this);
        MonitorAspect var10000 = MonitorAspect.aspectOf();
        Object[] var3 = new Object[]{this, var2};
        var10000.aroundJoinPoint((new MainActivity$AjcClosure5(var3)).linkClosureAndJoinPoint(69648));
    }

為什么要加日志?沒錯我就是想看一下AspectJ切片對原邏輯性能是否有影響,我相信這個對于加入項目的選型可能是致命的,下面來看我實際測試的日志

06-08 16:23:33.895 31713-31713/ E/MainActivity: befor testAspect() 
06-08 16:23:33.895 31713-31713/ E/MonitorAspect: MonitorAspect start
06-08 16:23:33.895 31713-31713/ E/MainActivity: testAspect() inn start
06-08 16:23:33.997 31713-31713/ E/MainActivity: testAspect() inn end
06-08 16:23:33.997 31713-31713/ E/MonitorAspect: MonitorAspect --->: MainActivity.testAspect:[Ljava.lang.Object;@7f6e61b --->:[101ms]
06-08 16:23:33.998 31713-31713/ E/MonitorAspect: MonitorAspect end
06-08 16:23:33.998 31713-31713/ E/MainActivity: after testAspect() 

這就說明在編譯器做的代理性能還是非常高的
可以說AspectJ是面向切面編程最好的切入點了

相關代碼傳送門

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,302評論 25 708
  • 一、簡述 1、AOP的概念 如果你用java做過后臺開發,那么你一定知道AOP這個概念。如果不知道也無妨,套用百度...
    GitLqr閱讀 4,242評論 6 25
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,933評論 18 139
  • 1. AOP與OOP的區別 平時我接觸多的就是OOP(Object Oriented Programming面向對...
    生椰拿鐵錘閱讀 2,412評論 3 22
  • 汽車從馬路駛過鳥兒從枝頭飛過船從港口飄過魚在礁石的邊上游過 窗口后的眼睛香爐里信仰的灰燼樓梯口靜默的高跟鞋午夜里幽...
    劉漢皇閱讀 162評論 1 2