为什么要使用面向切面编程?
举一个很简单的例子:
有很多的页面需要在手机网络正常的时候才能使用,如果手机网络异常,用户点击时不能进入下一层页面,并且要提示用户检查手机网络设置。
按照正常的做法,代码会这么写:
public void checkNetworkNormal(){
if (NetworkUtils.isNetworkAvailable(this)) {
Log.i(TAG, "手机有网,可以进入下一个页面");
} else {
Log.i(TAG,"请检查手机网络设置");
}
}
这样写最大的缺点是:如果项目中有很多的地方需要检查网络,那这一段代码在我们项目中会出现无数次。代码重复度过高并且不优雅。
AOP编程的优雅的解决
@CheckNetwork()
public void checkNetwork(){
Log.i(TAG,"进入下一个Activity");
}
如上所示,在需要检查网络的方法上,只需要一个自定义的注解即可。
Android使用AOP步骤
引入方式一
moudle下的build.gradle配置
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()
}
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;
}
}
}
}
android {
compileSdkVersion 27
......
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
......
compile 'org.aspectj:aspectjrt:1.8.9'
}
前辈们已经替我们搭建好了架子,把 android{}
上面的这一大段原封不动的 copy 过去即可,再添加依赖
compile 'org.aspectj:aspectjrt:1.8.9'
引入方式二
每次都copy这么多既麻烦又累,并且导致build.gradle
里面很混乱,我们又站在前辈们的肩膀上简化。
- 在项目根目录下的build.gradle中,添加依赖:
dependencies {
......
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
}
- 在app的build.gradle中,添加:
apply plugin: 'android-aspectjx'
dependencies {
......
implementation 'org.aspectj:aspectjrt:1.8.9'
}
是不是 So Easy,感谢沪江的大神们。
2.自定义注解
checkNetwork
方法上有一个注解@CheckNetwork()
,这个是一个自定义的注解,需要我们手动去写,其实也很简单。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckNetwork {
//String value();
//int type();
}
也可以添加自定义的方法,但是不是必须的,AOP切面的时候是可以拿到这些参数的。
3.创建切面类
其实这个代码也相当于是模版代码了。
@Aspect
public class CheckNetworkAspect {
private static final String TAG = CheckNetworkAspect.class.getSimpleName();
/**
* 找到处理的切点
* * *(..) “**”表示是任意包名 “..”表示任意类型任意多个参数
*/
@Pointcut("execution(@com.ldlywt.androidadvancedemo.aspect.CheckNetwork * *(..))")
public void executionCheckNetwork() {
}
/**
* 处理切面
*/
@Around("executionCheckNetwork()")
public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
CheckNetwork annotation = signature.getMethod().getAnnotation(CheckNetwork.class);
if (annotation != null) {
Context context = getContext(joinPoint.getThis());
if (NetworkUtils.isNetworkAvailable(context)) {
Toast.makeText(context, "当前网络正常", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "此时没有网络连接", Toast.LENGTH_SHORT).show();
}
return joinPoint.proceed();
}
return null;
}
/**
* 通过对象获取上下文
*/
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 android.app.Fragment) {
android.app.Fragment fragment = (android.app.Fragment) object;
return fragment.getActivity();
} else if (object instanceof View) {
View view = (View) object;
return view.getContext();
}
return null;
}
需要注意几点:
- @Pointcut(“execution(@此处写的是自定义注解所在的全路径 * *(..))”)
- @Around(“此处写的是@Pointcut注解下的方法名,要统一”)
- joinPoint.getThis()方法拿到的上下文要进行判断,有可能是view、activity、fragment中。
- 如果在自定义的注解中定义了方法,通过如下可以拿到值
String content = annotation.value();
int type = annotation.type();
- 自定义注解上的参数赋值如下
@BehaviorTrace(value = "打开首页", type = 1)
public void behaviorTrace(){
Log.i(TAG,"用户行为检查");
}
在Android中使用AOP就三步。
更多AOP使用场景
- AOP编程检查权限是否开启
- AOP编程检查网络状态是否可用
- AOP检查用户登录
- AOP用户行为统计
具体见Demo,代码直接复制过去即可用:
后记
其实AOP编程在Java后台的Spring中用的非常频繁,它跟OOP(面向对象)一样都是一种编程思想。
我们最主要的不是学习到一种检查手机网络的新方法,而是要了解并且学习AOP编程思想。
想具体学习AOP编程的请转大神写的blog: