Android面向切面基于AOP实现登录拦截的场景示例

目录
  • 前言
  • 一、了解面向切面AOP
  • 二、集成AOP框架
  • 三、定义注解实现功能
  • 总结

前言

场景如下:用户第一次下载App,点击进入首页列表,点击个人页面,需要校验登录,然后跳转到登录页面,注册/登录完成跳转到个人页面。

非常常见的场景,正常我们开发就只能判断是否已经登录,如果未登录就跳转到登录,然后登录完成之后怎么继续执行?如何封装?有哪些方式?其实很多人并不清楚。

这里做一个系列总结一下,看看公共有多少种方式实现,你们使用的是哪一种方案,或者说你们觉得哪一种方案最好用。

这一次分享的是全网最多的方案 ,面向切面 AOP 的方式。你去某度一搜,Android拦截登录 最多的结果就是AOP实现登录拦截的功能,既然大家都推荐,我们就来看看它到底如何?

一、了解面向切面AOP

我们学习Java的开始,我们一直就知道 OOP 面向对象,其实 AOP 面向切面,是对OOP的一个补充,AOP采取横向收取机制,取代了传统纵向继承体系重复性代码,把某一类问题集中在一个地方进行处理,比如处理程序中的点击事件、打印日志等。

AOP是编程思想就是把业务逻辑和横切问题进行分离,从而达到解耦的目的,提高代码的重用性和开发效率。OOP的精髓是把功能或问题模块化,每个模块处理自己的家务事。但在现实世界中,并不是所有功能都能完美得划分到模块中。AOP的目标是把这些功能集中起来,放到一个统一的地方来控制和管理。

我记得我最开始接触 AOP 还是在JavaEE的框架SSH的学习中,AspectJ框架,开始流行于后端,现在在Android开发的应用中也越来越广泛了,Android中使用AspectJ框架的应用也有很多,比如点击事件防抖,埋点,权限申请等等不一而足,这里不展开说明,毕竟我们这一期不是专门讲AspectJ的应用的。

简单的说一下AOP的重点概念(摘抄):

  • 前置通知(Before):在目标方法被调用之前调用通知功能。
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
  • 返回通知(After-returning):在目标方法成功执行之后调用通知。
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知。
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
  • 连接点:是在应用执行过程中能够插入切面的一个点。
  • 切点: 切点定义了切面在何处要织入的一个或者多个连接点。
  • 切面:是通知和切点的结合。通知和切点共同定义了切面的全部内容。
  • 引入:引入允许我们向现有类添加新方法或属性。
  • 织入:是把切面应用到目标对象,并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期中有多个点可以进行织入:
  • 编译期: 在目标类编译时,切面被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
  • 类加载期:切面在目标加载到JVM时被织入。这种方式需要特殊的类加载器(class loader)它可以在目标类被引入应用之前增强该目标类的字节码。
  • 运行期: 切面在应用运行到某个时刻时被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面的。

简单理解就是把一个方法拿出来,在这个方法执行前,执行后,做一些特别的操作。关于AOP的基本使用推荐大家看看大佬的教程:

深入理解Android之AOP

不多BB,我们直接看看Android中如何使用AspectJ实现AOP逻辑,实现拦截登录的功能。

二、集成AOP框架

Java项目集成

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

组件build.gradle

dependencies {
    implementation 'org.aspectj:aspectjrt:1.9.6'
}
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;
            }
        }
    }
}

Kotlin项目集成

dependencies {
        classpath 'com.android.tools.build:gradle:3.6.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'

项目build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'android-aspectjx'
android {
    ...
    // AOP 配置
    aspectjx {
        // 排除一些第三方库的包名(Gson、 LeakCanary 和 AOP 有冲突)
        exclude 'androidx', 'com.google', 'com.squareup', 'com.alipay', 'com.taobao',
                'org.apache',
                'org.jetbrains.kotlin',
                "module-info", 'versions.9'
    }
}
ependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'org.aspectj:aspectjrt:1.9.5'
}

集成AOP踩坑: zip file is empty

和第三方包有冲突,比如Gson,OkHttp等,需要配置排除一下第三方包,

gradle版本兼容问题

AGP版本4.0以上不支持 推荐使用3.6.1

kotlin兼容问题 :

基本都是推荐使用 com.hujiang.aspectjx

编译版本兼容问题:

4.0以上使用KT编译版本为Java11需要改为Java8

组件化兼容问题:

如果在library的moudle中自定义的注解, 想要通过AspectJ来拦截织入, 那么这个@Aspect类必须和自定义的注解在同一moudle中, 否则是没有效果的

等等...

