摘要:本文将带你了解Android源码之面向切面(AOP)编程实战,希望本文对大家学Android有所帮助。
AOP:面向切面编程(Aspect-Oriented Programming)。如果说,OOP如果是把问题划分到单个模块的话,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。 Aspect介绍篇:Android中的AOP编程 这里通过几个小例子,讲解在Android开发中,如何运用AOP的方式,进行全局切片管理,达到简洁优雅,一劳永逸的效果。
1、SingleClickAspect,防止View被连续点击出发多次事件
在使用aop之前,可以这样写了单独写个Click类(不优雅)或者RxBinding(不简洁):
<code> RxView.clicks(mButton) .throttleFirst(1, TimeUnit.SECONDS) .subscribe(new Action1<void>() { @Override public void call(Void v) { dosomething(); } }); </void></code> 现在,只需要一个注解,就可以轻松解决一切问题: <code><code>@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();//执行原方法 } } } } </code></code> 使用方法:标注在onClick上 <code><code><code> @SingleClick public 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()); } </code></code></code> 或者任何参数内有view可以做为参照系(view可以不是onClick的view,仅仅作为时间tag依附对象作为参照)的方法上,例如TRouter的页面跳转,防止连续快速点击重复跳页现象: <code><code><code><code>public class RouterHelper { @SingleClick // 防止连续点击 public static void go(String actionName, HashMap data, View view) { TRouter.go(actionName, data, view); } } </code></code></code></code> 2、CheckLoginAspect 拦截未登录用户的权限 不使用aop的情况,需要在每个方法体内判断用户登录状态,然后处理,现在,只需要一个注解轻松解决: <code><code><code><code> /** * 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() { @Override public void onClick(View view) { TRouter.go(C.LOGIN); } }).show(); return; } joinPoint.proceed();//执行原方法 } } </code></code></code></code> 使用方法: <code><code><code><code>public class AdvisePresenter extends AdviseContract.Presenter { @CheckLogin public 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("消息发送失败!")); } @CheckLogin public void initAdapterPresenter(AdapterPresenter mAdapterPresenter) { mAdapterPresenter .setRepository(ApiFactory::getMessageList) .setParam(C.INCLUDE, C.CREATER) .setParam(C.UID, SpUtil.getUser().objectId) .fetch(); } } </code></code></code></code> 从此只需要专注主要逻辑即可。 3、MemoryCacheAspect内存缓存切片 根据参数key缓存方法返回值,使我们纯净的Presenter(无参构造和无内部状态)达到全局缓存的单例复用效果,同样适用于其他需要缓存结果的方法: <code><code><code><code>/** * 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; } } </code></code></code></code> 看看Apt生成的Factory: <code><code><code><code>/** * @ 实例化工厂 此类由apt自动生成 */ public final class InstanceFactory { /** * @此方法由apt自动生成 */ @MemoryCache public 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(); } } } </code></code></code></code> 从此Presenter就是全局单例的可复用状态。 4、TimeLogAspect 自动打印方法的耗时 经常遇到需要log一个耗时操作究竟执行了多长时间,无aop时,需要每个方法体内添加代码,现在,只需要一个注解就可以一劳永逸: <code><code><code><code>/** * 根据注解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; } } </code></code></code></code> 使用方法: <code><code><code><code> @TimeLog public void onCreate() { super.onCreate(); mApp = this; SpUtil.init(this); store = new Stack<>(); registerActivityLifecycleCallbacks(new SwitchBackgroundCallbacks()); } </code></code></code></code> 从此方法耗时打印一个注解搞定! 5、SysPermissionAspect运行时权限申请 <code><code><code><code>/** * 申请系统权限切片,根据注解值申请所需运行权限 */ @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() { @Override public void onClick(DialogInterface dialog, int which) { MPermissionUtils.requestPermissionsResult(ac, 1, permission.value() , new MPermissionUtils.OnPermissionListener() { @Override public void onPermissionGranted() { try { joinPoint.proceed();//获得权限,执行原方法 } catch (Throwable e) { e.printStackTrace(); } } @Override public void onPermissionDenied() { MPermissionUtils.showTipsDialog(ac); } }); } }) .create() .show(); } } </code></code></code></code>
使用方法:
<code><code><code><code> @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); } </code></code></code></code>
动态权限申请一步搞定。
除了这些简单的示例,AOP还可以实现动态权限申请和其他用户权限管理,包括功能性切片和逻辑性切片,使日常开发更加简洁优雅,只需要关注重点业务逻辑,把其他的小事,都交给切片来自动处理吧。
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之Android频道
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号