美文网首页APP & programAndroid Other
Android登录拦截的场景-面向切面基于AOP实现

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

作者: 安安_660c | 来源:发表于2022-10-30 20:18 被阅读0次

    一、了解面向切面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框架

    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>()
        }
    

    来自:https://juejin.cn/post/7132643283083198501

    相关文章

      网友评论

        本文标题:Android登录拦截的场景-面向切面基于AOP实现

        本文链接:https://www.haomeiwen.com/subject/fnultdtx.html