难点就在集成,如何在指定版本的Gradle,Kotlin项目中集成成功。只要集成成功了,使用到是简单了。

三、定义注解实现功能

定义标记的注解

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

定义处理类

@Aspect
public class LoginAspect {
    @Pointcut("@annotation(com.guadou.kt_demo.demo.demo3_bottomtabbar_fragment.aop.Login)")
    public void Login() {
    }
     //不带回调的注解处理
    @Around("Login()")
    public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        YYLogUtils.w("走进AOP方法-Login()");
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature)){
            throw new RuntimeException("该注解只能用于方法上");
        }
        Login login = ((MethodSignature) signature).getMethod().getAnnotation(Login.class);
        if (login == null) return;
        //判断当前是否已经登录
        if (LoginManager.isLogin()) {
            joinPoint.proceed();
        } else {
            //如果未登录,去登录页面
            LoginManager.gotoLoginPage();
        }
    }
object LoginManager {
    @JvmStatic
    fun isLogin(): Boolean {
        val token = SP().getString(Constants.KEY_TOKEN, "")
        YYLogUtils.w("LoginManager-token:$token")
        val checkEmpty = token.checkEmpty()
        return !checkEmpty
    }
    @JvmStatic
    fun gotoLoginPage() {
        commContext().gotoActivity<LoginDemoActivity>()
    }
}

其实逻辑很简单,就是判断是否登录,看是放行还是跳转到登录页面

使用的逻辑也是很简单,把需要处理的逻辑使用方法抽取,并标记注解即可

    override fun init() {
        mBtnCleanToken.click {
            SP().remove(Constants.KEY_TOKEN)
            toast("清除成功")
        }
        mBtnProfile.click {
           //不带回调的登录方式
           gotoProfilePage2()
        }
    }
    @Login
    private fun gotoProfilePage2() {
        gotoActivity<ProfileDemoActivity>()
    }

效果:

这..这和我使用Token自己手动判断有什么区别,完成登录之后还得我再点一次按钮,当然了这只是登录拦截,我想要的是登录成功之后继续之前的操作,怎么办?

其实使用AOP的方式的话,我们可以使用消息通知的方式,比如LiveBus FlowBus之类的间接实现这个效果。

我们先单独的定义一个注解

//需要回调的处理用来触发用户登录成功后的后续操作
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginCallback {
}

修改定义的切面类

@Aspect
public class LoginAspect {
    @Pointcut("@annotation(com.guadou.kt_demo.demo.demo3_bottomtabbar_fragment.aop.Login)")
    public void Login() {
    }
    @Pointcut("@annotation(com.guadou.kt_demo.demo.demo3_bottomtabbar_fragment.aop.LoginCallback)")
    public void LoginCallback() {
    }
    //带回调的注解处理
    @Around("LoginCallback()")
    public void loginCallbackJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        YYLogUtils.w("走进AOP方法-LoginCallback()");
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature)){
            throw new RuntimeException("该注解只能用于方法上");
        }
        LoginCallback loginCallback = ((MethodSignature) signature).getMethod().getAnnotation(LoginCallback.class);
        if (loginCallback == null) return;
        //判断当前是否已经登录
        if (LoginManager.isLogin()) {
            joinPoint.proceed();
        } else {
            LifecycleOwner lifecycleOwner = (LifecycleOwner) joinPoint.getTarget();
            LiveEventBus.get("login").observe(lifecycleOwner, new Observer<Object>() {
                @Override
                public void onChanged(Object integer) {
                    try {
                        joinPoint.proceed();
                        LiveEventBus.get("login").removeObserver(this);
                    } catch (Throwable throwable) {
                        throwable.printStackTrace();
                        LiveEventBus.get("login").removeObserver(this);
                    }
                }
            });
            LoginManager.gotoLoginPage();
        }
    }
    //不带回调的注解处理
    @Around("Login()")
    public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        YYLogUtils.w("走进AOP方法-Login()");
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature)){
            throw new RuntimeException("该注解只能用于方法上");
        }
        Login login = ((MethodSignature) signature).getMethod().getAnnotation(Login.class);
        if (login == null) return;
        //判断当前是否已经登录
        if (LoginManager.isLogin()) {
            joinPoint.proceed();
        } else {
            //如果未登录,去登录页面
            LoginManager.gotoLoginPage();
        }
    }
}

在去登录页面之前注册一个LiveEventBus事件,当登录完成之后发出通知,这里就直接放行调用注解的方法。即可完成继续执行的操作。

使用:

    override fun init() {
        mBtnCleanToken.click {
            SP().remove(Constants.KEY_TOKEN)
            toast("清除成功")
        }
        mBtnProfile.click {
           //不带回调的登录方式
           gotoProfilePage()
        }
    }
    @LoginCallback
    private fun gotoProfilePage() {
        gotoActivity<ProfileDemoActivity>()
    }

