Android进阶系列之AOP面向切面编程

来源:互联网 发布:linux taskset 编辑:程序博客网 时间:2024/06/11 00:27

原文地址:http://blog.csdn.net/sw5131899/article/details/53885957

比较深入的介绍AOP的一篇博文:http://blog.csdn.net/innost/article/details/49387395



*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布  

Android的博大精深,不是一言两语能够说明道清的,它的魅力只有亲身去接触才能体会。就像美女一样


我先在这里申明一下,我这篇AOP只是学习笔记,适合入门级选手,如果阁下是大神想要参考AOP,那么我给个我认为写的很好的AOP链接:http://blog.csdn.net/innost/article/details/49387395,我就是参照他的博客和官网例子学习的。

那么我们先来说说什么是AOP。说到AOP也许不太了解,那么OOP一定是知道的。Object-Oriented Progreming.面向对象编程。学java基础的时候肯定都都会学面向对象思想和三大特性。这里就不详细介绍了。AOP是Aspect-Oriented Progreming的缩写,在OOP设计中有个单一职责原则,在很多时候都不会有问题,但是当很多模块都需要同一个功能的时候,这个时候还用OOP就会很麻烦。那么AOP在Android中的应用就应运而生。


那么先从搭环境开始吧,待会会把aspectJ的jar包放在git上,下载1.8.5.jar包之后,安装在电脑上。直接点安装即可。



下一步下一步就好。这里我只介绍Android Studio的。Eclipse也是可以使用的。Studio就好配置了,在Gradle中进行配置。

贴一下我的gradle的配置。

[java] view plain copy
  1. apply plugin: 'com.android.application'  
  2. import org.aspectj.bridge.IMessage  
  3. import org.aspectj.bridge.MessageHandler  
  4. import org.aspectj.tools.ajc.Main  
  5. buildscript {  
  6.     repositories {  
  7.         mavenCentral()  
  8.     }  
  9.     dependencies {  
  10.         classpath 'org.aspectj:aspectjtools:1.8.9'  
  11.         classpath 'org.aspectj:aspectjweaver:1.8.9'  
  12.     }  
  13. }  
  14. repositories {  
  15.     mavenCentral()  
  16. }  
  17. final def log = project.logger  
  18. final def variants = project.android.applicationVariants  
  19. variants.all { variant ->  
  20.     if (!variant.buildType.isDebuggable()) {  
  21.         log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")  
  22.         return;  
  23.     }  
  24.   
  25.     JavaCompile javaCompile = variant.javaCompile  
  26.     javaCompile.doLast {  
  27.         String[] args = ["-showWeaveInfo",  
  28.                          "-1.8",  
  29.                          "-inpath", javaCompile.destinationDir.toString(),  
  30.                          "-aspectpath", javaCompile.classpath.asPath,  
  31.                          "-d", javaCompile.destinationDir.toString(),  
  32.                          "-classpath", javaCompile.classpath.asPath,  
  33.                          "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]  
  34.         log.debug "ajc args: " + Arrays.toString(args)  
  35.   
  36.         MessageHandler handler = new MessageHandler(true);  
  37.         new Main().run(args, handler);  
  38.         for (IMessage message : handler.getMessages(nulltrue)) {  
  39.             switch (message.getKind()) {  
  40.                 case IMessage.ABORT:  
  41.                 case IMessage.ERROR:  
  42.                 case IMessage.FAIL:  
  43.                     log.error message.message, message.thrown  
  44.                     break;  
  45.                 case IMessage.WARNING:  
  46.                     log.warn message.message, message.thrown  
  47.                     break;  
  48.                 case IMessage.INFO:  
  49.                     log.info message.message, message.thrown  
  50.                     break;  
  51.                 case IMessage.DEBUG:  
  52.                     log.debug message.message, message.thrown  
  53.                     break;  
  54.             }  
  55.         }  
  56.     }  
  57. }  
  58.   
  59. android {  
  60.     compileSdkVersion 24  
  61.     buildToolsVersion "24.0.2"  
  62.     defaultConfig {  
  63.         applicationId "com.example.administrator.aspectjdemo"  
  64.         minSdkVersion 19  
  65.         targetSdkVersion 24  
  66.         versionCode 1  
  67.         versionName "1.0"  
  68.         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"  
  69.     }  
  70.     buildTypes {  
  71.         release {  
  72.             minifyEnabled false  
  73.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
  74.         }  
  75.     }  
  76. }  
  77.   
  78.   
  79.   
  80. dependencies {  
  81.     compile fileTree(include: ['*.jar'], dir: 'libs')  
  82.     androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {  
  83.         exclude group: 'com.android.support', module: 'support-annotations'  
  84.     })  
  85.     compile 'com.android.support:appcompat-v7:24.2.1'  
  86.     testCompile 'junit:junit:4.12'  
  87.     compile files('libs/aspectjrt.jar')  
  88. }  
