最近学习到了AOP这种架构思想,感觉很巧妙很实用,能应用到很多开发场景,在此就以常见的登录及用户行为统计功能来实践一下。

对于AOP的概念大概是这样的(百度百科):

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

以登录为例,在我们的App中判断登录状态有这极高的出场率,我猜大家一般都是这样写的:

if(Helper.isLogin()){....
}else {//去登录....
}

只是把他抽成了一个公用的方法,然后就是铺天盖地的调用,估计光个人中心就要写个十几二十次,类似的代码写多了,我们的工程就成了牛皮癣重症患者。接下来我们就看看AOP思想是怎么低耦高效的来做这样的事儿的呢。

首先我们来搭建一下环境,我们用到了Aspectj这个框架,基本用法可参考这篇博客

项目级build

buildscript { //编译时用Aspect专门的编译器,不在使用传统的javacrepositories {google()jcenter()}dependencies {classpath 'com.android.tools.build:gradle:3.2.1'//版本界限:AS-3.0.1 + gradle4.4-all(需要配置r17的NDK环境)//或者:As-3.2.1 + gradle4.6-all(正常使用,无警告)classpath 'org.aspectj:aspectjtools:1.8.9'classpath 'org.aspectj:aspectjweaver:1.8.9'}
}

module级build

apply plugin: 'com.android.application'
//版本界限:AS-3.0.1 + gradle4.4-all(需要配置r17的NDK环境)
//或者:As-3.2.1 + gradle4.6-all(正常使用,无警告)
android {compileSdkVersion 28defaultConfig {applicationId "com.example.aopdemo"minSdkVersion 21targetSdkVersion 28versionCode 1versionName "1.0"testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}
}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])implementation 'com.android.support:appcompat-v7:28.0.0'implementation 'com.android.support.constraint:constraint-layout:1.1.3'implementation 'org.aspectj:aspectjrt:1.8.13'
}// 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
// 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
//固定配置
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Mainfinal def log = project.logger
final def variants = project.android.applicationVariantsvariants.all { variant ->if (!variant.buildType.isDebuggable()) {log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")return;}JavaCompile 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);for (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;}}}
}

比较值得注意的有两点

1.androidstudio和gradle的版本问题

AS-3.0.1 + gradle4.4-all(需要配置r17的NDK环境)
AS-3.2.1 + gradle4.6-all  (正常使用,无警告)

AS-3.4.0 + gradle5.1.1-all(会有警告信息)

2. 编译时用Aspect专门的编译器,不在使用传统的javac(无法在编译期插入代码),这个也不用我们关心是Aspect做好的,它无缝支持java。

环境准备好开撸

新建了3个Activity,

MainActivity,包含了4个按钮(登录,评论,点赞,转发)及click方法

public class MainActivity extends AppCompatActivity {private final String TAG = "AOPDEMO";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}@ClickBehavior("登录")public void login(View view) {Log.e(TAG,"模拟登录请求...验证通过,登陆成功");}//用户行为统计@ClickBehavior("评论")@LoginCheckpublic void remark(View view) {Log.e(TAG,"评论 --> 评论、点赞、转发界面");startActivity(new Intent(this,ContentActivity.class));}//用户行为统计@ClickBehavior("点赞")@LoginCheckpublic void zan(View view) {Log.e(TAG,"点赞 --> 评论、点赞、转发界面");startActivity(new Intent(this,ContentActivity.class));}//用户行为统计@ClickBehavior("转发")@LoginCheckpublic void transmit(View view) {Log.e(TAG,"转发 --> 评论、点赞、转发界面");startActivity(new Intent(this,ContentActivity.class));}
}

其中ClickBehavior和LoginCheck是自定义的注解,分别是用户行为统计(点击行为)和登录检查

ClickBehavior

//用户行为统计
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickBehavior {String value();
}

LoginCheck

//用户行为统计
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginCheck {}

