面向切面编程定义:面向切面编程(AOP是Aspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。
上面的解释讲完后,估计很多人是似懂非懂的样子,简单的来解释一下,我们在运行我们的App时候,比如请求登录点击事件,请求拍照的事件,Button的埋点点击事件,在一个App里面有很多这样的场景吧,在正常情况下,我们的点击事件都会有相应的跳转或者去干些什么?但是假如我们在手机没有网络的时候,权限还没有被授予的时候,我们通常的做法是不是要写一些比如判断有没有网的,有没有权限的这样的代码,比如下面这段代码:
/**
* 登录的点击事件
*
* @param v
*/
public void login(View v) {
//CheckNetUtil网络处理的工具类
if (CheckNetUtil.isNetworkAvailable(this)) {
//如果有网,继续操作 比如处理登录的逻辑
} else {
Toast.makeText(this, "请检查您的网络连接!", Toast.LENGTH_LONG).show();
}
}我们在点击事件的时候,要去判断有没有网络,就要写if(有没有网络){}else{}这样的代码,app中很多地方这样写,当然这样写实没有问题的。但是我们的app体量大了以后,需要做网络判断的地方多了以后,需要权限的地方多了以后,像 if(有没有网络){}else{}这样的代码就是属于一个体力活。而且不便于后期变动,我们上面的代码现在是没有网络的情况下是弹了一个Toast,但是假如我们将来变个花样,说是我们在没有网络的情况下需要弹出一个dialog,叫用户点击前往设置界面去设置网络呢?是不是又是个体力活,又得去吭哧吭哧的找到这些if else去改代码,那我们到底有么有比较好的方法呢?当然有,那就是我们今天在文章一开始的地方所谈到的面向切面编程。就上面的例子来说,我们该怎么办呢?利用面向切面编程的思想,我们把网络判断的逻辑单读切出来去处理,这样做的好处:1:保证了代码的简洁。
2:便于扩展,比如上面的我们说的不想要吐司了,要dialog。
利用切面编程之后的代码:
/**
* 登录的点击事件
*
* @param v
*/
@CheckNet
public void login(View v) {
//有网,继续操作 比如处理登录的逻辑
}下面我们来看具体的步骤:先去官网下载aspectj点击打开链接
下载完成之后直接双击安装,如果无法直接双击安装,请检查您的JDK环境,或者直接使用命令进行安装(命令后是自己下载的jar包的名称):
命令:java -jar aspectj-1.8.13
新建工程,到我们刚才安装的AspectJ目录下,去拷贝一个jar包,放置到我们的工程下面。
配置app下的gradle文件,AspectJ的使用核心就是它的编译器,它就做了一件事,将AspectJ的代码在编译期插入目标程序当中,运行时跟在其它地方没什么两样,因此要使用它最关键的就是使用它的编译器去编译代码ajc。ajc会构建目标程序与AspectJ代码的联系,在编译期将AspectJ代码插入被切出的PointCut中,已达到AOP的目的。
apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
repositories {
mavenCentral()
}
android {
compileSdkVersion 26
defaultConfig {
applicationId "mjoys.com.aopprograming"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
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;
}
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
implementation files('libs/aspectjrt.jar')
}
新建一个注解,用来标切点/**
* Created by zsd on 2018/2/5 10:53
* desc:该注解是用来编辑切点的
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckNet {
}新建处理切点的工具类:@Aspect
public class AspectUtils {
/**
* 找到处理的切点
* * *(..) 表示可以处理所有的方法
*/
@Pointcut("execution(@mjoys.com.aopprograming.CheckNet * *(..))")
public void getCheckNetAspect() {
}
/**
* 处理切面
*
* @return
*/
@Around("getCheckNetAspect()")
public Object checkNet(ProceedingJoinPoint joinPoint) throws Throwable {
// 1.获取 CheckNet 注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
CheckNet checkNet = signature.getMethod().getAnnotation(CheckNet.class);
if (checkNet != null) {
//2.获取到注解后,再去检查有没有网络
//joinPoint.getThis()是当前切点方法所在的类
Object object = joinPoint.getThis();
Context context = getContext(object);
if (context != null) {
if (!isNetworkAvailable(context)) {
//没有网不执行后面的任务
Toast.makeText(context, "请检查您的网络", Toast.LENGTH_LONG).show();
return null;
}
}
}
return joinPoint.proceed();
}
/**
* 通过对象获取上下文
*
* @param object
* @return
*/
private Context getContext(Object object) {
if (object instanceof Activity) {
return (Activity) object;
} else if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
return fragment.getActivity();
} else if (object instanceof View) {
View view = (View) object;
return view.getContext();
}
return null;
}
/**
* 检查当前网络是否可用
*
* @return
*/
public boolean isNetworkAvailable(Context context) {
// 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
// 获取NetworkInfo对象
NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo();
if (networkInfo != null && networkInfo.length > 0) {
for (int i = 0; i < networkInfo.length; i++) {
// 判断当前网络状态是否为连接状态
if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
}
return false;
}
}代码中使用:public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 登录的点击事件
* 常用方法判断网络连接
* @param v
*/
public void login_common(View v) {
//CheckNetUtil网络处理的工具类
if (CheckNetUtil.isNetworkAvailable(this)) {
//如果有网,继续操作 比如处理登录的逻辑
Intent intent = new Intent(this,ActivityLoginSuccess.class);
startActivity(intent);
} else {
Toast.makeText(this, "请检查您的网络连接!", Toast.LENGTH_LONG).show();
}
}
/**
* 登录的点击事件
* 使用AOP来判断网络连接
* @param v
*/
@CheckNet
public void login_by_aop(View v) {
Intent intent = new Intent(this,ActivityLoginSuccess.class);
startActivity(intent);
}
}有网的情况下:
网络连接断开的情况下:
两种方法相同的结果,显然用AOP思想来判断代码简洁很多。当然我们也说过,其实“spectJ的使用核心就是它的编译器,它就做了一件事,将AspectJ的代码在编译期插入目标程序当中,运行时跟在其它地方没什么两样”我们来分析一下编译后的代码来看看是不是这么回事
反编译后的代码:
@CheckNet
public void login_by_aop(View paramView)
{
JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, this, this, paramView);
login_by_aop_aroundBody1$advice(this, paramView, localJoinPoint, AspectUtils.aspectOf(), (ProceedingJoinPoint)localJoinPoint);
}
public void login_common(View paramView)
{
if (CheckNetUtil.isNetworkAvailable(this))
{
startActivity(new Intent(this, ActivityLoginSuccess.class));
return;
}
Toast.makeText(this, "请检查您的网络连接!", 1).show();
} private static final Object login_by_aop_aroundBody1$advice(MainActivity paramMainActivity, View paramView, JoinPoint paramJoinPoint, AspectUtils paramAspectUtils, ProceedingJoinPoint paramProceedingJoinPoint)
{
if ((CheckNet)((MethodSignature)paramProceedingJoinPoint.getSignature()).getMethod().getAnnotation(CheckNet.class) != null)
{
Context localContext = AspectUtils.ajc$inlineAccessMethod$mjoys_com_aopprograming_AspectUtils$mjoys_com_aopprograming_AspectUtils$getContext(paramAspectUtils, paramProceedingJoinPoint.getThis());
if ((localContext != null) && (!paramAspectUtils.isNetworkAvailable(localContext)))
{
Toast.makeText(localContext, "请检查您的网络", 1).show();
return null;
}
}
login_by_aop_aroundBody0(paramMainActivity, paramView, paramProceedingJoinPoint);
return null;
}
我们的代码没有进行混淆,如果不使用面向切面编程,我们反编译后的文件是不会有太大变化的,但是我们在使用面向切面编程的时候,会改掉我们的class文件如上面的代码所示。