文章目录
一 、开始之前:
集中式登陆架构设计,此登陆并非登陆请求,而是判断当前是否登陆状态,如不是登陆状态,跳转登陆界面;否则进行对应的跳转逻辑 。该业务是一个全局业务,全局业务抽取到一个切面。
如果是OOP思想编程的话,采用SharedPreferenced
来保存isLogined
登陆状态,如果isLogined == true
跳转到我的积分、我的专区等等。否则跳转到LoginActivity
。
二 、实现方式一 :运行时动态代理的方式实现
采用运行时动态代理的方式实现,具体查看代码CentralizedLoginArchitecture
重点介绍方式二:
三、实现方式二:AspectJ
1. 举例,有如下需求:
类似需求如:用户的行为统计分析,也是一个全局业务。也是可以通过AOP切面的思想来完成。
言归正传,开始AspectJ,AspectJ
是面向切面编程的一个框架,拓展了Java的语言,并且定义实现AOP的语法。
2. 名词解释:
切入点,PointCut
通知,Advice
连接点,Joint Point, Before After Around
3. 集成AspectJ
集成AspectJ可以通过插件的方式集成,也可以通过gradle直接集成。
1. 插件集成
- 新建主module
app
- 新建android library
aspectj
2. gradle集成
- 项目根build.gradle添加依赖:
值得注意的是,如果在同步项目过程中报错:// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } google() jcenter() // mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:3.5.0' // 版本界限: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' } } allprojects { repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } google() jcenter() // mavenCentral() } } task clean(type: Delete) { delete rootProject.buildDir }
Received close_notify during handshake
,可以尝试如上,采用阿里云的仓库maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
- 修改app的moudle中build.gradle:
编译时用Aspect专门的编译器,不再使用传统的javac
// 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境) // 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告) buildscript { // 编译时用Aspect专门的编译器,不再使用传统的javac repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } // mavenCentral() } dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' } }
- 整体如下:
apply plugin: 'com.android.application' // 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境) // 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告) buildscript { // 编译时用Aspect专门的编译器,不再使用传统的javac repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } // mavenCentral() } dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' } } android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { applicationId "com.example.aspectj" minSdkVersion 15 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' // 引入aspectj的包 implementation 'org.aspectj:aspectjrt:1.8.13' } /** * 以下groovy代码不需要太关心,只是作为一个支持 */ // 版本界限: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.Main final def log = project.logger final def variants = project.android.applicationVariants variants.all { variant -> if (!variant.buildType.isDebuggable()) { log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.") return; } 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); 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; } } } }
4. 代码实操
需求:点击登录;查看我的专区、我的优惠券、我的积分(查看之前先检查是否登录状态,如是则跳转相应的功能区,否则跳转登录页面重新登录);此外都需要做行为统计分析。
问题:如何实现用户行为统计分析?
答:友盟等第三方统计。但如果要求自己实际实现 ,那只能通过AOP的思想来实现。
统计用户点击某功能行为。(实际项目中,存储到本地,每隔N天上传到服务器)。
1. 用户行为统计
-
自定义
行为
注解//用户点击痕迹(行为统计) 自定义注解参看:公开课 IOC容器 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ClickBehavior { String value(); }
-
将
行为
注解作用在需要进行行为统计的
代码上。// 用户行为统计 @ClickBehavior(value = "登录") public void login(View view) { Log.e(TAG, "login: 验证通过,登录成功"); }
-
定义
AspectJ
书写该类时需要注意,不能写错一个符号,否则不容易排查。
AspectJ
是由PointCut(切入点)
和Advice(通知)
构成的。 而Advice
有:Before After Around
三种情况- 使用注解
@Aspect
来定义一个切面类。@Aspect public class ClickBehaviorAspect { }
- 通过
@Pointcut
寻找需要处理的切入点
关于通配符// 1. 应用中使用到了哪些注解,放到当前的切入点进行处理(找到需要处理的切入点) // execution 以方法执行时作为切点,触发Aspect类。 语法:execution() // * *(..) 通配符,可以处理ClickBehavior这个类所有的方法,也可以指定com.example.aspectj.MainActivity.login()方法作为切入点 @Pointcut("execution(@com.example.aspectj.annotation.ClickBehavior * *(..))") // public void methodPointCut() { }
* *(..)
可以查看execution(* com.xxx..(…)) - 处理切入点
// 2. 对切入点如何处理, // Around 围绕切入点如何处理 @Around("methodPointCut()") public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable { // 遵循三个规则,1.返回值Object 2.参数ProceedingJoinPoint 3. 抛出异常 Throwable }
- 使用注解
2. 集中式登录
- 同样定义
LoginCheck
注解// 检查是否登录 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LoginCheck { }
- 使用
LoginCheck
注解//登录成功之后 用户行为统计 @ClickBehavior(value = "我的积分") @LoginCheck() public void score(View view) { Log.e(TAG, "score: 跳转我的积分"); startActivity(new Intent(MainActivity.this, OtherActivity.class)); }
- 定义
Aspect
并处理切入点@Aspect public class LoginCheckAspect { private static final String TAG = LoginCheckAspect.class.getSimpleName(); @Pointcut("execution(@com.example.aspectj.annotation.LoginCheck * *(..))") public void methodLoginCheck() { } @Around("methodLoginCheck()") public Object check(ProceedingJoinPoint joinPoint) throws Throwable { Context context = (Context) joinPoint.getThis(); boolean isLogin = false; //该处进行登录模拟, 实际项目中,从SharedPreference 从读取 if (isLogin) { Log.e(TAG, "检测到已登录"); return joinPoint.proceed();//检测到登录,继续执行后续代码 } else { Log.e(TAG, "检测到未登录"); context.startActivity(new Intent(context, LoginActivity.class)); return null; //未登录情况下,后续代码都不需要执行了。 } } }
详见代码 AspectJ
四、AspectJ使用过程中碰到的坑
- 临界版本as3.0.1和gradle4.4-all需要配置ndk的r17环境
- as3.2.1和gradle4.6-all 正常使用,无过时警告
- as3.4.0和gradle5.1.1-all有过时的api警告