未经同意禁止抄袭,如需转载请在显要位置标注

前言

登录应该是应用开发中一个很常见的功能,一般在应用中有两种登录,一种是一进入应用就必须登录才能使用(如微信和QQ等),另一种是需要登录的时候才会去登录(如淘宝京东等)。我在工作中遇到的大部分是第二种情况,针对于第二种的登录,我之前都是通过if(){}else()去判断是否登录的,但是这样项目结构庞大了之后就会使代码臃肿。因为判断用户登录状态是一个频次很高的操作,所以针对这方面我就考虑有没有一种方案既能很方便的判断登录状态又使代码很简洁。

想来想去方案有两种,一种是hook到AMS拦截startActivity中的intent,在启动activity的时候判断是否登录,如果没有对intent做动态替换,另一种就是通过AOP实现方法添加判断登录代码片段。hook对系统有兼容性,需要考虑到各个版本的api是否改动,而aop的实现方式与版本没有任何兼容性问题,所以最后就采用了aop的方式去实现app集中式登录。

集中式登录架构的使用

为什么我先讲架构的使用,是因为你只有知道了使用这种架构是多么方便,才会有兴趣去了解如何实现这种架构。好了,先来用demo给大家演示一下!

看完gif后,你是不是觉得这不就是一个很简单的demo,通过判断登陆状态跳转不同的页面嘛,有什么难的啊!demo是很简单,但你继续往下看代码,就会觉着这个代码实现是多么酷了!下面看代码:
我们在Application里进行初始化(初始化之后才能接收登录事件,所以越早越好)。

public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();LoginSDK.getInstance().init(this, new ILogin() {@Overridepublic void login(Context applicationContext, int userDefine) {switch (userDefine) {case 0:startActivity(new Intent(applicationContext, LoginActivity.class));break;case 1:Toast.makeText(applicationContext, "您还没有登录,请登录后执行", Toast.LENGTH_SHORT).show();break;case 2:new AlertDialog.Builder(MyApplication.this)...break;default:Toast.makeText(applicationContext, "执行失败,因为您还没有登录!", Toast.LENGTH_SHORT).show();break;}}@Overridepublic boolean isLogin(Context applicationContext) {return SharePreferenceUtil.getBooleanSp(SharePreferenceUtil.IS_LOGIN, applicationContext);}});}}
复制代码

可以看到初始化方法实现了ILogin接口,ILogin接口有两个方法,第一个login()用于接收登录事件,第二个方法isLogin是判断登录状态,这两个方法留给用户自己实现,提高架构的可用性。我们所有的登录请求都会回调到ILogin接口,这也意味着登录事件只有一个统一的入口,这也就是我们集中式登录架构的核心好处了。

好了,我们先来使用以下。

例子1:
//demo演示1 跳转到需要过滤登录的Activity
@LoginFilter(userDefine = 0)
public void onClick(View view) {startActivity(new Intent(this, SecondActivity.class));
}
复制代码

上面代码就是监听一个Button的点击事件,然后加入注解@LoginFilter,看方法实现只是跳转到SecondActivity,并没有登录逻辑的判断,但通过这个注解我们就可以在运行时检测是否登录,如果没有登录就会中断方法的执行,转而调用MyApplication里init()方法中我们自己实现的login()方法,login(Context applicationContext, int userDefine)方法中userDefine是留给用户自定义的一个值,为了区别使用哪种登录方式。是不是很简单?再来看例子二:

例子2:

如果我们嫌弃在需要判断登录状态的按钮上加入@LoginFilter()注解麻烦,而是想实现启动一个Activity自动判断是否登录,如果没有登录就回调到我们的ILogin接口,那么你只需要创建一个LoginFilterActivity如下:

//demo演示2 直接过滤登陆,不需要加注解,则继承LoginFilterActivity
public class LoginFilterActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (!LoginAssistant.getInstance().getiLogin().isLogin(getApplicationContext())) {//TODO: 你可以想做什么就做什么,在这里我让页面结束,并给用户提示Toast.makeText(this, "没有登录!", Toast.LENGTH_SHORT).show();finish();}}}
复制代码

然后我们让需要登录才能进入的Activity继承自LoginFilterActivity就可以了。假如UserActivity继承了LoginFilterActivity,当用户没有登录的时候,我们启动UserActivity的时候便会回调到我们的ILogin接口,是不是很方便,这就是我们今天要讲的集中式登录架构。

下面,我们来讲一讲如何实现这个架构。

AOP原理

我们先来了解一下AOP,因为这个架构是基于AOP编程实现的。

  • 什么是AOP

关于AOP是什么,这里我简单介绍一下,AOP是Aspect Oriented Programming的缩写,即面向切面编程,与面向对象编程(oop)是两种不同的思维方式,也可以看做是对oop的一种补充。传统的oop开发会提倡功能模块化等,而aop适合于针对某一类型的问题统一处理。AOP思想的讲解不是我们本篇文章的重点,如果有同学对AOP思想不是很理解,这里我推荐一篇文章,讲得很不错Java AOP & Spring AOP 原理和实现

  • AspectJ介绍

AspectJ是一个面向切面编程的一个框架,它扩展了java语言,并定义了实现AOP的语法。我们知道,在将.java文件编译为.class文件时默认使用javac编译工具,而AspectJ会有一套符合java字节码编码规范的编译工具来替代javac,在将.java文件编译为.class文件时,会动态的插入一些代码来做到对某一类特定东西的统一处理。我举个例子,比如在应用中有很多个button的onClick事件需要检测是否登录,如果没有登录则需要去登录之后才能继续执行,针对这一类型的问题,相对笨一点的做法就是在每一个onClick方法中都显式的去判断登录状态,这样不免过于麻烦。而我们用AOP的方式实现的话,就需要在每一个onClick方法上加入一个标注,让编译器在编译时能识别到这个标注,然后根据标注来生成一些代码检测登录状态。好了,如果有同学对AOP还不是很理解的话也不用急,下面我会用例子来给大家演示如何使用AOP实现统一的集中式登录。

AOP实现集中式登录

  • aspectj环境搭建

首先,我们导入AspectJ的jar包,AspectJ的jar网上一搜就有,也可以直接去我demo里面拿,LoginArchitecture AOP实现集中式登录 github链接点我。demo里jar包导入:
好了,导入jar后还需要在app.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
//标注1
final def log = project.logger
final def variants = project.android.applicationVariantsvariants.all { variant ->//标注2if (!variant.buildType.isDebuggable()) {log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")return;}//标注3JavaCompile javaCompile = variant.javaCompilejavaCompile.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);//标注4for (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:log.warn message.message, message.thrownbreak;case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}}
}
复制代码

关于上面这一大片代码就是对aspectj的配置,先看标注1,获取log打印工具和构建配置,然后标注2判断是否debug,如果打release把return去掉就可以,标注3处意思是使aspectj配置生效,标注4就是为了在编译时打印信息如警告、error等等,这些东西在网上也有很多,大家如果不理解,可以去搜索一下,这里不再详细解释。

  • 切面代码编写

好了,配置完上面的内容之后,我们就开始编写代码了,首先,定义一个注解LoginFilter,用来注解方法,以便在编译期被编译器检测到需要做切面的方法。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginFilter {int userDefine() default 0;}
复制代码

大家看到我在注解里加了个userDefine,就是为了给用户提供自定义实现,如根据userDifine值不同做不同的登录处理。

然后,编写LoginSDK文件用于初始化和接收登录事件,代码如下:

public class LoginSDK {public void init(Context context, ILogin iLogin) {applicationContext = context.getApplicationContext();LoginAssistant.getInstance().setApplicationContext(context);LoginAssistant.getInstance().setiLogin(iLogin);}//...}
复制代码

然后,新建LoginFilterAspect.java文件用来处理加入LoginFilter注解的方法,对这些方法做统一的切面处理。

@Aspect
public class LoginFilterAspect {private static final String TAG = "LoginFilterAspect";@Pointcut("execution(@com.xsm.loginarchitecture.lib_login.annotation.LoginFilter * *(..))")public void loginFilter() {}@Around("loginFilter()")public void aroundLoginPoint(ProceedingJoinPoint joinPoint) throws Throwable {//标注1ILogin iLogin = LoginAssistant.getInstance().getiLogin();if (iLogin == null) {throw new NoInitException("LoginSDK 没有初始化!");}//标注2Signature signature = joinPoint.getSignature();if (!(signature instanceof MethodSignature)) {throw new AnnotationException("LoginFilter 注解只能用于方法上");}MethodSignature methodSignature = (MethodSignature) signature;LoginFilter loginFilter = methodSignature.getMethod().getAnnotation(LoginFilter.class);if (loginFilter == null) {return;}Context param = LoginAssistant.getInstance().getApplicationContext();//标注3if (iLogin.isLogin(param)) {joinPoint.proceed();} else {iLogin.login(param, loginFilter.userDefine());}}
}复制代码

代码并不多,我们来一一解释。首先看loginFilter方法,这个方法上加入@Pointcut注解,并指定了LoginFilter注解的路径,@Pointcut注解包括aroundLoginPoint()方法上的@Around注解等都是AspectJ定义的API。@Pointcut注解代表切入点,具体就是指哪些方法需要被执行"AOP"。execution()里指定了LoginFilter注解的路径,即加入LoginFilter注解的方法就是需要处理的切面。@Around注解表示这个方法执行时机的前后都可以做切面处理,常用到的还有@Before、@After等等。@Before即方法执行前做处理,@After反之。
好了,aroundLoginPoint(ProceedingJoinPoint joinPoint)方法就是对切面的具体实现了,这里ProceedingJoinPoint参数意为环绕通知,这个类里面可以获取到方法的签名等各种信息。

标注1

首先看标注1处,我们先获取用户实现的ILogin类,如果没有调用init()设置初始化就抛出异常。

标注2

标注2处先得到方法的签名methodSignature,然后得到@LoginFilter注解,如果注解为空,就不再往下走。

标注3

然后看标注3,调用iLogin的isLogin()方法判断是否登录,这个isLogin是留给使用者自己实现的,如果登录,就会继续执行方法体调用方法直到完成,如果没有登录,调用ilogin的login方法,并把userDefine传过去,login方法是用户自己实现的。

好了,切面代码的处理介绍完了,这个时候我们build一下项目,会在项目下\build\intermediates\classes\debug文件夹生成经过AspectJ编译器编译后的.class文件,我们看下上面例子1中的方法skip(View v)方法,编译成class文件的方法体变成了如下这样:

    @LoginFilterpublic void onClick(View view) {JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, view);skip_aroundBody1$advice(this, view, var3, LoginFilterAspect.aspectOf(), (ProceedingJoinPoint)var3);}
复制代码

可以看到我们的点击事件方法已经被植入了一些代码,而原来startActivity(new Intent(this, SecondActivity.class));也不见了,实际上这里是把我们方法的执行给封装了,这里会在运行期,目标类加载后,为接口动态生成代理类,将切面织入到代理类中,从而实现对方法进行统一的处理。注:这里面有个小插曲,就是我在演示的时候

另外,评论中有同学提出单点登录机制处理麻烦,于是我在LoginSDK中加入后台token验证失效统一接入入口,我贴出用法:

LoginSDK.getInstance().serverTokenInvalidation(TOKEN_INVALIDATION);
复制代码

想要详细了解的同学可以参考demo。

小结

到这里,是不是觉得通过切面处理登录很简单,实际上我们只要熟悉了切面编程的API,便可以利用这么简单的方法对一批拥有某项特征的东西做特定处理。本项目的demo我放在了github,如果对本篇文章感兴趣的同学可以clone下来自己熟悉之后,运用到项目中。demo地址,欢迎star,我的github还有许多有意思的库,欢迎参观哦

联系方式: xiasem@163.com

AOP实现Android集中式登录架构相关推荐

  1. AOP思想实现集中式登录,用户行为统计框架

    最近学习到了AOP这种架构思想,感觉很巧妙很实用,能应用到很多开发场景,在此就以常见的登录及用户行为统计功能来实践一下. 对于AOP的概念大概是这样的(百度百科): AOP为Aspect Orient ...

  2. 你真的了解传统IT的集中式三层架构及主流开发技术选型?!

    [引言] 最近在看一本书<未来架构 从服务化到云原生>,关于技术架构这部分,结合书中内容,有一些认知和感悟. 互联网的技术架构正在经历由集中式->分布式->云平台的发展历程中. ...

  3. 电源架构--集中式电源架构/分布式电源架构

    这里写目录标题 概述 隔离式电源 集中式电源架构 分布式电源架构: 个人理解 概述 · 集中式电源架构(CPA):效率高,但成本高,PCB占用面积大. · 分布式电源架构(DPA):节省成本和PCB占 ...

