Android面向切面编程

面向切面编程定义:面向切面编程(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文件如上面的代码所示。


版权声明:本文为grg106原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。