Android Kotlin使用AspectJ进行AOP面向切面编程

前言

什么是面向切面编程?首先我们来了解下两个概念:
OOP(面向对象编程):针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
AOP(面向切面编程):则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

由此可知,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

配置AspectJ以及报错处理

这里,我们就使用AspectJ进行切面编程,其相对应的github地址:hujiang.aspectjx

配置步骤如下:

  1. 根目录build.gradle中添加 classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'
  2. 对应app模块build.gradle 添加:
apply plugin: 'android-aspectjx'
implementation 'org.aspectj:aspectjrt:1.8.9'

AspectJ配置
当然如果按照以上配置就可以编译通过,那要先祝贺你了,这里我记录下我配置后编译报错的一些问题:

> Failed to apply plugin 'android-aspectjx'.
   > Build was configured to prefer settings repositories over project repositories but repository 'MavenLocal' was added by plugin 'android-aspectjx'

解决方案: 替换setting.gradle文件dependencyResolutionManagement配置中 repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
aspect问题1

Cause: zip file is empty

解决方案:对应app模块添加配置

aspectjx{
        exclude 'org.jetbrains.kotlin', "module-info", 'versions.9'
    }

aspect问题2
经过上述的配置,我们就可以开始使用AspectJ进行切面编程了!!!

一个小栗子

这里,我们举个简单又实际开发中会碰到的案例:
比如在开发中,碰到很多地方需要先判断当前是否有网络,如果没有网络就弹Toast,有网络的时候才进行对应界面跳转!
那怎么去处理这类问题呢?我们常用的做法是一个个地方去进行判断是否有网络,这样如果需求进行变动,就会很麻烦,同时这种做法也不太优雅。
下面我们就使用AspectJ来解决这个问题;

  • 首先定义CheckNet注解
package com.crystal.essayjoker.aop
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class CheckNet()
  • CheckNet注解进行界面处理
package com.crystal.essayjoker.aop

import android.app.Activity
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut

@Aspect
class CheckNetAspect {
   /**
    * 找到要处理的切点
    *  * *(..)表示可以处理所有的方法 比如有参无参的
    */
   @Pointcut("execution(@com.crystal.essayjoker.aop.CheckNet * *(..))")
   fun checkNetBehavior() {

   }

   /**
    * 处理切面
    */
   @Around("checkNetBehavior()")
   fun checkNetRound(joinPoint: ProceedingJoinPoint): Any? {
       Log.e("CheckNetAspect", "checkNetRound")
       val target = joinPoint.`this` //获取当前切点所在的类
       val context = getContext(target)
       if (context != null) {
           if (!isNetworkAvailable(context)) {
               Toast.makeText(context, "请检查网络是否正常", Toast.LENGTH_SHORT).show()
               return null
           }
       }
       return joinPoint.proceed()
   }

   private fun getContext(target: Any): Context? {
       return when (target) {
           is Activity -> {
               target
           }
           is Fragment -> {
               target.context
           }
           is View -> {
               target.context
           }
           else -> null
       }
   }

   /**
    * 判断网络是否可用
    */
   private fun isNetworkAvailable(context: Context): Boolean {
       // 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
       val connectivityManager =
           context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
       // 获取NetworkInfo对象
       val networkInfo = connectivityManager.allNetworkInfo
       if (networkInfo.isNotEmpty()) {
           for (i in networkInfo.indices) {
               // 判断当前网络状态是否为连接状态
               if (networkInfo[i].state == NetworkInfo.State.CONNECTED) {
                   return true
               }
           }
       }
       return false
   }

}
  • 测试验证
class CheckNetActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_check_net)
       findViewById<Button>(R.id.btn).setOnClickListener {
           onCheckNet()
       }
   }

   @CheckNet
   fun onCheckNet() {
       Toast.makeText(this@CheckNetActivity, "跳转到其他界面", Toast.LENGTH_SHORT).show()
   }
}

测试结果:点击按钮时,会去检查网络,没有会提示请检查网络是否正常,有网则会提示跳转到其他界面!

AspectJ实现原理

上面我们简单实用AspectJ进行了切面编程,那咱一定很好奇它是如何做到切面处理的呢?我们使用jadx反编译生成的APK,找到CheckNetActivity中的onCheckNet方法

  private static final /* synthetic */ void onCheckNet_aroundBody0(CheckNetActivity ajc$this, JoinPoint joinPoint) {
        Toast.makeText(ajc$this, "跳转到其他界面", 0).show();
    }

    private static final /* synthetic */ Object onCheckNet_aroundBody1$advice(CheckNetActivity ajc$this, JoinPoint thisJoinPoint, CheckNetAspect ajc$aspectInstance, ProceedingJoinPoint joinPoint) {
        Intrinsics.checkNotNullParameter(joinPoint, "joinPoint");
        Log.e("CheckNetAspect", "checkNetRound");
        Object target = joinPoint.getThis();
        Intrinsics.checkNotNullExpressionValue(target, TypedValues.AttributesType.S_TARGET);
        Context context = ajc$aspectInstance.getContext(target);
        if (context == null || ajc$aspectInstance.isNetworkAvailable(context)) {
            onCheckNet_aroundBody0(ajc$this, joinPoint);
            return null;
        }
        Toast.makeText(context, "请检查网络是否正常", 0).show();
        return null;
    }

可以看出在编译时AspectJ将代码插入到onCheckNet方法中,由此实现了切面编程!

小结

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值