  4. postgresql 分区视图_PostgreSQL架构集中式到分布式主流架构总结

    文章目录 一.PG未来主流架构为什么是分布式二.PostgreSQL集中式到分布式架构总结 一.PG未来主流架构为什么是分布式 如果说5年前DB的分布式还只是一种趋势,如今分布式数据库正逐渐从趋势变成 ...

  5. 某大型保险集团在线财险业务系统数据库存储架构由集中式向分布式转型实践

     [摘要]随着某机构业务自传统B2B类型向互联网的转变,访问量的激增.用户量持续爆炸式增长.数据量爆炸式增长,业务场景具备高吞吐量.高并发量等等新需求,这些都要求后台数据库具备支持高TPS.高QPS( ...

  6. 集中式架构vs分布式架构

    个人博客原文链接 历史 自从20世纪60年代大型主机被发明出来以后,凭借其超强的计算和I/O处理能力以及在稳定性和安全性方面的卓越表现,在很长一段时间内,大型主机引领了计算机行业以及商业计算领域的发展 ...

  7. 国内首家车身区域控制器量产 电子电气架构进入中央集中式3.0阶段

    从功能独立的分布式架构,到功能集成的域控制架构,如今整车电子电气架构正在加速跨入集中式电子电气架构3.0阶段. 在中央集中式架构中,算力逐渐向中央集中,多个域控制器继续融合最终形成1个中央计算平台+N ...

  8. 组织应该采用集中式发电机吗?

    如今,很多组织需要在集中式中压备用发电机组和分散式发电机之间进行选择. 由于备用发电机占了数据中心基础设施成本的很大一部分,尤其是对于托管数据中心而言,这是需要优化的关键子系统.如今,在大型超大规模数 ...

  9. 架构选型必读:集中式与分布式全方位优劣对比

    应用现状比较 由于历史原因,集中式架构多用于传统银行.电信等行业.主机资源集中在大型主机或小型机上.集中式架构下,包括操作系统.中间件.数据库等"基础软件" 均为闭源商用系统.集中 ...

最新文章

  1. mingw32-gcc.exe: error: CreateProcess: No such file or directory
  2. 说说大型高并发高负载网站的系统架构(更新)
  3. 手把手教你写篇出彩的分析报告(万字长文):以叮咚买菜为例,看生鲜电商的春天是否已经到来?
  4. python训练营微信公众号真实性_用python进行微信公众号开发(仅测试学习)
  5. 新内容、新交互、新增长:视频云为短视频及电商直播行业高效赋能
  6. 链接服务器 慢_redis服务器cpu100%的原因和解决方案
  7. javascript学习系列(18):数组中的include方法
  8. AT1984 Wide Swap
  9. c++ stl队列初始化_创建一个向量,并将其像C ++ STL中的数组一样初始化
  10. volatile是Java提供的一种轻量级的同步机制
  11. Navicat的使用,连表查询,python代码操作sql语句
  12. 详解Visual Studio 2010中ASP.NET新增23项功能 转
  13. Cisco NTP配置
  14. ArcGis学习资料汇总整理
  15. 机器学习笔记(12)— K均值算法
  16. mov格式的视频转换mp4?视频格式转换这样做
  17. Dubbo 入门教程与实战(一)上
  18. 【新闻早报简报】早上微信里发的那些新闻早报哪里来的
  19. Spring Boot/Cloud 界面与安全设计
  20. 今日头条起诉今日油条!后者还注册了“饼多多”和“快手抓饼”

热门文章

  1. 高级软件工程课程第二次作业
  2. 使用Hive UDF和GeoIP库为Hive加入IP识别功能
  3. Soap UI 负载测试
  4. HDU 1301 Jungle Roads(裸最小生成树)
  5. 苹果iPad视觉设计分析
  6. SQLHELPER C#
  7. 网卡故障:弹出界面eth0: 错误:没有找到合适的设备:没有找到可用于链接System eth0 的
  8. 如何判断两物体加速度相等_高中物理:速度和加速度知识点
  9. 操作系统原理第六章:进程同步
  10. 【Android 逆向】x86 CPU 架构体系 ( 堆内存 | 栈内存 | 函数调用 )