Android使用AOP做登录拦截

常见App中有两大类,一类是需要通过登录才能进入的App,另一类是不用登录,但是使用相关功能过程中需要登录后才能操作。那么第一类我们常见的做法就是,每次点击按钮的时候去用逻辑判断来实现,大大增加了工作量,使用AOP只需要一个注解即可解决。

AOP面向切面编程

Spring有两大特性,一个是Ioc,另一个则是Aop。关于Aop相关术语简介如下:

  • 通知:通知定义了切面是什么以及何时使用的概念。Spring切面可以应用5种类型的通知:

(1)前置通知(Before):在目标方法被调用之前调用通知功能
(2)后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
(3)返回通知(After-returning):在目标方法成功执行之后调用通知
(4)异常通知(After-throwing):在目标方法抛出异常后调用通知
(5)环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

  • 连接点:是在应用执行过程中能够插入切面的一个点
  • 切点: 切点定义了切面在何处要织入的一个或者多个连接点
  • 切面:是通知和切点的结合。通知和切点共同定义了切面的全部内容
  • 引入:引入允许我们向现有类添加新方法或属性
  • 织入:是把切面应用到目标对象,并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期中有多个点可以进行织入:

(1)编译期:在目标类编译时,切面被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的
(2)类加载期:切面在目标加载到JVM时被织入。这种方式需要特殊的类加载器(class loader)它可以在目标类被引入应用之前增强该目标类的字节码
(3)运行期:切面在应用运行到某个时刻时被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面的

AspectJ框架实现

gradle配置

在app目录下的build.gradle中配置

apply plugin: 'com.android.application'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.10'
        classpath 'org.aspectj:aspectjweaver:1.8.10'
    }
}

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "me.yezhou.authfilter"
        minSdkVersion 15
        targetSdkVersion 29
        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 1.8
        targetCompatibility 1.8
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

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
    Task javaCompile
    if (variant.hasProperty('javaCompileProvider')) {
        // Android 3.3.0+
        javaCompile = variant.javaCompileProvider.get()
    } else {
        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
            }
        }
    }
}

创建注解LoginFilter

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginFilter {
    int loginDefine() default 0;
}

注解知识点

(1)注解的定义:Java文件叫做Annotation,用@interface表示

(2)元注解:@interface上面按需要注解一些东西,包括@Retention@Target@Document@Inherited四种

(3)注解的保留策略:

@Retention(RetentionPolicy.SOURCE) // 注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

(4)注解的作用目标:

@Target(ElementType.TYPE) // 接口、类、枚举、注解
@Target(ElementType.FIELD) // 字段、枚举的常量
@Target(ElementType.METHOD) // 方法
@Target(ElementType.PARAMETER) // 方法参数
@Target(ElementType.CONSTRUCTOR) // 构造函数
@Target(ElementType.LOCAL_VARIABLE) // 局部变量
@Target(ElementType.ANNOTATION_TYPE) // 注解
@Target(ElementType.PACKAGE) // 包

(5)注解包含在javadoc中:@Documented

(6)注解可以被继承:@Inherited

(7)注解解析器:用来解析自定义注解

登录相关回调接口以及工具辅助类

登录相关回调接口,方法:登录、已登录、清除登录状态等,可以根据自己的需求定义:

public interface ILoginFilter {
    void login(Context applicationContext, int loginDefine);
    boolean isLogin(Context applicationContext);
    void clearLoginStatus(Context applicationContext);
}

辅助工具类,获取接口,上下文等,使用单例模式:

public class LoginAssistant {
    private static LoginAssistant instance;

    private LoginAssistant() {
    }

    public static LoginAssistant getInstance() {
        if (instance == null) {
            synchronized (LoginAssistant.class) {
                if (null == instance) {
                    instance = new LoginAssistant();
                }
            }
        }
        return instance;
    }

    private ILoginFilter iLoginFilter;

    public ILoginFilter getILoginFilter() {
        return iLoginFilter;
    }

    public void setILoginFilter(ILoginFilter iLoginFilter) {
        this.iLoginFilter = iLoginFilter;
    }

    private Context applicationContext;

    public Context getApplicationContext() {
        return applicationContext;
    }

    public void setApplicationContext(Context applicationContext) {
        this.applicationContext = applicationContext;
    }
}

及初始化管理类:

public class LoginManger {
    private static LoginManger instance;

    private LoginManger() {
    }

    public static LoginManger getInstance() {
        if (null == instance) {
            synchronized (LoginManger.class) {
                if (null == instance) {
                    instance = new LoginManger();
                }
            }
        }
        return instance;
    }

    public void init(Context context, ILoginFilter iLoginFilter) {
        LoginAssistant.getInstance().setApplicationContext(context);
        LoginAssistant.getInstance().setILoginFilter(iLoginFilter);
    }
}

重点切面拦截:

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

    @Pointcut("execution(@me.yezhou.authfilter.annotation.LoginFilter * * (..))")
    public void LoginFilter() {
    }

    @Around("LoginFilter()")
    public void aroundLoginPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取用户实现的ILogin类,如果没有调用init()设置初始化就抛出异常
        ILoginFilter iLoginFilter = LoginAssistant.getInstance().getILoginFilter();
        if (iLoginFilter == null) {
            throw new RuntimeException("LoginManger没有初始化");
        }
        //先得到方法的签名methodSignature,然后得到@LoginFilter注解,如果注解为空,就不再往下走
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MemberSignature)) {
            throw new RuntimeException("该注解只能用于方法上");
        }
        MethodSignature methodSignature = (MethodSignature) signature;
        LoginFilter loginFilter = methodSignature.getMethod().getAnnotation(LoginFilter.class);
        if (loginFilter == null) {
            return;
        }
        Context context = LoginAssistant.getInstance().getApplicationContext();
        //调用iLogin的isLogin()方法判断是否登录,这个isLogin是留给使用者自己实现的,如果登录,就会继续执行方法体调用方法直到完成,如果没有登录,调用iLogin的login方法
        if (iLoginFilter.isLogin(context)) {
            joinPoint.proceed();
        } else {
            iLoginFilter.login(context, loginFilter.loginDefine());
        }
    }
}

在Application中进行初始化:

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        LoginManger.getInstance().init(this, iLoginFilter);
    }

    ILoginFilter iLoginFilter = new ILoginFilter() {
        @Override
        public void login(Context applicationContext, int loginDefine) {
            switch (loginDefine) {
                case 0:
                    Intent intent = new Intent(applicationContext, LoginActivity.class);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(intent);
                    break;
                case 1:
                    Toast.makeText(applicationContext, "您还没有登录,请登陆后执行!", Toast.LENGTH_SHORT).show();
                    break;
                case 2:
                    Toast.makeText(applicationContext, "执行失败,因为您还没有登录!", Toast.LENGTH_SHORT).show();
                    break;
            }
        }

        @Override
        public boolean isLogin(Context applicationContext) {
            return SharePreferenceUtil.getBooleanSp(applicationContext, SharePreferenceUtil.IS_LOGIN);
        }

        @Override
        public void clearLoginStatus(Context applicationContext) {
            SharePreferenceUtil.setBooleanSp(applicationContext, SharePreferenceUtil.IS_LOGIN, false);
        }
    };
}

使用方法

在需要做登录拦截的方法上加上以上注解:

@LoginFilter(loginDefine = 0)
@Override
public void onClick(View v) {
    startActivity(new Intent(this, SecondActivity.class));
}

点击按钮,如果已经登录,则执行这个目标方法体,如果没有登录,则会拦截,拦截时回调ILoginFilter接口,根据注解中的值loginDefine进行相关判断处理逻辑,loginDefine可以自行赋值,根据需求而定。

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/18/android-uses-aop-for-login-interception/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Android使用AOP做登录拦截
常见App中有两大类,一类是需要通过登录才能进入的App,另一类是不用登录,但是使用相关功能过程中需要登录后才能操作。那么第一类我们常见的做法就是,每次点……
<<上一篇
下一篇>>
文章目录
关闭
目 录