Android入门第66天-使用AOP

开篇

        这篇恐怕又是一篇补足网上超9成关于这个领域实际都是错的、用不起来的一个知识点了。网上太多太多教程和案例用的是一个叫hujiang的AOP组件-com.hujiang.aspectjx:gradle-android-plugin-aspectjx

        首先这些错的文章我不知道是怎么来的,其次那些案例真的运行成功过吗?它们用的都是aspectjx功能,是为了兼容Kotlin。

        还是那句话,Java开发和Kotlin还有Flutter开发到底有什么区别?整一堆无用功、把框架搞得很复杂、一堆错误的无用的代码混在一起,这才是本质。开发了好用什么语言都可以开发出好东西来,开发了不好就只剩下了:我用的开发语言是最先进的。因此我们坚持使用Java。

        扯回来,我们继续来看AOP功能在Android中的使用,由于Android是Java语言,因此它也支持aspectj功能,用法和在spring系里使用一模一样的简单。只不过都是被了这个叫:com.hujiang.aspectjx:gradle-android-plugin-aspectjx搞了太复杂了,甚至有人几天都调不通一个AOP功能。

        因此本篇就是交给大家正确的、纯净的、纯真的AOP使用方法。

AOP的使用场景

        AOP使用场景太多了,如:拦截方法打印日志,特别是:APP里有很多activity,它要求用户必须登录才可以打开,如:用户积分、我的历史订单等。对于这些界面,我们要求如果用户不登录那么APP自动跳到登录页。

        这种操作就是用的AOP功能去实现的。

如何使用一个AOP来判断用户打开界面前是否已经登录

        一个activity从进入到展示界面在onCreate方法里一般会经历这么几个方法,这里的initMain()就是用来展示界面的。

Android入门第66天-使用AOP_第1张图片

我们为了做这个界面在展示前判断用户是否已经登录,我们会这么“切”一刀。

Android入门第66天-使用AOP_第2张图片

然后把这个判断用户是否已经登录做成一个和后端交互的API,整个是否登录的判断逻辑如下截图:

Android入门第66天-使用AOP_第3张图片

动手使用AOP实现

        我们假设后台的这个接口如下所示(这边用了上一篇讲到的retrofit2+okhttp3+rxjava)。

package com.mkyuan.aset.mall.android.login.api;

import com.mkyuan.aset.mall.android.login.model.LoginResponseBean;

import io.reactivex.Observable;
import okhttp3.RequestBody;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.POST;

public interface LoginAPI {
    @POST("/api/account/checkLoginUT")
    Observable  checkLoginUT(@Header("ut") String ut);
}

        所以我们现在开始动手制作我们的AOP了。

先引入AOP包

        我们这边不会使用网上的已经不维护的、一堆问题的“com.hujiang.aspectjx:gradle-android-plugin-aspectjx”。

        请直接使用“org.aspectj:aspectjrt:1.9.6”。为此

第一步:编程全局build.gradle

加入以下语句:

dependencies {
        classpath 'org.aspectj:aspectjtools:1.9.6'
}

第二步:编辑我们的模块级别的build.gradle文件

先引入aspectjrt包

    implementation 'org.aspectj:aspectjrt:1.9.6' //引入 aspectj

然后再要在同一个build.gradle中加入如下语句

//使得aop生效
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

// 获取log打印工具和构建配置
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
    if (!variant.buildType.isDebuggable()) {
        // 判断是否debug,如果打release把return去掉就可以
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        // return;
    }
    // 使aspectj配置生效
    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-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);
        //在编译时打印信息如警告、error等等
        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;
            }
        }
    }
}

加完后的build.gradle长这个样

/**
 * following plugins is the original version
 */
/*
plugins {
    id 'com.android.application'
}
*/
/**
 * by using aspectj must ad following lines tart
 */
apply plugin: 'com.android.application'
//apply plugin: 'android-aspectjx' //暂时注了
/**
 * by using aspectj must ad following lines tart
 */
android {
    namespace 'com.mkyuan.aset.mall.android'
    compileSdk 32

    defaultConfig {
        applicationId "com.mkyuan.aset.mall.android"
        minSdk 27
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    dataBinding {
        enabled = true
    }

    //aspectjx {
    //    exclude "**/module-info.class"
    //    exclude "META-INF.versions.9.module-info"
    //    exclude "META-INF/versions/9/*.class"
    //    exclude 'com.google', 'com.squareup', 'org.apache','com.taobao','com.ut'
    //    exclude 'androidx', 'com.squareup', 'com.alipay', 'org.apache', 'org.jetbrains.kotlin',
    //    "module-info", 'versions.9'
    //}

}

dependencies {
    implementation 'org.aspectj:aspectjrt:1.9.6' //引入 aspectj
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
    implementation 'org.aspectj:aspectjrt:1.9.6'

    implementation 'io.github.youth5201314:banner:2.2.2'
    //com.google.android.material.theme
    //implementation 'com.google.android.material:material:'
    //retrofit2
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    //日志拦截器
    implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    //rxjava
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.12'
    //gson
    implementation 'com.google.code.gson:gson:2.8.7'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

//使得aop生效
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

// 获取log打印工具和构建配置
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
    if (!variant.buildType.isDebuggable()) {
        // 判断是否debug,如果打release把return去掉就可以
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        // return;
    }
    // 使aspectj配置生效
    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-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);
        //在编译时打印信息如警告、error等等
        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;
            }
        }
    }
}

制作AOP相关的代码

Android入门第66天-使用AOP_第4张图片

Login.java-定义一个基于方法切面的annotation

package com.mkyuan.aset.mall.android.util.aop.login;

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

//不需要回调的处理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
}

LoginAspect.java -使用Around模式对含有@Login的方法进行切面

