Android使用AOP做登录拦截

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/wb_001/article/details/81975645

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

二、这里使用的是AOP,面向切面编程。知道Java开发的都知道,Spring有两大特性,一个是Ioc,另一个则是Aop。关于Aop相关术语简介如下:
通知: 通知定义了切面是什么以及何时使用的概念。Spring 切面可以应用5种类型的通知:

前置通知(Before):在目标方法被调用之前调用通知功能。
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
返回通知(After-returning):在目标方法成功执行之后调用通知。
异常通知(After-throwing):在目标方法抛出异常后调用通知。
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
连接点:是在应用执行过程中能够插入切面的一个点。

切点: 切点定义了切面在何处要织入的一个或者多个连接点。
切面:是通知和切点的结合。通知和切点共同定义了切面的全部内容。
引入:引入允许我们向现有类添加新方法或属性。
织入:是把切面应用到目标对象,并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期中有多个点可以进行织入:
编译期: 在目标类编译时,切面被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标加载到JVM时被织入。这种方式需要特殊的类加载器(class loader)它可以在目标类被引入应用之前增强该目标类的字节码。
运行期: 切面在应用运行到某个时刻时被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面的。

三、这里我们使用AspectJ这个框架来实现。废话不多说,上代码:

1、首先在AS中如果AspectJ的jar包(见附件),然后在app目录下的build.gradle中做配置:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.8'
        classpath 'org.aspectj:aspectjweaver:1.8.8'
    }
}
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;
            }
        }
    }
}

2、创建注解LoginFilter:

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

这里对注解做一些了解:

a.注解的定义:Java文件叫做Annotation,用@interface表示。
b.元注解:@interface上面按需要注解上一些东西,包括@Retention、@Target、@Document、@Inherited四种。
c.注解的保留策略:

  @Retention(RetentionPolicy.SOURCE) // 注解仅存在于源码中,在class字节码文件中不包含

  @Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得

  @Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

d.注解的作用目标:

  @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) // 包

e.注解包含在javadoc中:

  @Documented

f.注解可以被继承:

  @Inherited

g.注解解析器:用来解析自定义注解

3、写登录相关回调接口以及工具辅助类:写一个接口,方法:登录,已登录,清除登录状态等,这个可以根据自己的需求去定义:

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

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

public class LoginAssistant {
    private LoginAssistant(){}
    private static LoginAssistant instance;
    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(@com.szsftxx.loginfilter.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 mContext = LoginAssistant.getInstance().getApplicationContext();
        //调用iLogin的isLogin()方法判断是否登录,这个isLogin是留给使用者自己实现的,如果登录,就会继续执行方法体调用方法直到完成,如果没有登录,执行下一个
        if (iLoginFilter.isLogin(mContext)){
            joinPoint.proceed();
        }else {
            iLoginFilter.login(mContext,loginFilter.loginDefine());
        }

    }
}

对于这个切面拦截的相关内容,要了解的太多太多了,可以看看这个AOP之AspectJ语法详解 ,我也不是很懂,不敢乱bb。

最后就是初始化,初始化我们在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(SharePreferenceUtil.IS_LOGIN, applicationContext);
        }

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

四、使用方法,在你需要做登录拦截的方法上加上一下注解:


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

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

五、我的demo附件: demo下载

最后,要感谢这个大神的付出,我也是从这为大神的文章中学的,做了一些精简 谢谢

**六、**GIF演示:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/wb_001/article/details/81975645