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

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

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

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

 RxView.clicks(mButton).throttleFirst(1, TimeUnit.SECONDS).subscribe(new Action1<Void>() {@Overridepublic void call(Void v) {dosomething();}});

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

@Aspect
public class SingleClickAspect {static int TIME_TAG = R.id.click_time;public static final int MIN_CLICK_DELAY_TIME = 600;//间隔时间600ms@Pointcut("execution(@com.app.annotation.aspect.SingleClick * *(..))")//根据SingleClick注解找到方法切入点public void methodAnnotated() {}@Around("methodAnnotated()")//在连接点进行方法替换public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {View view = null;for (Object arg : joinPoint.getArgs())if (arg instanceof View) view = (View) arg;if (view != null) {Object tag = view.getTag(TIME_TAG);long lastClickTime = ((tag != null) ? (long) tag : 0);LogUtils.showLog("SingleClickAspect", "lastClickTime:" + lastClickTime);long currentTime = Calendar.getInstance().getTimeInMillis();if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {//过滤掉600毫秒内的连续点击view.setTag(TIME_TAG, currentTime);LogUtils.showLog("SingleClickAspect", "currentTime:" + currentTime);joinPoint.proceed();//执行原方法}}}
}

使用方法:标注在onClick上

   @SingleClickpublic void onClick(View view) {String comment = mViewBinding.btComment.getText().toString();if (TextUtils.isEmpty(comment))Snackbar.make(mViewBinding.fab, "评论不能为空!", Snackbar.LENGTH_LONG).show();else mPresenter.createComment(comment, mArticle, SpUtil.getUser());}

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

public class RouterHelper {@SingleClick // 防止连续点击public static void go(String actionName, HashMap data, View view) {TRouter.go(actionName, data, view);}
}

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

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


/*** Created by baixiaokang* 通过CheckLogin注解检查用户是否登陆注解,通过aop切片的方式在编译期间织入源代码中* 功能:检查用户是否登陆,未登录则提示登录,不会执行下面的逻辑*/
@Aspect
public class CheckLoginAspect {@Pointcut("execution(@com.app.annotation.aspect.CheckLogin * *(..))")//方法切入点public void methodAnnotated() {}@Around("methodAnnotated()")//在连接点进行方法替换public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {if (null == SpUtil.getUser()) {Snackbar.make(App.getAppContext().getCurActivity().getWindow().getDecorView(), "请先登录!", Snackbar.LENGTH_LONG).setAction("登录", new View.OnClickListener() {@Overridepublic void onClick(View view) {TRouter.go(C.LOGIN);}}).show();return;}joinPoint.proceed();//执行原方法}
}

使用方法:

public class AdvisePresenter extends AdviseContract.Presenter {@CheckLoginpublic void createMessage(String msg) {_User user = SpUtil.getUser();ApiFactory.createMessage(new Message(ApiUtil.getPointer(new _User(C.ADMIN_ID)), msg,ApiUtil.getPointer(user),user.objectId)).subscribe(res -> mView.sendSuc(),e -> mView.showMsg("消息发送失败!"));}@CheckLoginpublic void initAdapterPresenter(AdapterPresenter mAdapterPresenter) {mAdapterPresenter.setRepository(ApiFactory::getMessageList).setParam(C.INCLUDE, C.CREATER).setParam(C.UID, SpUtil.getUser().objectId).fetch();}
}

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

3、MemoryCacheAspect内存缓存切片

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

/*** Created by baixiaokang on 16/10/24.* 根据MemoryCache注解自动添加缓存代理代码,通过aop切片的方式在编译期间织入源代码中* 功能:缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。*/
@Aspect
public class MemoryCacheAspect {@Pointcut("execution(@com.app.annotation.aspect.MemoryCache * *(..))")//方法切入点public void methodAnnotated() {}@Around("methodAnnotated()")//在连接点进行方法替换public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();String methodName = methodSignature.getName();MemoryCacheManager mMemoryCacheManager = MemoryCacheManager.getInstance();StringBuilder keyBuilder = new StringBuilder();keyBuilder.append(methodName);for (Object obj : joinPoint.getArgs()) {if (obj instanceof String) keyBuilder.append((String) obj);else if (obj instanceof Class) keyBuilder.append(((Class) obj).getSimpleName());}String key = keyBuilder.toString();Object result = mMemoryCacheManager.get(key);//key规则 : 方法名+参数1+参数2+...LogUtils.showLog("MemoryCache", "key:" + key + "--->" + (result != null ? "not null" : "null"));if (result != null) return result;//缓存已有,直接返回result = joinPoint.proceed();//执行原方法if (result instanceof List && result != null && ((List) result).size() > 0 //列表不为空|| result instanceof String && !TextUtils.isEmpty((String) result)//字符不为空|| result instanceof Object && result != null)//对象不为空mMemoryCacheManager.add(key, result);//存入缓存LogUtils.showLog("MemoryCache", "key:" + key + "--->" + "save");return result;}
}