效果:

总结

从上面的代码我们就基于AOP思想实现了登录拦截功能,以后我们对于需要用户登录之后才能使用的功能只需要在对应的方法上添加指定的注解即可完成逻辑,彻底摆脱传统耗时耗力的开发方式。

需要注意的是AOP框架虽然使用起来很方便,能帮我们轻松完成函数插桩功能,但是它也有自己的缺点。

AspectJ 在实现时会包装自己的一些类,不仅会影响切点方法的性能,还会导致安装包体积的增大。

最关键的是对Kotlin不友好,对高版本AGP不友好,所以大家在使用时需要仔细权衡是否适合自己的项目。如有需求可以运行源码查看效果。源码在此

以上就是Android面向切面基于AOP实现登录拦截的场景示例的详细内容,更多关于Android AOP登录拦截的资料请关注我们其它相关文章!

(0)

相关推荐

  • 浅谈Android面向切面编程(AOP)

    一.简述 1.AOP的概念 如果你用java做过后台开发,那么你一定知道AOP这个概念.如果不知道也无妨,套用百度百科的介绍,也能让你明白这玩意是干什么的: AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合

  • Android 采用AOP方式封装6.0权限管理的方法

    [一]背景 6.0运行时申请权限已经是一个老生常谈的内容了,最近项目TargetSDKVersion升到23以上,所以我们也需要做权限管理,我想到的需求是这样的: 1.支持单个权限.多个权限申请 2.运行时申请 3.无侵入式申请,无需关注权限申请的逻辑 4.除了Activity.Fragment之外,还需要支持Service中申请 5.对国产手机做兼容处理 第一.二点,Google都有对应的API: 第三点可以通过自定义注解+AOP切面方式来解决.为什么采用AOP方式呢?首先看AOP定义: 面向

  • Android基于方法池与回调实现登录拦截的场景

    目录 前言 一.使用通知与回调 二.使用方法池 总结 前言 前面的文章我们讲到APP登录拦截的功能实现,现在网上比较多的推荐使用AOP,我们使用下来还是太麻烦,兼容性问题很多,(坑太多,项目我已经改回来了,如果想体验AOP可以切换aop分支运行) 难道就想实现一个这么简单的功能,就非得使用AOP了吗?有没有简单一点的方式,方式其实太多了,个人感觉的话,完全没必要为了这么个小功能导入一个AOP库.今天我们看看使用方法池与通知回调的方式来处理登录拦截的逻辑. 一.使用通知与回调 其实本质逻辑就是想判

  • AndroidStudio 配置 AspectJ 环境实现AOP的方法

    昨天看了一段android配置aspectj实现AOP的直播视频,就试着自己配置了一下,可能是因为我自己的AndroidStudio环境的问题,碰到了不少的坑(其实还是因为对gradle理解的不多),但总归是配置好了,就分享一下. 试了两种方式,不过项目下的build.gradle,没什么变化,直接看一下代码吧: build.gradle(项目下) buildscript { ext { //android appcompat支持库版本 androidSupportVersion = '26.1

  • Android中AOP(面向切向编程)的深入讲解

    一.闲谈AOP 大家都知道OOP,即ObjectOriented Programming,面向对象编程.而本文要介绍的是AOP.AOP是Aspect Oriented Programming的缩写,中译文为面向切向编程.OOP和AOP是什么关系呢? 首先: l OOP和AOP都是方法论.我记得在刚学习C++的时候,最难学的并不是C++的语法,而是C++所代表的那种看问题的方法,即OOP.同样,今天在AOP中,我发现其难度并不在利用AOP干活,而是从AOP的角度来看待问题,设计解决方法.这就是为什

  • Android中AOP的应用实践之过滤重复点击

    前言 大家对AOP应该都不陌生, 就算没有用过也肯定听说过,切面编程一直是一个热点的话题,AOP即Aspect Oriented Programming的缩写,习惯称为切面编程;与OOP(面向对象编程)万物模块化的思想不同,AOP则是将涉及到众多模块的某一类问题进行统一管理,AOP的优点是将业务逻辑与系统化功能高度解耦,让我们在开发过程中可以只专注于业务逻辑,其他一些系统化功能(如路由.日志.权限控制.拦截器.埋点.事件防抖等)则由AOP统一处理; AspectJ简介 AOP是一种编程思想,或者

  • Android面向切面基于AOP实现登录拦截的场景示例

    目录 前言 一.了解面向切面AOP 二.集成AOP框架 三.定义注解实现功能 总结 前言 场景如下:用户第一次下载App,点击进入首页列表,点击个人页面,需要校验登录,然后跳转到登录页面,注册/登录完成跳转到个人页面. 非常常见的场景,正常我们开发就只能判断是否已经登录,如果未登录就跳转到登录,然后登录完成之后怎么继续执行?如何封装?有哪些方式?其实很多人并不清楚. 这里做一个系列总结一下,看看公共有多少种方式实现,你们使用的是哪一种方案,或者说你们觉得哪一种方案最好用. 这一次分享的是全网最多

  • Spring面向切面编程AOP详情

    目录 1. 面向切面编程 2. AOP核心概念 3. AOP的实现 4. Spring 对AOP支持 4.1 支持@Aspect 4.2 声明一个切面 4.3 声明一个切入点 4.4 声明增强 5. 用AOP实现日志拦截 5.1 一般的实现 5.2 仅拦截需要的方法 5.3 requestId传递 5.4 关于增强执行的顺序 6. 思考 1. 面向切面编程 定义:面向切面编程(AOP,Aspect Oriented Programming)是通过预编译方式和运行期间动态代理实现程序功能的统一维护

  • Python 面向切面编程 AOP 及装饰器

    目录 什么是 AOP 装饰器 函数装饰器 类装饰器 1.函数装饰函数 2.类装饰函数 3.函数装饰类 4.类装饰类 什么是 AOP AOP,就是面向切面编程,简单的说,就是动态地将代码切入到类的指定方法.指定位置上的编程思想就是面向切面的编程. 我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类.哪些方法则叫切入点.这样我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为. 这种思想,可以使原有代码逻辑更清晰,对原有代码毫无入侵性,常用于像权限

  • Spring 面向切面编程AOP实现详解

    简介 1.什么叫做面向切面编程? 概念:把一个个的横切关注点(某种业务的实现代码)放到某个模块中去,称之为切面.每个切面影响业务的一种功能,切面的目的就是为了功能增强,将需要增强的方法做成切面,实现对业务的增强,就是面向切面编程. 目的:将与业务本身无关,却被业务模块所共同调用的功能代码封装成切面,以减少系统的重复代码,降低耦合,提高可扩展性. 优势:把多个方法前/后的共同代码抽离出来,使用动态代理机制来控制,先执行抽离出来的代码,再执行每一个真实方法. 2.Spring中的AOP使用动态代理来

  • SpringBoot拦截器实现登录拦截的方法示例

    源码 GitHub:https://github.com/291685399/springboot-learning/tree/master/springboot-interceptor01 SpringBoot拦截器可以做什么 可以对URL路径进行拦截,可以用于权限验证.解决乱码.操作日志记录.性能监控.异常处理等 SpringBoot拦截器实现登录拦截 pom.xml: <?xml version="1.0" encoding="UTF-8"?> &

  • 详解spring面向切面aop拦截器

    spring中有很多概念和名词,其中有一些名字不同,但是从功能上来看总感觉是那么的相似,比如过滤器.拦截器.aop等. 过滤器filter.spring mvc拦截器Interceptor .面向切面编程aop,实际上都具有一定的拦截作用,都是拦截住某一个面,然后进行一定的处理. 在这里主要想着手的是aop,至于他们的比较,我想等三个都一一了解完了再说,因此这里便不做过多的比较. 在我目前的项目实践中,只在一个地方手动显示的使用了aop,那便是日志管理中对部分重要操作的记录. 据我目前所知,ao

  • Quarkus中的依赖注入DI和面向切面aop编程

    目录 前言 JSR365:Java2.0的上下文和依赖注规范 Bean声明和依赖注入 Bean的生命周期 条件化初始Bean 面向切面编程aop Bean列表接口 结语 前言 做java开发的肯定清楚spring中的核心思想ioc和aop,ioc即控制反转的意思,di的核心思想和ioc一样,描述的也是同一个事情同一个思想,只是di的依赖注入更容易被理解了,aop即面向切面,如注解事务功能,就是基于aop的思想来实现的.Quarkus中也实现了一套非标准的cdi规范,下面就来看看Quarkus中的

  • MVC AOP面向切面编程简单介绍及实例

    MVC AOP面向切面编程 AOP这个词相信大家都没有接触太多过,但是实际上你们已经有所接触了,就在设计模式中.AOP所用的思想其实和设计模式是一样的,即在不修改原代码的情况下统一增加或者修改功能.还有,AOP大多用在spring里面,但是本文所写的只是在MVC中的应用,要注意. 一.简介 所谓AOP(Aspect Oriented Programming的缩写)意为面向切面的编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是

随机推荐