一、常见App中有两大类,一类是需要通过登录才能进入的App,另一类是不用登录,但是使用相关功能过程中需要登录后才能操作。那么第二类我们常见的做法就是,每次点击按钮的时候去用逻辑判断来实现,这样大大的增加了工作量。那么这篇文章将要改变你之前的做法,只需要一个注解,就可以解决。
二、这里使用的是AOP,面向切面编程。知道Java开发的都知道,Spring有两大特性,一个是Ioc,另一个则是Aop。关于Aop相关术语简介如下:
通知: 通知定义了切面是什么以及何时使用的概念。Spring 切面可以应用5种类型的通知:
前置通知(Before):在目标方法被调用之前调用通知功能。
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
返回通知(After-returning):在目标方法成功执行之后调用通知。
异常通知(After-throwing):在目标方法抛出异常后调用通知。
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
连接点:是在应用执行过程中能够插入切面的一个点。
切点: 切点定义了切面在何处要织入的一个或者多个连接点。
切面:是通知和切点的结合。通知和切点共同定义了切面的全部内容。
引入:引入允许我们向现有类添加新方法或属性。
织入:是把切面应用到目标对象,并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期中有多个点可以进行织入:
编译期: 在目标类编译时,切面被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标加载到JVM时被织入。这种方式需要特殊的类加载器(class loader)它可以在目标类被引入应用之前增强该目标类的字节码。
运行期: 切面在应用运行到某个时刻时被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面的。
三、这里我们使用AspectJ这个框架来实现。废话不多说,上代码:
1、首先在AS中如果AspectJ的jar包(见附件),然后在app目录下的build.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
//获取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;
}
}
}
}
2、创建注解LoginFilter:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginFilter {
int loginDefine() default 0;
}
这里对注解做一些了解:
a.注解的定义:Java文件叫做Annotation,用@interface表示。
b.元注解:@interface上面按需要注解上一些东西,包括@Retention、@Target、@Document、@Inherited四种。
c.注解的保留策略:
@Retention(RetentionPolicy.SOURCE) // 注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
d.注解的作用目标:
@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) // 包
e.注解包含在javadoc中:
@Documented
f.注解可以被继承:
@Inherited
g.注解解析器:用来解析自定义注解
3、写登录相关回调接口以及工具辅助类:写一个接口,方法:登录,已登录,清除登录状态等,这个可以根据自己的需求去定义:
public interface ILoginFilter {
void login(Context applicationContext, int loginDefine);
boolean isLogin(Context applicationContext);
void clearLoginStatus(Context applicationContext);
}
然后写一个辅助工具类,获取接口,上下文等,使用单例模式:
public class LoginAssistant {
private LoginAssistant(){}
private static LoginAssistant instance;
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;
}
}
及初始化管理类:
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);
}
}
在就是写切面拦截:
@Aspect
public class LoginFilterAspect {
private static final String TAG = "LoginFilterAspect";
@Pointcut("execution(@com.szsftxx.loginfilter.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 mContext = LoginAssistant.getInstance().getApplicationContext();
//调用iLogin的isLogin()方法判断是否登录,这个isLogin是留给使用者自己实现的,如果登录,就会继续执行方法体调用方法直到完成,如果没有登录,执行下一个
if (iLoginFilter.isLogin(mContext)){
joinPoint.proceed();
}else {
iLoginFilter.login(mContext,loginFilter.loginDefine());
}
}
}
对于这个切面拦截的相关内容,要了解的太多太多了,可以看看这个AOP之AspectJ语法详解 ,我也不是很懂,不敢乱bb。
最后就是初始化,初始化我们在Application中进行初始化:
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(SharePreferenceUtil.IS_LOGIN, applicationContext);
}
@Override
public void clearLoginStatus(Context applicationContext) {
SharePreferenceUtil.setBooleanSp(SharePreferenceUtil.IS_LOGIN, false, applicationContext);
}
};
}
四、使用方法,在你需要做登录拦截的方法上加上一下注解:
@LoginFilter(loginDefine = 0)
@Override
public void onClick(View v) {
startActivity(new Intent(this, SecondActivity.class));
}
点击按钮,如果已经登录,则这些这个目标方法体,如果没有登录,则会拦截,拦截时回调iLoginFilter接口,根据你写的注解中的值“loginDefine”进行相关判断处理你的逻辑,loginDefine 可以自行赋值,根据自己的需求。
五、我的demo附件: demo下载
最后,要感谢这个大神的付出,我也是从这为大神的文章中学的,做了一些精简 谢谢!
**六、**GIF演示: