AOP:面向切面编程

转载自https://www.jianshu.com/p/b96a68ba50db

 

 

AOP:面向切面编程(Aspect-Oriented Programming)。如果说,OOP如果是把问题划分到单个模块的话,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。 Aspect介绍篇:Android中的AOP编程 这里通过几个小例子,讲解在Android开发中,如何运用AOP的方式,进行全局切片管理,达到简洁优雅,一劳永逸的效果。

1、SingleClickAspect,防止View被连续点击出发多次事件

在使用aop之前,可以这样写了单独写个Click类(不优雅)或者RxBinding(不简洁):

 
  1. RxView.clicks(mButton)

  2. .throttleFirst(1, TimeUnit.SECONDS)

  3. .subscribe(new Action1<Void>() {

  4.  
  5. @Override

  6. public void call(Void v) {

  7. dosomething();

  8. }

  9. });

  10.  

现在,只需要一个注解,就可以轻松解决一切问题:

 
  1. @Aspect

  2. public class SingleClickAspect {

  3. static int TIME_TAG = R.id.click_time;

  4. public static final int MIN_CLICK_DELAY_TIME = 600;//间隔时间600ms

  5.  
  6. @Pointcut("execution(@com.app.annotation.aspect.SingleClick * *(..))")//根据SingleClick注解找到方法切入点

  7. public void methodAnnotated() {

  8. }

  9.  
  10. @Around("methodAnnotated()")//在连接点进行方法替换

  11. public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

  12. View view = null;

  13. for (Object arg : joinPoint.getArgs())

  14. if (arg instanceof View) view = (View) arg;

  15. if (view != null) {

  16. Object tag = view.getTag(TIME_TAG);

  17. long lastClickTime = ((tag != null) ? (long) tag : 0);

  18. LogUtils.showLog("SingleClickAspect", "lastClickTime:" + lastClickTime);

  19. long currentTime = Calendar.getInstance().getTimeInMillis();

  20. if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {//过滤掉600毫秒内的连续点击

  21. view.setTag(TIME_TAG, currentTime);

  22. LogUtils.showLog("SingleClickAspect", "currentTime:" + currentTime);

  23. joinPoint.proceed();//执行原方法

  24. }

  25. }

  26. }

  27. }

使用方法:标注在onClick上

 
  1. @SingleClick

  2. public void onClick(View view) {

  3. String comment = mViewBinding.btComment.getText().toString();

  4. if (TextUtils.isEmpty(comment))

  5. Snackbar.make(mViewBinding.fab, "评论不能为空!", Snackbar.LENGTH_LONG).show();

  6. else mPresenter.createComment(comment, mArticle, SpUtil.getUser());

  7. }

或者任何参数内有view可以做为参照系(view可以不是onClick的view,仅仅作为时间tag依附对象作为参照)的方法上,例如TRouter的页面跳转,防止连续快速点击重复跳页现象:

 
  1. public class RouterHelper {

  2.  
  3. @SingleClick // 防止连续点击

  4. public static void go(String actionName, HashMap data, View view) {

  5. TRouter.go(actionName, data, view);

  6. }

  7. }

2、CheckLoginAspect 拦截未登录用户的权限

不使用aop的情况,需要在每个方法体内判断用户登录状态,然后处理,现在,只需要一个注解轻松解决:

 
  1.  
  2. /**

  3. * Created by baixiaokang

  4. * 通过CheckLogin注解检查用户是否登陆注解,通过aop切片的方式在编译期间织入源代码中

  5. * 功能:检查用户是否登陆,未登录则提示登录,不会执行下面的逻辑

  6. */

  7. @Aspect

  8. public class CheckLoginAspect {

  9.  
  10. @Pointcut("execution(@com.app.annotation.aspect.CheckLogin * *(..))")//方法切入点

  11. public void methodAnnotated() {

  12. }

  13.  
  14. @Around("methodAnnotated()")//在连接点进行方法替换

  15. public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

  16. if (null == SpUtil.getUser()) {

  17. Snackbar.make(App.getAppContext().getCurActivity().getWindow().getDecorView(), "请先登录!", Snackbar.LENGTH_LONG)

  18. .setAction("登录", new View.OnClickListener() {

  19. @Override

  20. public void onClick(View view) {

  21. TRouter.go(C.LOGIN);

  22. }

  23. }).show();

  24. return;

  25. }

  26. joinPoint.proceed();//执行原方法

  27. }

  28. }

使用方法:

 
  1. public class AdvisePresenter extends AdviseContract.Presenter {

  2.  
  3. @CheckLogin

  4. public void createMessage(String msg) {

  5. _User user = SpUtil.getUser();

  6. ApiFactory.createMessage(

  7. new Message(ApiUtil.getPointer(

  8. new _User(C.ADMIN_ID)), msg,

  9. ApiUtil.getPointer(user),

  10. user.objectId))

  11. .subscribe(

  12. res -> mView.sendSuc(),

  13. e -> mView.showMsg("消息发送失败!"));

  14. }

  15.  
  16. @CheckLogin

  17. public void initAdapterPresenter(AdapterPresenter mAdapterPresenter) {

  18. mAdapterPresenter

  19. .setRepository(ApiFactory::getMessageList)

  20. .setParam(C.INCLUDE, C.CREATER)

  21. .setParam(C.UID, SpUtil.getUser().objectId)

  22. .fetch();

  23. }

  24. }

从此只需要专注主要逻辑即可。

3、MemoryCacheAspect内存缓存切片

根据参数key缓存方法返回值,使我们纯净的Presenter(无参构造和无内部状态)达到全局缓存的单例复用效果,同样适用于其他需要缓存结果的方法:

 
  1. /**

  2. * Created by baixiaokang on 16/10/24.

  3. * 根据MemoryCache注解自动添加缓存代理代码,通过aop切片的方式在编译期间织入源代码中

  4. * 功能:缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。

  5. */

  6. @Aspect

  7. public class MemoryCacheAspect {

  8.  
  9. @Pointcut("execution(@com.app.annotation.aspect.MemoryCache * *(..))")//方法切入点

  10. public void methodAnnotated() {

  11. }

  12.  
  13. @Around("methodAnnotated()")//在连接点进行方法替换

  14. public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

  15. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

  16. String methodName = methodSignature.getName();

  17. MemoryCacheManager mMemoryCacheManager = MemoryCacheManager.getInstance();

  18. StringBuilder keyBuilder = new StringBuilder();

  19. keyBuilder.append(methodName);

  20. for (Object obj : joinPoint.getArgs()) {

  21. if (obj instanceof String) keyBuilder.append((String) obj);

  22. else if (obj instanceof Class) keyBuilder.append(((Class) obj).getSimpleName());

  23. }

  24. String key = keyBuilder.toString();

  25. Object result = mMemoryCacheManager.get(key);//key规则 : 方法名+参数1+参数2+...

  26. LogUtils.showLog("MemoryCache", "key:" + key + "--->" + (result != null ? "not null" : "null"));

  27. if (result != null) return result;//缓存已有,直接返回

  28. result = joinPoint.proceed();//执行原方法

  29. if (result instanceof List && result != null && ((List) result).size() > 0 //列表不为空

  30. || result instanceof String && !TextUtils.isEmpty((String) result)//字符不为空

  31. || result instanceof Object && result != null)//对象不为空

  32. mMemoryCacheManager.add(key, result);//存入缓存

  33. LogUtils.showLog("MemoryCache", "key:" + key + "--->" + "save");

  34. return result;

  35. }

  36. }

看看Apt生成的Factory:

 
  1. /**

  2. * @ 实例化工厂 此类由apt自动生成 */

  3. public final class InstanceFactory {

  4. /**

  5. * @此方法由apt自动生成 */

  6. @MemoryCache

  7. public static Object create(Class mClass) throws IllegalAccessException, InstantiationException {

  8. switch (mClass.getSimpleName()) {

  9. case "AdvisePresenter": return new AdvisePresenter();

  10. case "ArticlePresenter": return new ArticlePresenter();

  11. case "HomePresenter": return new HomePresenter();

  12. case "LoginPresenter": return new LoginPresenter();

  13. case "UserPresenter": return new UserPresenter();

  14. default: return mClass.newInstance();

  15. }

  16. }

  17. }

从此Presenter就是全局单例的可复用状态。

4、TimeLogAspect 自动打印方法的耗时

经常遇到需要log一个耗时操作究竟执行了多长时间,无aop时,需要每个方法体内添加代码,现在,只需要一个注解就可以一劳永逸:

 
  1. /**

  2. * 根据注解TimeLog自动添加打印方法耗代码,通过aop切片的方式在编译期间织入源代码中

  3. * 功能:自动打印方法的耗时

  4. */

  5. @Aspect

  6. public class TimeLogAspect {

  7.  
  8. @Pointcut("execution(@com.app.annotation.aspect.TimeLog * *(..))")//方法切入点

  9. public void methodAnnotated() {

  10. }

  11.  
  12. @Pointcut("execution(@com.app.annotation.aspect.TimeLog *.new(..))")//构造器切入点

  13. public void constructorAnnotated() {

  14. }

  15.  
  16. @Around("methodAnnotated() || constructorAnnotated()")//在连接点进行方法替换

  17. public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {

  18. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

  19. LogUtils.showLog("TimeLog getDeclaringClass", methodSignature.getMethod().getDeclaringClass().getCanonicalName());

  20. String className = methodSignature.getDeclaringType().getSimpleName();

  21. String methodName = methodSignature.getName();

  22. long startTime = System.nanoTime();

  23. Object result = joinPoint.proceed();//执行原方法

  24. StringBuilder keyBuilder = new StringBuilder();

  25. keyBuilder.append(methodName + ":");

  26. for (Object obj : joinPoint.getArgs()) {

  27. if (obj instanceof String) keyBuilder.append((String) obj);

  28. else if (obj instanceof Class) keyBuilder.append(((Class) obj).getSimpleName());

  29. }

  30. String key = keyBuilder.toString();

  31. LogUtils.showLog("TimeLog", (className + "." + key + joinPoint.getArgs().toString() + " --->:" + "[" + (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) + "ms]"));// 打印时间差

  32. return result;

  33. }

  34. }