看看Apt生成的Factory:

/*** @ 实例化工厂 此类由apt自动生成 */
public final class InstanceFactory {/*** @此方法由apt自动生成 */@MemoryCachepublic static Object create(Class mClass) throws IllegalAccessException, InstantiationException {switch (mClass.getSimpleName()) {case "AdvisePresenter": return  new AdvisePresenter();case "ArticlePresenter": return  new ArticlePresenter();case "HomePresenter": return  new HomePresenter();case "LoginPresenter": return  new LoginPresenter();case "UserPresenter": return  new UserPresenter();default: return mClass.newInstance();}}
}

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

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

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

/*** 根据注解TimeLog自动添加打印方法耗代码,通过aop切片的方式在编译期间织入源代码中* 功能:自动打印方法的耗时*/
@Aspect
public class TimeLogAspect {@Pointcut("execution(@com.app.annotation.aspect.TimeLog * *(..))")//方法切入点public void methodAnnotated() {}@Pointcut("execution(@com.app.annotation.aspect.TimeLog *.new(..))")//构造器切入点public void constructorAnnotated() {}@Around("methodAnnotated() || constructorAnnotated()")//在连接点进行方法替换public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();LogUtils.showLog("TimeLog getDeclaringClass", methodSignature.getMethod().getDeclaringClass().getCanonicalName());String className = methodSignature.getDeclaringType().getSimpleName();String methodName = methodSignature.getName();long startTime = System.nanoTime();Object result = joinPoint.proceed();//执行原方法StringBuilder keyBuilder = new StringBuilder();keyBuilder.append(methodName + ":");for (Object obj : joinPoint.getArgs()) {if (obj instanceof String) keyBuilder.append((String) obj);else if (obj instanceof Class) keyBuilder.append(((Class) obj).getSimpleName());}String key = keyBuilder.toString();LogUtils.showLog("TimeLog", (className + "." + key + joinPoint.getArgs().toString() + " --->:" + "[" + (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) + "ms]"));// 打印时间差return result;}
}

使用方法:

  @TimeLogpublic void onCreate() {super.onCreate();mApp = this;SpUtil.init(this);store = new Stack<>();registerActivityLifecycleCallbacks(new SwitchBackgroundCallbacks());}

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

5、SysPermissionAspect运行时权限申请

