Android使用AOP做登录拦截

常见App中有两大类,一类是需要通过登录才能进入的App,另一类是不用登录,但是使用相关功能过程中需要登录后才能操作。那么第一类我们常见的做法就是,每次点击按钮的时候去用逻辑判断来实现,大大增加了工作量,使用AOP只需要一个注解即可解决。

AOP面向切面编程

Spring有两大特性,一个是Ioc,另一个则是Aop。关于Aop相关术语简介如下:

  • 通知:通知定义了切面是什么以及何时使用的概念。Spring切面可以应用5种类型的通知:

(1)前置通知(Before):在目标方法被调用之前调用通知功能
(2)后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
(3)返回通知(After-returning):在目标方法成功执行之后调用通知
(4)异常通知(After-throwing):在目标方法抛出异常后调用通知
(5)环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

  • 连接点:是在应用执行过程中能够插入切面的一个点
  • 切点: 切点定义了切面在何处要织入的一个或者多个连接点
  • 切面:是通知和切点的结合。通知和切点共同定义了切面的全部内容
  • 引入:引入允许我们向现有类添加新方法或属性
  • 织入:是把切面应用到目标对象,并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期中有多个点可以进行织入:

(1)编译期:在目标类编译时,切面被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的
(2)类加载期:切面在目标加载到JVM时被织入。这种方式需要特殊的类加载器(class loader)它可以在目标类被引入应用之前增强该目标类的字节码
(3)运行期:切面在应用运行到某个时刻时被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面的

AspectJ框架实现

gradle配置

在app目录下的build.gradle中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
apply plugin: 'com.android.application'

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

android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "me.yezhou.authfilter"
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'
}
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}

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.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}


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
Task javaCompile
if (variant.hasProperty('javaCompileProvider')) {
// Android 3.3.0+
javaCompile = variant.javaCompileProvider.get()
} else {
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
}
}
}
}

创建注解LoginFilter

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginFilter {
int loginDefine() default 0;
}

注解知识点

(1)注解的定义:Java文件叫做Annotation,用@interface表示

(2)元注解:@interface上面按需要注解一些东西,包括@Retention@Target@Document@Inherited四种

(3)注解的保留策略:

1
2
3
@Retention(RetentionPolicy.SOURCE) // 注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

(4)注解的作用目标:

1
2
3
4
5
6
7
8
@Target(ElementType.TYPE) // 接口、类、枚举、注解
@Target(ElementType.FIELD) // 字段、枚举的常量
@Target(ElementType.METHOD) // 方法
@Target(ElementType.PARAMETER) // 方法参数
@Target(ElementType.CONSTRUCTOR) // 构造函数
@Target(ElementType.LOCAL_VARIABLE) // 局部变量
@Target(ElementType.ANNOTATION_TYPE) // 注解
@Target(ElementType.PACKAGE) // 包

(5)注解包含在javadoc中:@Documented

(6)注解可以被继承:@Inherited

(7)注解解析器:用来解析自定义注解

登录相关回调接口以及工具辅助类

登录相关回调接口,方法:登录、已登录、清除登录状态等,可以根据自己的需求定义:

1
2
3
4
5
public interface ILoginFilter {
void login(Context applicationContext, int loginDefine);
boolean isLogin(Context applicationContext);
void clearLoginStatus(Context applicationContext);
}

辅助工具类,获取接口,上下文等,使用单例模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class LoginAssistant {
private static LoginAssistant instance;

private LoginAssistant() {
}

public static LoginAssistant getInstance() {
if (instance == null) {
synchronized (LoginAssistant.class) {
if (null == instance) {
instance = new LoginAssistant();
}
}
}
return instance;
}

private ILoginFilter iLoginFilter;

public ILoginFilter getILoginFilter() {
return iLoginFilter;
}

public void setILoginFilter(ILoginFilter iLoginFilter) {
this.iLoginFilter = iLoginFilter;
}

private Context applicationContext;

public Context getApplicationContext() {
return applicationContext;
}

public void setApplicationContext(Context applicationContext) {
this.applicationContext = applicationContext;
}
}

及初始化管理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class LoginManger {
private static LoginManger instance;

private LoginManger() {
}

public static LoginManger getInstance() {
if (null == instance) {
synchronized (LoginManger.class) {
if (null == instance) {
instance = new LoginManger();
}
}
}
return instance;
}

public void init(Context context, ILoginFilter iLoginFilter) {
LoginAssistant.getInstance().setApplicationContext(context);
LoginAssistant.getInstance().setILoginFilter(iLoginFilter);
}
}

重点切面拦截:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Aspect
public class LoginFilterAspect {
private static final String TAG = "LoginFilterAspect";

@Pointcut("execution(@me.yezhou.authfilter.annotation.LoginFilter * * (..))")
public void LoginFilter() {
}

@Around("LoginFilter()")
public void aroundLoginPoint(ProceedingJoinPoint joinPoint) throws Throwable {
//获取用户实现的ILogin类,如果没有调用init()设置初始化就抛出异常
ILoginFilter iLoginFilter = LoginAssistant.getInstance().getILoginFilter();
if (iLoginFilter == null) {
throw new RuntimeException("LoginManger没有初始化");
}
//先得到方法的签名methodSignature,然后得到@LoginFilter注解,如果注解为空,就不再往下走
Signature signature = joinPoint.getSignature();
if (!(signature instanceof MemberSignature)) {
throw new RuntimeException("该注解只能用于方法上");
}
MethodSignature methodSignature = (MethodSignature) signature;
LoginFilter loginFilter = methodSignature.getMethod().getAnnotation(LoginFilter.class);
if (loginFilter == null) {
return;
}
Context context = LoginAssistant.getInstance().getApplicationContext();
//调用iLogin的isLogin()方法判断是否登录,这个isLogin是留给使用者自己实现的,如果登录,就会继续执行方法体调用方法直到完成,如果没有登录,调用iLogin的login方法
if (iLoginFilter.isLogin(context)) {
joinPoint.proceed();
} else {
iLoginFilter.login(context, loginFilter.loginDefine());
}
}
}

在Application中进行初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LoginManger.getInstance().init(this, iLoginFilter);
}

ILoginFilter iLoginFilter = new ILoginFilter() {
@Override
public void login(Context applicationContext, int loginDefine) {
switch (loginDefine) {
case 0:
Intent intent = new Intent(applicationContext, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
break;
case 1:
Toast.makeText(applicationContext, "您还没有登录,请登陆后执行!", Toast.LENGTH_SHORT).show();
break;
case 2:
Toast.makeText(applicationContext, "执行失败,因为您还没有登录!", Toast.LENGTH_SHORT).show();
break;
}
}

@Override
public boolean isLogin(Context applicationContext) {
return SharePreferenceUtil.getBooleanSp(applicationContext, SharePreferenceUtil.IS_LOGIN);
}

@Override
public void clearLoginStatus(Context applicationContext) {
SharePreferenceUtil.setBooleanSp(applicationContext, SharePreferenceUtil.IS_LOGIN, false);
}
};
}

使用方法

在需要做登录拦截的方法上加上以上注解:

1
2
3
4
5
@LoginFilter(loginDefine = 0)
@Override
public void onClick(View v) {
startActivity(new Intent(this, SecondActivity.class));
}

点击按钮,如果已经登录,则执行这个目标方法体,如果没有登录,则会拦截,拦截时回调ILoginFilter接口,根据注解中的值loginDefine进行相关判断处理逻辑,loginDefine可以自行赋值,根据需求而定。

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :