Android 使用 AOP 入門學習

網上有很多介紹 AOP 的, 就不多說什麼了, 這裏就直接開始了.
場景: 比如界面上有 N 個按鈕, 有網絡的時候, 點擊按鈕可以進入下一頁, 沒有網絡的時候, 不響應點擊事件, 那麼使用 AOP 的這種方式就很容易實現, 當然, 這裏只是隨便說了一個場景, 實際上, 還有很多場景適合使用 AOP.

  1. 加依賴
implementation 'org.aspectj:aspectjrt:1.9.6'
  1. app build.gradle 中添加其他配置
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.9",
                         "-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;
            }
        }
    }
}

  1. 創建註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckNet {
    String value() default "";
}
  1. 創建切面類
//@Aspect 聲明這是一個切面類.
@Aspect
public class SectionAspect {
    //抽取公共的切入點表達式
    //本類引用的話, 可直接這樣寫 @Before("pointCut()") 
    //其他切面引用的話, 可以寫爲 @Before("com.example.aop_study.pointCut()")
    //
    @Pointcut("execution(@com.example.aop_study.CheckNet * * (..))")
    public void pointCut() {}

    @Around("pointCut()")
    public Object aroundHandle(ProceedingJoinPoint joinPoint) throws Throwable {
        //拿到方法簽名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        CheckNet checkNet = signature.getMethod().getAnnotation(CheckNet.class);
        if (checkNet != null) {
            //判斷有沒有網絡, 獲取 context.
            //getThis 表示當前切點方法所在的類
            Object obj = joinPoint.getThis();
            Context context = getContext(obj);
            if (context != null) {
                if (!isNetworkAvailable(context)) {
                    //不再向下執行
                    Log.e("SectionAspect","沒有網絡");
                    return null;
                }
            }

        }
        return joinPoint.proceed();
    }

    /**
     * 通過對象獲取上下文
     *
     * @param obj
     * @return
     */
    private Context getContext(Object obj) {
        if (obj instanceof Activity) {
            return (Activity) obj;
        } else if (obj instanceof Fragment) {
            Fragment fragment = (Fragment) obj;
            return fragment.getActivity();
        } else if (obj instanceof View) {
            View view = (View) obj;
            return view.getContext();
        }
        return null;
    }


    /**
     * 檢查當前網絡是否可用
     *
     * @return
     */
    private static boolean isNetworkAvailable(Context context) {
        // 獲取手機所有連接管理對象(包括對wi-fi,net等連接的管理)
        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

        if (Build.VERSION_CODES.LOLLIPOP <= Build.VERSION.SDK_INT) {
            Network[] networks = connectivityManager.getAllNetworks();
            NetworkInfo networkInfo;
            for (Network network : networks) {
                networkInfo = connectivityManager.getNetworkInfo(network);
                if (NetworkInfo.State.CONNECTED.equals(networkInfo.getState())) {
                    return true;
                }
            }
        } else {
            if (connectivityManager != null) {
                NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo();
                if (networkInfo != null && networkInfo.length > 0) {
                    for (int i = 0; i < networkInfo.length; i++) {
                        // 判斷當前網絡狀態是否爲連接狀態
                        if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }
}

關於切入點表達式, 詳細的可以參考 切入點表達式 我就不在這裏丟人現眼了. (Android 中使用這個表達式好像在 API 開發的時候使用有點不同, 好像是這樣.)

  1. 使用.
    @CheckNet
    public void test(View view) {
        Intent intent = new Intent(this,MainActivity.class);
        startActivity(intent);
    }

簡單粗暴. 在切面類中中使用的是環繞通知, 其實還有其他通知, 例如前置通知, 後置通知, 返回通知等. 但是一般都會使用環繞通知, 因爲如果使用後面幾個的話順序會錯亂. 剩下的分別爲

  • @Before 前置通知
  • @After 後置通知
  • @AfterReturning 返回通知, 返回通知可以獲取到返回值, 可以這樣寫
    @AfterReturning(value = "pointCut()",returning = "result")
     public void logReturn(JoinPoint joinPoint,Object result){
         System.out.println("@AfterReturning_返回結果爲{"+result+"}");
     }
    
  • @AfterThrowing 異常通知.寫法爲
      @AfterThrowing(value = "pointCut()",throwing = "exception")
      public void logException(Exception exception){
          System.out.println("@AfterThrowing_異常信息,{"+exception+"}");
      }
    

還有更多的 AOP 使用方式等待我們發現, 並且使用的話, 不需要擔心性能問題, 因爲使用後就是使用 Aspectj 的編譯器, class 文件由 Aspect 去編譯, 會將註解的代碼 copyclass 文件. 類似於在編譯的時候, 將我們網絡判斷的那段代碼 Copy 到了跳轉的地方, 有興趣的同學可以運行後反編譯看一下.
文末分享一位大神總結的關於 AOP 的一些常見的用法. 歸納AOP在Android開發中的幾種常見用法

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