/*** 申请系统权限切片,根据注解值申请所需运行权限*/
@Aspect
public class SysPermissionAspect {@Around("execution(@com.app.annotation.aspect.Permission * *(..)) && @annotation(permission)")public void aroundJoinPoint(ProceedingJoinPoint joinPoint, Permission permission) throws Throwable {AppCompatActivity ac = (AppCompatActivity) App.getAppContext().getCurActivity();new AlertDialog.Builder(ac).setTitle("提示").setMessage("为了应用可以正常使用,请您点击确认申请权限。").setNegativeButton("取消", null).setPositiveButton("允许", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {MPermissionUtils.requestPermissionsResult(ac, 1, permission.value(), new MPermissionUtils.OnPermissionListener() {@Overridepublic void onPermissionGranted() {try {joinPoint.proceed();//获得权限,执行原方法} catch (Throwable e) {e.printStackTrace();}}@Overridepublic void onPermissionDenied() {MPermissionUtils.showTipsDialog(ac);}});}}).create().show();}
}

使用方法:

   @Permission(Manifest.permission.CAMERA)public void takePhoto() {startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(new File(getExternalCacheDir()+ "user_photo.png"))),C.IMAGE_REQUEST_CODE);}

动态权限申请一步搞定。

除了这些简单的示例,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切片,处理缓存和日志


作者:North_2016链接:https://www.jianshu.com/p/b96a68ba50db來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Android面向切面(AOP)编程实战相关推荐

  1. Spring 详解(三):AOP 面向切面的编程

    AOP即面向切面编程,它通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型. ...

  2. Android 面向切面编程(AOP)

    概念: AOP: Aspect Oriented Programming 面向切面编程. AOP是个概念,AspectJ 是它的一个具体实现.和Java配合使用. AspectJ:核心是他的编译器(a ...

  3. Android 面向切面编程-aspjectj应用

    先附上自己基于aspject封装的aop 插件 1.为什么要用切面 随着业务越来越复杂,项目中的模块可能越来越多, 面向切面可以减少模块间的耦合,提高模块的复用率 2.aspjectj语法 andro ...

  4. Android面向切面AspectJ

    这一节主要了解一下AspectJ技术,它属于AOP(Aspect Oriented Programming)技术,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.最 ...

  5. spring面向切面aop拦截器

    spring中有很多概念和名词,其中有一些名字不同,但是从功能上来看总感觉是那么的相似,比如过滤器.拦截器.aop等. 过滤器filter.spring mvc拦截器Interceptor .面向切面 ...

  6. Spring学习笔记:3(面向切面AOP)

    AOP:Aspect Oriented Program(面向切面) 我们再回顾一下AOP的一些术语: 通知(Advice) 就是你想要的功能,也就是的安全.事物.日志等.先定义好,然后在想用的地方用一 ...

  7. Android面向切面编程框架(AspectJ 讲解)

    安装AspectJ Pangu-Immortal (Pangu-Immortal) · GitHub Android上的ApsectJ开发由几部分组成,AspectJ gradle插件,ApsectJ ...

  8. 面向切面编程AspectJ在Android埋点的实践

    在项目开发中,对 App 客户端重构后,发现用于统计用户行为的友盟统计代码和用户行为日志记录代码分散在各业务模块中,比如在某个模块,要想实现对用户的行为一和行为二进行统计,因此按照OOP面向对象编程思 ...

  9. AOP面向切面编程 淘宝京东网络处理

    ####现象描述 当我们打开京东 app 进入首页,如果当前是没有网络的状态,里面的按钮点击是没有反应的.只有当我们打开网络的情况下,点击按钮才能跳转页面,按照我们一般人写代码的逻辑应该是这个样子: ...

最新文章

  1. Nginx源代码分析 - 日志处理
  2. python 路径往上一层_Python常用模块之模块、包介绍和相关语法
  3. C程序中如何获取shell命令执行结果和返回值
  4. mysql 过程和函数_MySQL:存储过程和函数
  5. 在Access中执行SQL语句
  6. Python分析pdf简历
  7. oracle并行查询结果不唯一,Oracle数据库并行查询出错的解决方法
  8. AndroidStudio_A connection was leaked. Did you forget to close a response body?---Android原生开发工作笔记241
  9. DVR服务器如何装系统,车载监控dvr如何配置服务器
  10. Web应用程序系统的多用户权限控制设计及实现-总述【1】
  11. python打印换行符_在Python编程中不使用换行符进行打印
  12. 力扣-872 叶子相似的树
  13. MDK5 (Keil5)注册机破解
  14. 使用js-xlsx纯前端导出excel
  15. 智能化工厂数字化管理系统软件解决方案
  16. python_csv文件写入
  17. antd FormItem嵌套FormItem的适用场景
  18. mediawiki mysql_Windows下安装MediaWiki (iis+php+mysql+mediawiki)
  19. HDU 4313 最小生成树
  20. linux环境下随时照看服务器进程的ps和top命令

热门文章

  1. 赛效:WPS中绘制的表格如何添加边框?
  2. PS-第四天-矢量形状与路径
  3. 减轻学生过重课业负担的规定
  4. 怎样在视频画面指定位置编辑马赛克
  5. CentOS 8.5 NVIDIA驱动卸载
  6. LaTex支持中文的三种方式
  7. 获取json中数组的length
  8. 数据库SQLserver(课本)
  9. win10 屏幕切换鼠标手势桌面边缘快捷切换 ahk
  10. ueditor 编辑器增加css样式_百度编辑器(uedtior)怎么更换样式文件