Android入门第66天-使用AOP
Posted TGITCIC
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android入门第66天-使用AOP相关的知识,希望对你有一定的参考价值。
开篇
这篇恐怕又是一篇补足网上超9成关于这个领域实际都是错的、用不起来的一个知识点了。网上太多太多教程和案例用的是一个叫hujiang的AOP组件-com.hujiang.aspectjx:gradle-android-plugin-aspectjx。
首先这些错的文章我不知道是怎么来的,其次那些案例真的运行成功过吗?它们用的都是aspectjx功能,是为了兼容Kotlin。
还是那句话,Java开发和Kotlin还有Flutter开发到底有什么区别?整一堆无用功、把框架搞得很复杂、一堆错误的无用的代码混在一起,这才是本质。开发了好用什么语言都可以开发出好东西来,开发了不好就只剩下了:我用的开发语言是最先进的。因此我们坚持使用Java。
扯回来,我们继续来看AOP功能在Android中的使用,由于Android是Java语言,因此它也支持aspectj功能,用法和在spring系里使用一模一样的简单。只不过都是被了这个叫:com.hujiang.aspectjx:gradle-android-plugin-aspectjx搞了太复杂了,甚至有人几天都调不通一个AOP功能。
因此本篇就是交给大家正确的、纯净的、纯真的AOP使用方法。
AOP的使用场景
AOP使用场景太多了,如:拦截方法打印日志,特别是:APP里有很多activity,它要求用户必须登录才可以打开,如:用户积分、我的历史订单等。对于这些界面,我们要求如果用户不登录那么APP自动跳到登录页。
这种操作就是用的AOP功能去实现的。
如何使用一个AOP来判断用户打开界面前是否已经登录
一个activity从进入到展示界面在onCreate方法里一般会经历这么几个方法,这里的initMain()就是用来展示界面的。
我们为了做这个界面在展示前判断用户是否已经登录,我们会这么“切”一刀。
然后把这个判断用户是否已经登录做成一个和后端交互的API,整个是否登录的判断逻辑如下截图:
动手使用AOP实现
我们假设后台的这个接口如下所示(这边用了上一篇讲到的retrofit2+okhttp3+rxjava)。
package com.mkyuan.aset.mall.android.login.api;
import com.mkyuan.aset.mall.android.login.model.LoginResponseBean;
import io.reactivex.Observable;
import okhttp3.RequestBody;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.POST;
public interface LoginAPI
@POST("/api/account/checkLoginUT")
Observable <LoginResponseBean> checkLoginUT(@Header("ut") String ut);
所以我们现在开始动手制作我们的AOP了。
先引入AOP包
我们这边不会使用网上的已经不维护的、一堆问题的“com.hujiang.aspectjx:gradle-android-plugin-aspectjx”。
请直接使用“org.aspectj:aspectjrt:1.9.6”。为此
第一步:编程全局build.gradle
加入以下语句:
dependencies
classpath 'org.aspectj:aspectjtools:1.9.6'
第二步:编辑我们的模块级别的build.gradle文件
先引入aspectjrt包
implementation 'org.aspectj:aspectjrt:1.9.6' //引入 aspectj
然后再要在同一个build.gradle中加入如下语句
//使得aop生效
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;
加完后的build.gradle长这个样
/**
* following plugins is the original version
*/
/*
plugins
id 'com.android.application'
*/
/**
* by using aspectj must ad following lines tart
*/
apply plugin: 'com.android.application'
//apply plugin: 'android-aspectjx' //暂时注了
/**
* by using aspectj must ad following lines tart
*/
android
namespace 'com.mkyuan.aset.mall.android'
compileSdk 32
defaultConfig
applicationId "com.mkyuan.aset.mall.android"
minSdk 27
targetSdk 32
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 JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
dataBinding
enabled = true
//aspectjx
// exclude "**/module-info.class"
// exclude "META-INF.versions.9.module-info"
// exclude "META-INF/versions/9/*.class"
// exclude 'com.google', 'com.squareup', 'org.apache','com.taobao','com.ut'
// exclude 'androidx', 'com.squareup', 'com.alipay', 'org.apache', 'org.jetbrains.kotlin',
// "module-info", 'versions.9'
//
dependencies
implementation 'org.aspectj:aspectjrt:1.9.6' //引入 aspectj
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
implementation 'org.aspectj:aspectjrt:1.9.6'
implementation 'io.github.youth5201314:banner:2.2.2'
//com.google.android.material.theme
//implementation 'com.google.android.material:material:<version>'
//retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//日志拦截器
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
//rxjava
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.12'
//gson
implementation 'com.google.code.gson:gson:2.8.7'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
//使得aop生效
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;
制作AOP相关的代码
Login.java-定义一个基于方法切面的annotation
package com.mkyuan.aset.mall.android.util.aop.login;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//不需要回调的处理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login
LoginAspect.java -使用Around模式对含有@Login的方法进行切面
package com.mkyuan.aset.mall.android.util.aop.login;
import android.util.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
@Aspect
public class LoginAspect
private static final String TAG = "LoginAspect";
@Pointcut("execution(@com.mkyuan.aset.mall.android.util.aop.login.Login * *(..))")
public void executionCheckLogin()
//不带回调的注解处理
@Around("executionCheckLogin()")
public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable
Log.i(TAG, ">>>>>>走进AOP方法");
Signature signature = joinPoint.getSignature();
if (!(signature instanceof MethodSignature))
throw new RuntimeException("该注解只能用于方法上");
Login login = ((MethodSignature) signature).getMethod().getAnnotation(Login.class);
if (login == null)
return;
//判断当前是否已经登录
LoginManager.isLogin(new LoginCheckCallBack()
@Override
public void changeValue(boolean loginResult)
if(loginResult)
Log.i(TAG, ">>>>>>用户己登录走入下一步");
try
joinPoint.proceed();
catch (Throwable e)
Log.e(TAG,e.getMessage(),e);
else
//如果未登录,去登录页面
Log.i(TAG, ">>>>>>用户未登录去登录页面");
LoginManager.gotoLoginPage();
);
LoginManager.java
内含有和后台是否登录接口交互以及判断用户如果已经登录那么继续“走下去-打开界面”,否则跳到Login登录界面的逻辑
package com.mkyuan.aset.mall.android.util.aop.login;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
import com.google.gson.Gson;
import com.mkyuan.aset.mall.android.home.MainActivity;
import com.mkyuan.aset.mall.android.login.LoginActivity;
import com.mkyuan.aset.mall.android.login.SmsLoginActivity;
import com.mkyuan.aset.mall.android.login.api.LoginAPI;
import com.mkyuan.aset.mall.android.login.model.LoginResponseBean;
import com.mkyuan.aset.mall.android.network.BaseObserver;
import com.mkyuan.aset.mall.android.network.NetworkApi;
import com.mkyuan.aset.mall.android.util.ContextUtils;
import com.mkyuan.aset.mall.android.util.SharedPreferenceHelper;
import com.mkyuan.aset.mall.android.util.activity.ActivityCollector;
import java.util.Map;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.RequestBody;
public class LoginManager
private static final String TAG = "LoginAspect";
public static void isLogin(LoginCheckCallBack loginCheckCallBack)
Context ctx = null;
try
ctx = ContextUtils.getCurApplicationContext();
SharedPreferenceHelper spHelper = new SharedPreferenceHelper(ctx);
Map<String, String> data = spHelper.read();
if (data.get("ut") != null && data.get("ut").trim().length() > 0)
String utValue = data.get("ut");
//开始调用okhttp3, retrofit, rxjava框架
LoginAPI loginAPI = NetworkApi.createService(LoginAPI.class);
//loginAPI.checkLoginUT().
loginAPI.checkLoginUT(utValue).compose(NetworkApi.applySchedulers(new BaseObserver<LoginResponseBean>()
@Override
public void onSuccess(LoginResponseBean loginResponseBean)
Log.i(TAG, ">>>>>>" + new Gson().toJson(loginResponseBean));
int returnCode = loginResponseBean.getCode();
String returnMsg = loginResponseBean.getMessage();
if (returnCode == 0)
//result = true;
loginCheckCallBack.changeValue(true);
Log.i(TAG,
">>>>>>get verifiedCode->" + loginResponseBean.getData());
//startActivity(new Intent(SmsLoginActivity.this, MainActivity
// .class));
else
loginCheckCallBack.changeValue(false);
@Override
public void onFailure(Throwable e)
Log.e(TAG, ">>>>>>Network Error: " + e.toString(), e);
loginCheckCallBack.changeValue(false);
));
else
loginCheckCallBack.changeValue(false);
catch (Exception e)
Log.e(TAG, ">>>>>>isLogin error: " + e.getMessage());
loginCheckCallBack.changeValue(false);
public static void gotoLoginPage()
Context ctx = null;
try
ctx = ContextUtils.getCurApplicationContext();
Intent intent = new Intent();
intent.setClass(ctx, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(intent);
ActivityCollector.getInstance().quitCurrentProcess("com.mkyuan.aset.mall.android.home.MainActivity");
catch (Exception e)
Log.e(TAG, ">>>>>>gotoLoginPage error: " + e.getMessage(), e);
使用这个AOP
在MainActivity代码里有一个initMain方法,渲染界面的逻辑全部在这个initMain方法里。
接着我们来看这个initMain()方法。在方法前我们加入了自定义的“切入点”。
效果演示
第一次打开APP,用户未登录,因此直接被跳到了Login界面
然后输入手机,点获取验证码
按提交,登录成功。
然后关闭APP,再次打开APP
由于之前用户已经登录过了,因此AOP直接会把用户带入到主页
结合着我上一篇:retrofit2+okhttp3+rxjava自己不妨动动手试试看吧
以上是关于Android入门第66天-使用AOP的主要内容,如果未能解决你的问题,请参考以下文章