大家可能有疑问了,就写了两个注解就能实现功能了?之前引入的Aspectj又有什么用呢?ok最关键的一步骚操作即将到达战场

ClickBehaviorAspect(类名可随意)

@Aspect //定义切片类
public class ClickBehaviorAspect {private final String TAG = "AOPDEMO";//1.应用中用到了哪些注解,放入当前的切入点进行处理(找到需要处理的切入点)// execution, 以方法执行时作为切点,触发Aspect类// @:应因为ClickBehavior是一个接口,如果参数是一个类则不加@// * *(..))":可以处理ClickBehavior这个类的所有方法,也可具体指定某个方法// method() : 方法名可随意@Pointcut("execution(@com.example.aopdemo.annotation.ClickBehavior * *(..))")public void method() {}//2.对这些切入点如何处理@Around("method()")public Object joinPoint(ProceedingJoinPoint joinPoint) throws Throwable{//获取签名方法MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();//获取方法所属类名String className = methodSignature.getDeclaringType().getSimpleName();//获取方法名String methodName = methodSignature.getName();//获取方法的注解值(需要统计的用户行为)String funName = methodSignature.getMethod().getAnnotation(ClickBehavior.class).value();//统计方法的执行时间,统计用户点击某功能的行为(真实项目:存储到本地,每过x天上传到服务器)long begin= System.currentTimeMillis();Log.e(TAG,"ClickBehavior Method start >>>");//MainActivity中切面的方法,如:remark(View view),即在运行remark函数的函数提前会先         //运行以上内容,在remark函数运行完后运行 Object result = joinPoint.proceed();
//之后的内容,就是通过这种方式在预编译时插入相同代码的Object result = joinPoint.proceed();long duration= System.currentTimeMillis() - begin;Log.e(TAG,"ClickBehavior Method end >>>");Log.e(TAG,String.format("统计了:%s功能,在%s类的%s方法,用时%s ms",funName,className,methodName,duration));return result;}
}
LoginCheckAspect
@Aspect //定义切片类
public class LoginCheckAspect {private final String TAG = "AOPDEMO";//1.应用中用到了哪些注解,放入当前的切入点进行处理(找到需要处理的切入点)// execution, 以方法执行时作为切点,触发Aspect类// @:应为ClickBehavior是一个接口// * *(..))":可以处理ClickBehavior这个类的所有方法// method() : 方法名可随意@Pointcut("execution(@com.example.aopdemo.annotation.LoginCheck * *(..))")public void method() {}//2.对这些切入点如何处理@Around("method()")public Object joinPoint(ProceedingJoinPoint joinPoint) throws Throwable {Context context = (Context) joinPoint.getThis();//在此改true或false来设定登录状态if (true) { //正式项目从sharedPreferences中读取Log.e(TAG, "检测到已登录");return joinPoint.proceed();} else {Log.e(TAG, "检测到未登录");Toast.makeText(context, "请先登录!", Toast.LENGTH_LONG).show();context.startActivity(new Intent(context, LoginActivity.class));return null; //方法不执行(切入点)}}
}

完整demo

点击按钮时,可对应查看logcat效果

AOP思想实现集中式登录,用户行为统计框架相关推荐

  1. AOP实现Android集中式登录架构

    未经同意禁止抄袭,如需转载请在显要位置标注 前言 登录应该是应用开发中一个很常见的功能,一般在应用中有两种登录,一种是一进入应用就必须登录才能使用(如微信和QQ等),另一种是需要登录的时候才会去登录( ...

  2. Vuex-全局状态集中式管理神器,做vue项目不知道Vuex真的out了

    目录 一.概念 1.什么是vuex? 2.状态管理到底是什么? 3.等等,如果是这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装个对象来管理吗? 4.管理什么状态呢? 二..单界 ...

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

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

  4. 服务器接收消息写日志,在Ubuntu 18.04上配置Rsyslog集中式日志服务器的方法

    本文介绍在Ubuntu 18.04操作系统上配置Rsyslog集中式日志服务器的方法. 前言 登录任何Linux系统对于分析和排除与系统和应用程序相关的任何问题至关重要,借助Graylog等工具(参考 ...

  5. 构建集中式会话的分析与实践(一)

    2019独角兽企业重金招聘Python工程师标准>>> 前言 我们都知道,在传统的单机环境中,设置一个用户的会话信息(session),一般用request.getSession() ...

  6. xshell 密钥身份验证_使用密钥斗篷和大使边缘堆栈进行集中式身份验证

    xshell 密钥身份验证 Keycloak is a widely adopted Identity and Access Management (IAM for short) open-sourc ...

  7. VMware vCenter Server 8.0U1 发布 - 集中式管理 vSphere 环境

    请访问原文链接:VMware vCenter Server 8.0U1 - 集中式管理 vSphere 环境,查看最新版.原创作品,转载请保留出处. 作者主页:sysin.org 2023-04-18 ...

  8. 5 个最值得注意的开源集中式日志管理工具

    集中式日志记录与安全性一样,是 IT 基础结构(包括 Web 应用程序和硬件设备)中核心资源监控和健全管理的一个基本方面.有能力的运维团队能够搭建一个日志监控和管理系统,来应对系统故障或应用程序的怪异 ...

  9. 分布式技术原理(九):分布式体系结构之集中式结构

    分布式体系结构之集中式结构 云这个话题对我们来说已经非常熟悉了.可以说,云在我们的生活中无处不在,比如我们平时看的视频通常就是放在云上的.当我们要播放一段视频时,请求会先转发到云上,从云上下载数据到本 ...

最新文章

  1. uitextfield 键盘类型_UITextField 键盘弹出问题
  2. 左神算法:复制含有随机指针节点的链表 / 复杂链表的复制(Java版本)
  3. 蓝桥练习-算法训练 Collecting Luggage
  4. python 正则表达式 re findall 返回能匹配的字符串
  5. Atom飞行手册翻译: 3.7 调试
  6. win10 linux安卓模拟器,genymotion安卓模拟器在Window10中使用的问题
  7. 解决:[ERROR] Error executing Maven. [ERROR] 1 problem was encountered while building the effective set
  8. linux apache 大文件,Apache下error.log文件太大的处理方法
  9. linux 窗口不能移动的替换命令
  10. fullcalendar自定义搜索框_高效搜索任意文件,拯救凌乱的电脑桌面!
  11. Shiro 权限注解
  12. php简单的设计模式,MVC,composer
  13. 两个pv挂一个vg_今日德杯:VG、TES零封对手会师四强;FOFO状态爆表台湾网友热议:Maple比Fofo混得差;管泽元:牛宝快跑...
  14. 28 Implement strStr() @Python
  15. x5内核有什么优点_关于接入腾讯X5内核的一些坑(不断更新)
  16. 智慧树源码_智慧树怎么查看网站源代码答案
  17. 虚拟机无法启动服务器失败,Hyper-V虚拟机无法启动故障图解
  18. 金仓数据库-java连接金仓数据库方法笔记
  19. bzoj 4816: 洛谷 P3704: [SDOI2017]数字表格
  20. 刘强东的“长期主义”:做正确的事,敢于追求极致

热门文章

  1. 小鱼的航程(两种解决方法)
  2. [转载]扩展Log4Net中的ILog实现自定义日志字段
  3. js实现京东商城导航
  4. 百度网盘网页版视频在线倍速播放
  5. xshell6使用技巧_Xshell 6怎么设置为中文-Xshell 6设置为中文的方法 - 河东软件园
  6. python之pyttsx3实现文字转语音播报
  7. 微信对接推送模板消息
  8. 超越MRT无数倍的MODIS在线拼接AppEERAS
  9. kubernetes用户使用token安全认证教程
  10. python苹果手机照片导入电脑_iphone照片怎么导入电脑?四种方法汇总