Android面向切面(AOP)编程实战

来源:互联网 发布:淘宝美工将来有前途么 编辑:程序博客网 时间:2024/05/22 11:35

转载自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>() {                    @Override                    public void call(Void v) {                        dosomething();                    }                });

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

@Aspectpublic 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上

   @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());    }

或者任何参数内有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切片的方式在编译期间织入源代码中 * 功能:检查用户是否登陆,未登录则提示登录,不会执行下面的逻辑 */@Aspectpublic 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();//执行原方法    }}

使用方法:

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();    }}

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

3、MemoryCacheAspect内存缓存切片

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

/** * Created by baixiaokang on 16/10/24. * 根据MemoryCache注解自动添加缓存代理代码,通过aop切片的方式在编译期间织入源代码中 * 功能:缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。 */@Aspectpublic 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自动生成 */  @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();    }  }}

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

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

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

/** * 根据注解TimeLog自动添加打印方法耗代码,通过aop切片的方式在编译期间织入源代码中 * 功能:自动打印方法的耗时 */@Aspectpublic 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;    }}

使用方法:

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

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

5、SysPermissionAspect运行时权限申请

/** * 申请系统权限切片,根据注解值申请所需运行权限 */@Aspectpublic 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();    }}

使用方法:

   @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
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 娱乐圈御厨 御厨 御厨投喂手册 御厨传奇火锅 皇家御厨水果捞 御厨传奇代理 古穿未之星际御厨 星际第一女神之御厨驾到 参精御阳片 参精御阳片多少钱一盒 御芳可 御台场 大唐御史台 我姓夏 御蝶坊 御妍工坊丰胸精油 龙翔御书坊 肌御坊面膜 御品坊 御品皇三汁焖锅 御品国际 御品华府 御品翠轩 桃源居御品 海南桃源居御品房价 海南保亭桃源居御品 台湾御品轩 御品轩西安店 御品轩面包 御品轩蛋糕盒 御品轩 凤梨酥 御品轩 绿豆糕 御品轩店 御品轩高新 御品轩小寨店 御品轩地址 御品轩水果蛋糕 御品轩会员卡 御品轩蛋糕电话 御品轩介绍 御品轩高新店 御品轩大酒店