package com.mkyuan.aset.mall.android.util.aop.login;

import android.util.Log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

@Aspect
public class LoginAspect {
    private static final String TAG = "LoginAspect";

    @Pointcut("execution(@com.mkyuan.aset.mall.android.util.aop.login.Login  * *(..))")
    public void executionCheckLogin() {
    }

    //不带回调的注解处理
    @Around("executionCheckLogin()")
    public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        Log.i(TAG, ">>>>>>走进AOP方法");
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            throw new RuntimeException("该注解只能用于方法上");
        }
        Login login = ((MethodSignature) signature).getMethod().getAnnotation(Login.class);
        if (login == null)
            return;
        //判断当前是否已经登录

        LoginManager.isLogin(new LoginCheckCallBack() {
            @Override
            public void changeValue(boolean loginResult) {
                if(loginResult) {
                    Log.i(TAG, ">>>>>>用户己登录走入下一步");
                    try {
                        joinPoint.proceed();
                    } catch (Throwable e) {
                        Log.e(TAG,e.getMessage(),e);
                    }
                }else{
                    //如果未登录,去登录页面
                    Log.i(TAG, ">>>>>>用户未登录去登录页面");
                    LoginManager.gotoLoginPage();
                }
            }
        });
    }
}

LoginManager.java

        内含有和后台是否登录接口交互以及判断用户如果已经登录那么继续“走下去-打开界面”,否则跳到Login登录界面的逻辑

package com.mkyuan.aset.mall.android.util.aop.login;

import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

import com.google.gson.Gson;
import com.mkyuan.aset.mall.android.home.MainActivity;
import com.mkyuan.aset.mall.android.login.LoginActivity;
import com.mkyuan.aset.mall.android.login.SmsLoginActivity;
import com.mkyuan.aset.mall.android.login.api.LoginAPI;
import com.mkyuan.aset.mall.android.login.model.LoginResponseBean;
import com.mkyuan.aset.mall.android.network.BaseObserver;
import com.mkyuan.aset.mall.android.network.NetworkApi;
import com.mkyuan.aset.mall.android.util.ContextUtils;
import com.mkyuan.aset.mall.android.util.SharedPreferenceHelper;
import com.mkyuan.aset.mall.android.util.activity.ActivityCollector;

import java.util.Map;

import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.RequestBody;

public class LoginManager {
    private static final String TAG = "LoginAspect";

    public static void isLogin(LoginCheckCallBack loginCheckCallBack) {
        Context ctx = null;
        try {
            ctx = ContextUtils.getCurApplicationContext();
            SharedPreferenceHelper spHelper = new SharedPreferenceHelper(ctx);
            Map data = spHelper.read();
            if (data.get("ut") != null && data.get("ut").trim().length() > 0) {
                String utValue = data.get("ut");
                //开始调用okhttp3, retrofit, rxjava框架
                LoginAPI loginAPI = NetworkApi.createService(LoginAPI.class);
                //loginAPI.checkLoginUT().
                loginAPI.checkLoginUT(utValue).compose(NetworkApi.applySchedulers(new BaseObserver() {
                    @Override
                    public void onSuccess(LoginResponseBean loginResponseBean) {
                        Log.i(TAG, ">>>>>>" + new Gson().toJson(loginResponseBean));
                        int returnCode = loginResponseBean.getCode();
                        String returnMsg = loginResponseBean.getMessage();
                        if (returnCode == 0) {
                            //result = true;
                            loginCheckCallBack.changeValue(true);
                            Log.i(TAG,
                                    ">>>>>>get verifiedCode->" + loginResponseBean.getData());
                            //startActivity(new Intent(SmsLoginActivity.this, MainActivity
                            // .class));
                        } else {
                            loginCheckCallBack.changeValue(false);
                        }
                    }

                    @Override
                    public void onFailure(Throwable e) {
                        Log.e(TAG, ">>>>>>Network Error: " + e.toString(), e);
                        loginCheckCallBack.changeValue(false);
                    }
                }));
            } else {
                loginCheckCallBack.changeValue(false);
            }
        } catch (Exception e) {
            Log.e(TAG, ">>>>>>isLogin error: " + e.getMessage());
            loginCheckCallBack.changeValue(false);
        }
    }

    public static void gotoLoginPage() {
        Context ctx = null;
        try {
            ctx = ContextUtils.getCurApplicationContext();
            Intent intent = new Intent();
            intent.setClass(ctx, LoginActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            ctx.startActivity(intent);
            ActivityCollector.getInstance().quitCurrentProcess("com.mkyuan.aset.mall.android.home.MainActivity");
        } catch (Exception e) {
            Log.e(TAG, ">>>>>>gotoLoginPage error: " + e.getMessage(), e);
        }
    }
}

使用这个AOP

        在MainActivity代码里有一个initMain方法,渲染界面的逻辑全部在这个initMain方法里。

Android入门第66天-使用AOP_第5张图片

接着我们来看这个initMain()方法。在方法前我们加入了自定义的“切入点”。

Android入门第66天-使用AOP_第6张图片

效果演示

第一次打开APP,用户未登录,因此直接被跳到了Login界面

Android入门第66天-使用AOP_第7张图片
Android入门第66天-使用AOP_第8张图片

然后输入手机,点获取验证码

Android入门第66天-使用AOP_第9张图片

按提交,登录成功。

然后关闭APP,再次打开APP

由于之前用户已经登录过了,因此AOP直接会把用户带入到主页

Android入门第66天-使用AOP_第10张图片

结合着我上一篇:retrofit2+okhttp3+rxjava自己不妨动动手试试看吧

你可能感兴趣的:(Android从入门到精通,安卓,AOP,android,aop,android,aspectj,Android,登录,登录,aop)