至于为什么这么配置,AspectJ是对java的扩展,而且是完全兼容java的。但是编译时得用Aspect专门的编译器,这里的配置就是使用Aspect的编译器。这里还在libs导入了一个jar包。创建依赖。


好啦,所有准备工作已完成。那么拿着梭子就是干,别想那么多了。上车吧!

AOP很多是拿来做用户行为统计和性能检测的。那我这里写一个手机权限检测的使用方法。

首先创建一个类,用来处理触发切面的回调。

[java] view plain copy
  1. package com.example.administrator.aspectjdemo;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Context;  
  5. import android.util.Log;  
  6.   
  7. import org.aspectj.lang.ProceedingJoinPoint;  
  8. import org.aspectj.lang.annotation.Around;  
  9. import org.aspectj.lang.annotation.Aspect;  
  10. import org.aspectj.lang.annotation.Pointcut;  
  11. import org.aspectj.lang.reflect.MethodSignature;  
  12.   
  13. /** 
  14.  * Created by Administrator on 2016/12/26. 
  15.  */  
  16. @Aspect  
  17. public class AspectJTest {  
  18.     private static final String TAG = "tag00";  
  19.   
  20.     @Pointcut("execution(@com.example.administrator.aspectjdemo.AspectJAnnotation  * *(..))")  
  21.     public void executionAspectJ() {  
  22.   
  23.     }  
  24.   
  25.     @Around("executionAspectJ()")  
  26.     public Object aroundAspectJ(ProceedingJoinPoint joinPoint) throws Throwable {  
  27.         MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();  
  28.         Log.i(TAG, "aroundAspectJ(ProceedingJoinPoint joinPoint)");  
  29.         AspectJAnnotation aspectJAnnotation = methodSignature.getMethod().getAnnotation(AspectJAnnotation.class);  
  30.         String permission = aspectJAnnotation.value();  
  31.         Context  context = (Context) joinPoint.getThis();  
  32.         Object o = null;  
  33.         if (PermissionManager.getInnerInstance().checkPermission(context,permission)) {  
  34.             o = joinPoint.proceed();  
  35.             Log.i(TAG, "有权限");  
  36.         } else {  
  37.             Log.i(TAG, "没有权限,不给用");  
  38.         }  
  39.         return o;  
  40.     }  
  41.   
  42. }  
这里要使用Aspect的编译器编译必须给类打上标注,@Aspect。

还有这里的Pointcut注解,就是切点,即触发该类的条件。里面的字符串都有哪些呢。让我们来看看,


在我的例子中,我使用了execution,也就是以方法执行时为切点,触发Aspect类。而execution里面的字符串是触发条件,也是具体的切点。我来解释一下参数的构成。“execution(@com.example.administrator.aspectjdemo.AspectJAnnotation  * *(..)”这个条件是所有加了AspectJAnnotation注解的方法或属性都会是切点,范围比较广。

“**”表示是任意包名

“..”表示任意类型任意多个参数

com.example.administrator.aspectjdemo.AspectJAnnotation”这是我的项目包名下需要指定类的绝对路径

那知道了这些基本上就能定义自己想要的切点了。比如我只想在MainActivity的onCreate()方法执行时作为切点。那么字符串应该是“* com.example.administrator.aspectjdemo.MainActivity.onCreate(..)”,那么这个切定就很明确,只有一个。不同的切点可以用与或来连接。看清楚哦,前面有个*号。因为onCreate方法时编译时执行,所以在回调时传入的参数必须是父类JoinPoint。上面除了execution还有其他很多切点类型。比如call方法回调时,get和set分别是获取和执行时。等等,就不举例了。

再来看看@Around,Around是指JPoint执行前或执行后备触发,而around就替代了原JPoint。除了Around还有其他几种方式。


这里的说明和示例大家看看就大概明白什么意思了,具体怎么使用还是亲自去实践才行的。

回到我们的例子上来,创建完Aspect类之后,还需要一个注解类来做笔,哪里需要做切点,那么哪里就用注解标注一下,这样方便快捷。解决了OOP的单一原则问题。

[java] view plain copy
  1. package com.example.administrator.aspectjdemo;  
  2.   
  3.   
  4. import java.lang.annotation.ElementType;  
  5. import java.lang.annotation.Retention;  
  6. import java.lang.annotation.RetentionPolicy;  
  7. import java.lang.annotation.Target;  
  8.   
  9. /** 
  10.  * Created by Administrator on 2016/12/26. 
  11.  */  
  12. @Target(ElementType.METHOD)  
  13. @Retention(RetentionPolicy.RUNTIME)  
  14. public @interface AspectJAnnotation {  
  15.     String value();  
  16. }  
注解其实很简单的,里面的类型并不多。想要了解的童鞋我下篇文章做个学习笔记,大家一起学习一下。

在哪使用呢。方法名和属性名上都可以。

[java] view plain copy
  1. package com.example.administrator.aspectjdemo;  
  2.   
  3. import android.Manifest;  
  4. import android.support.v7.app.AppCompatActivity;  
  5. import android.os.Bundle;  
  6. import android.util.Log;  
  7.   
  8. public class MainActivity extends AppCompatActivity {  
  9.   
  10.     @Override  
  11.     protected void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.activity_main);  
  14.         test();  
  15.     }  
  16.   
  17.     @AspectJAnnotation(value = Manifest.permission.CAMERA)  
  18.     public void test(){  
  19.         Log.i("tag00","检查权限");  
  20.     }  
  21. }  
text()方法执行时就是一个切点。在执行test时,会回调上面的Aspect类的executionAspectJ()方法。然后会依次执行@Before,@After。至于After和Around谁先谁后,这个我不太清楚,如果两个都写的话会报错。所以不知先后。有兴趣的可以自己试试。那么我们在看看aroundAspectJ(ProceedingJoinPoint joinPoint)这个方法里的逻辑。

[java] view plain copy
  1. @Around("executionAspectJ()")  
  2.     public Object aroundAspectJ(ProceedingJoinPoint joinPoint) throws Throwable {  
  3.         MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();  
  4.         Log.i(TAG, "aroundAspectJ(ProceedingJoinPoint joinPoint)");  
  5.         AspectJAnnotation aspectJAnnotation = methodSignature.getMethod().getAnnotation(AspectJAnnotation.class);  
  6.         String permission = aspectJAnnotation.value();  
  7.         Context  context = (Context) joinPoint.getThis();  
  8.         Object o = null;  
  9.         if (PermissionManager.getInnerInstance().checkPermission(context,permission)) {  
  10.             o = joinPoint.proceed();  
  11.             Log.i(TAG, "有权限");  
  12.         } else {  
  13.             Log.i(TAG, "没有权限,不给用");  
  14.         }  
  15.         return o;  
  16.     }  
如果使用的是以方法相关为切点,那么使用MethodSignature来接收joinPoint的Signature。如果是属性或其他的,那么可以使用Signature类来接收。之后可以使用Signature来获取注解类。通过注解可以拿到需要检测的权限名称。但是检测权限又需要上下文,那么通过jointPoint.getThis()获取使用该注解的Activity的上下文,至于Fragment的我还试过。有兴趣的自己试试。那么我们再来看看PermissionManager这个类。

[java] view plain copy
  1. package com.example.administrator.aspectjdemo;  
  2.   
  3. import android.content.Context;  
  4. import android.content.pm.PackageManager;  
  5. import android.support.v4.content.ContextCompat;  
  6. import android.util.Log;  
  7.   
  8. /** 
  9.  * Created by Administrator on 2016/12/26. 
  10.  */  
  11.   
  12. public class PermissionManager {  
  13.     private static volatile PermissionManager permissionManager;  
  14.   
  15.     public PermissionManager(){}  
  16.   
  17.     //DCL单例模式  
  18.     public static PermissionManager getInstance(){  
  19.         if (permissionManager == null){  
  20.             synchronized (PermissionManager.class){  
  21.                 if (permissionManager == null){  
  22.                     permissionManager = new PermissionManager();  
  23.                 }  
  24.             }  
  25.         }  
  26.         return permissionManager;  
  27.     }  
  28.   
  29.     private static class InnerInsatance{  
  30.         public static final PermissionManager instance = new PermissionManager();  
  31.     }  
  32.   
  33.     //内部类单例模式  
  34.     public static PermissionManager getInnerInstance(){  
  35.         synchronized (PermissionManager.class){  
  36.             return InnerInsatance.instance;  
  37.         }  
  38.     }  
  39.   
  40.     public boolean checkPermission(Context context,String permission){  
  41.         Log.i("tag00","检查的权限:"+permission);  
  42.         if (ContextCompat.checkSelfPermission(context,permission) == PackageManager.PERMISSION_GRANTED){  
  43.             return true;  
  44.         }  
  45. //        if (permission.equals("android.permission.CAMERA")){  
  46. //            return true;  
  47. //        }  
  48.         return false;  
  49.     }  
  50. }  
在这个类我试验了一下两种单例模式的性能差别,刚都说了AOP可以用来检测性能嘛。不过没在高并发的环境下其实两种都差不多。ContextCompat.checkSelfPermission(context,permission) == PackageManager.PERMISSION_GRANTED这句代码是检测权限。Android提供的类,可以直接使用。那么到这里思路差不多清晰了。以后想要做用户行为统计,或是性能检测,亦或是权限或者用户权限管理,AOP都是你的不二之选。

怎么样?捋一遍你学到了吗?觉得有用顶一下,谢谢。

最后附上git地址:https://github.com/SingleShu/AspectJDemo。觉得有用的给个star,谢谢。


原创粉丝点击