使用方法:

 
  1. @TimeLog

  2. public void onCreate() {

  3. super.onCreate();

  4. mApp = this;

  5. SpUtil.init(this);

  6. store = new Stack<>();

  7. registerActivityLifecycleCallbacks(new SwitchBackgroundCallbacks());

  8. }

从此方法耗时打印一个注解搞定!

5、SysPermissionAspect运行时权限申请

 
  1. /**

  2. * 申请系统权限切片,根据注解值申请所需运行权限

  3. */

  4. @Aspect

  5. public class SysPermissionAspect {

  6.  
  7. @Around("execution(@com.app.annotation.aspect.Permission * *(..)) && @annotation(permission)")

  8. public void aroundJoinPoint(ProceedingJoinPoint joinPoint, Permission permission) throws Throwable {

  9. AppCompatActivity ac = (AppCompatActivity) App.getAppContext().getCurActivity();

  10. new AlertDialog.Builder(ac)

  11. .setTitle("提示")

  12. .setMessage("为了应用可以正常使用,请您点击确认申请权限。")

  13. .setNegativeButton("取消", null)

  14. .setPositiveButton("允许", new DialogInterface.OnClickListener() {

  15. @Override

  16. public void onClick(DialogInterface dialog, int which) {

  17. MPermissionUtils.requestPermissionsResult(ac, 1, permission.value()

  18. , new MPermissionUtils.OnPermissionListener() {

  19. @Override

  20. public void onPermissionGranted() {

  21. try {

  22. joinPoint.proceed();//获得权限,执行原方法

  23. } catch (Throwable e) {

  24. e.printStackTrace();

  25. }

  26. }

  27.  
  28. @Override

  29. public void onPermissionDenied() {

  30. MPermissionUtils.showTipsDialog(ac);

  31. }

  32. });

  33. }

  34. })

  35. .create()

  36. .show();

  37. }

  38. }

使用方法:

 
  1. @Permission(Manifest.permission.CAMERA)

  2. public void takePhoto() {

  3. startActivityForResult(

  4. new Intent(MediaStore.ACTION_IMAGE_CAPTURE)

  5. .putExtra(MediaStore.EXTRA_OUTPUT,

  6. Uri.fromFile(new File(getExternalCacheDir()+ "user_photo.png"))),

  7. C.IMAGE_REQUEST_CODE);

  8. }

  9.  

动态权限申请一步搞定。

除了这些简单的示例,AOP还可以实现动态权限申请和其他用户权限管理,包括功能性切片和逻辑性切片,使日常开发更加简洁优雅,只需要关注重点业务逻辑,把其他的小事,都交给切片来自动处理吧。

更多AOP的实际应用,请关注项目T-MVP

或者加群来搞基:

QQ群:AndroidMVP 555343041

更新日志:

2017/1/31:AOP新增SysPermissionAspect支持动态申请系统权限切片,轻松适配6.0+

2017/1/27:AOP新增DbRealmAspect支持Realm数据库,数据库突破你想像的简单(年夜特供)

2017/1/8: 使用Apt封装Retrofit生成ApiFactory替换掉所有的Repository,狂删代码

2017/1/7: 使用DataBinding替换掉所有的ButterKnife,狂删代码

2017/1/6: 使用DataBinding替换掉所有的ViewHolder,狂删代码,从此迈向新时代

2016/12/30:使用Apt生成全局路由TRouter,更优雅的页面跳转,支持传递参数和共享view转场动画

2016/12/29:去掉BaseMultiVH新增VHClassSelector支持更完美的多ViewHolder

2016/12/28:使用Apt生成全局的ApiFactory替代所有的Model

2016/12/27:增加了BaseMultiVH扩展支持多类型的ViewHolder

2016/12/26:抽离CoreAdapterPresenter优化TRecyclerView

安卓AOP实战:面向切片编程

Android实用技巧之:用好泛型,少写代码

安卓AOP实战:APT打造极简路由

全局路由TRouter,更优雅的页面跳转

安卓AOP实战:Javassist强撸EventBus

加入OkBus,实现注解传递事件

安卓AOP三剑客:APT,AspectJ,Javassist

1、去掉所有反射>2、新增apt初始化工厂,替换掉了dagger2。>3、新增aop切片,处理缓存和日志

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值