Android中的注解原来也可以这么好玩!

640?wx_fmt=jpeg


/   今日科技快讯   /


近日,美国一名高级官员透露,美国政府可能在2-4周内批准向美国公司颁发重新开始向华为供货的许可证。报道评论称,这一迹象表明,美国总统特朗普最近放松对华为限制的决定可能迅速推进。今年5月,美国商务部将华为列入“实体清单”,禁止美国公司在未获得许可证的情况下向华为提供产品和服务。


/   作者简介   /


本篇文章来自艾神一不小心的投稿,和大家分享了一个基于AOP的Android注解框架相关的内容,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。


艾神一不小心的博客地址:

https://juejin.im/user/5767d8b280dda4005fa0a38e


/   前言   /


当下Java后端的SpringBoot微服务框架大火,原因离不开注解的使用,其简单易配置的注解方式使得更多的社区为其编写适用于SpringBoot的框架,也就是注解逐渐取代了传统的xml配置方式。那么注解在Android中也同样的得到了升华,著名的框架有ButterKnife、 Dagger2、Retrofit等等。


今天带来一款Android中比较实用的注解框架AopArms,其用法简单,里面编写了Android开发中常用的一套注解,如日志、拦截(登录)、异步处理、缓存、SP、延迟操作、定时任务、重试机制、try-catch安全机制、过滤频繁点击等,后续还会有更多更强大的注解功能加入。


/   简介   /


AOP(Aspect-Oriented Programming,面向切面编程),可谓是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。AOP技术利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。


所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性。Android相关AOP常用的方法有 JNI HOOK 和 静态织入,本文以静态织入的方式之一AspectJ来进行讲解使用。(在编译期织入,切面直接以字节码的形式编译到目标字节码文件中,这要求使用特殊的 Java 编译器。)


/   实践   /


添加依赖


 
 
apply plugin: 'android-aspectjx'dependencies {    ...    compile 'org.aspectj:aspectjrt:1.8.9'}

dependencies {
    ...
    compile 'org.aspectj:aspectjrt:1.8.9'
}


项目跟目录的gradle脚本中加入


 
 
buildscript {    repositories {        mavenCentral()    }    dependencies {        //此处推荐该库,由沪江出品,可免去配置各种复杂任务        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'    }}
        mavenCentral()
    }
    dependencies {
        //此处推荐该库,由沪江出品,可免去配置各种复杂任务
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
    }
}


AOP注解与使用


 
 
@Aspect:声明切面,标记类@Pointcut(切点表达式):定义切点,标记方法@Before(切点表达式):前置通知,切点之前执行@Around(切点表达式):环绕通知,切点前后执行@After(切点表达式):后置通知,切点之后执行@AfterReturning(切点表达式):返回通知,切点方法返回结果之后执行@AfterThrowing(切点表达式):异常通知,切点抛出异常时执行
@Pointcut(切点表达式):定义切点,标记方法
@Before(切点表达式):前置通知,切点之前执行
@Around(切点表达式):环绕通知,切点前后执行
@After(切点表达式):后置通知,切点之后执行
@AfterReturning(切点表达式):返回通知,切点方法返回结果之后执行
@AfterThrowing(切点表达式):异常通知,切点抛出异常时执行


切点表达式的示例:


 
 
如:execution(@com.xxx.aop.TimeLog *.(..))切点表达式的组成:execution(@注解 访问权限 返回值的类型 包名.函数名(参数))com.xxx.aop.TimeLog *.(..))
切点表达式的组成:
execution(@注解 访问权限 返回值的类型 包名.函数名(参数))


下面简单列举两个简单例子:


 
 
@Before("execution(@com.xxx.aop.activity * *(..))")public void before(JoinPoint point) {    Log.i(TAG, "method excute before...");}匹配activity包下的所有方法,在方法执行前输出日志"execution(@com.xxx.aop.activity * *(..))")
public void before(JoinPoint point) {
    Log.i(TAG, "method excute before...");
}
匹配activity包下的所有方法,在方法执行前输出日志


 
 
@Around("execution(@cn.com.xxx.Async * *(..))")public void doAsyncMethod(ProceedingJoinPoint joinPoint) {    Log.i(TAG, "method excute before...");    joinPoint.proceed();    Log.i(TAG, "method excute after...");}匹配带有Async注解的方法,在方法执行前后分别输出日志"execution(@cn.com.xxx.Async * *(..))")
public void doAsyncMethod(ProceedingJoinPoint joinPoint) {
    Log.i(TAG, "method excute before...");
    joinPoint.proceed();
    Log.i(TAG, "method excute after...");
}
匹配带有Async注解的方法,在方法执行前后分别输出日志


 
 
@Around("execution(@cn.com.xxx.Async * *(..)) && @annotation(async)")public void doAsyncMethod(ProceedingJoinPoint joinPoint, Async async) {    Log.i(TAG, "value>>>>>"+async.value());    <!--to do somethings-->    joinPoint.proceed();    <!--to do somethings-->}//注意:@annotation(xxx)必须与下面的的参数值对应匹配带有Async注解的方法,并获取注解中的值,去做相应处理。"execution(@cn.com.xxx.Async * *(..)) && @annotation(async)")
public void doAsyncMethod(ProceedingJoinPoint joinPoint, Async async) {
    Log.i(TAG, "value>>>>>"+async.value());
    <!--to do somethings-->
    joinPoint.proceed();
    <!--to do somethings-->
}
//注意:@annotation(xxx)必须与下面的的参数值对应
匹配带有Async注解的方法,并获取注解中的值,去做相应处理。


实际应用举例


场景一、利用注解实现缓存处理


定义注解Cache如下:


 
 
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Cache {    String key(); //缓存的key    int expiry() default -1; // 过期时间,单位是秒}
@Target(ElementType.METHOD)
public @interface Cache {

    String key()//缓存的key

    int expiry() default -1// 过期时间,单位是秒
}


编写Aspect实现


 
 
@Aspectpublic class CacheAspect {    private static final String POINTCUT_METHOD = "execution(@cn.com.xxx.annotation.Cache * *(..))";    @Pointcut(POINTCUT_METHOD)    public void onCacheMethod() {    }    @Around("onCacheMethod() && @annotation(cache)")    public Object doCacheMethod(ProceedingJoinPoint joinPoint, Cache cache) throws Throwable {        //获取注解中的key        String key = cache.key();        //获取注解中的过期时间        int expiry = cache.expiry();        //执行当前注解的方法(放行)        Object result = joinPoint.proceed();        //方法执行后进行缓存(缓存对象必须是方法返回值)        ArmsCache aCache = ArmsCache.get(AopArms.getContext());        if (expiry>0) {            aCache.put(key,(Serializable)result,expiry);        } else {            aCache.put(key,(Serializable)result);        }        return result;    }}
public class CacheAspect {
    private static final String POINTCUT_METHOD = "execution(@cn.com.xxx.annotation.Cache * *(..))";

    @Pointcut(POINTCUT_METHOD)
    public void onCacheMethod() {
    }

    @Around("onCacheMethod() && @annotation(cache)")
    public Object doCacheMethod(ProceedingJoinPoint joinPoint, Cache cache) throws Throwable {
        //获取注解中的key
        String key = cache.key();
        //获取注解中的过期时间
        int expiry = cache.expiry();
        //执行当前注解的方法(放行)
        Object result = joinPoint.proceed();
        //方法执行后进行缓存(缓存对象必须是方法返回值)
        ArmsCache aCache = ArmsCache.get(AopArms.getContext());
        if (expiry>0) {
            aCache.put(key,(Serializable)result,expiry);
        } else {
            aCache.put(key,(Serializable)result);
        }
        return result;
    }
}


此处引用ACache该项目中的缓存实现,仅一个Acache文件,个人觉得还是比较好用的,当然你也可以自己去实现。 


测试


 
 
public static void main(String[] args) {    initData();    getUser();}//缓存数据@Cache(key = "userList")private ArrayList<User> initData() {    ArrayList<User> list = new ArrayList<>();    for (int i=0; i<5; i++){        User user = new User();        user.setName("艾神一不小心:"+i);        user.setPassword("密码:"+i);        list.add(user);    }    return list;}//获取缓存private void getUser() {    ArrayList<User> users = ArmsCache.get(this).getAsList("userList", User.class);    Log.e(TAG, "getUser: "+users);}
    initData();
    getUser();
}

//缓存数据
@Cache(key = "userList")
private ArrayList<User> initData() {
    ArrayList<User> list = new ArrayList<>();
    for (int i=0; i<5; i++){
        User user = new User();
        user.setName("艾神一不小心:"+i);
        user.setPassword("密码:"+i);
        list.add(user);
    }
    return list;
}

//获取缓存
private void getUser() {
    ArrayList<User> users = ArmsCache.get(this).getAsList("userList", User.class);
    Log.e(TAG, "getUser: "+users);
}


场景二、利用注解实现缓存移除


定义注解CacheEvict如下:


 
 
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface CacheEvict {    //需要移除的key    String key();    // 缓存的清除是否在方法之前执行, 默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除    boolean beforeInvocation() default false;    //是否清空所有缓存    boolean allEntries() default false;}
@Target(ElementType.METHOD)
public @interface CacheEvict {

    //需要移除的key
    String key();

    // 缓存的清除是否在方法之前执行, 默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
    boolean beforeInvocation() default false;

    //是否清空所有缓存
    boolean allEntries() default false;
}


编写Aspect实现


 
 
@Aspectpublic class CacheEvictAspect {    private static final String POINTCUT_METHOD = "execution(@cn.com.xxx.annotation.CacheEvict * *(..))";    //切点位置,所有的CacheEvict处    @Pointcut(POINTCUT_METHOD)    public void onCacheEvictMethod() {    }    //环绕处理,并拿到CacheEvict注解值    @Around("onCacheEvictMethod() && @annotation(cacheEvict)")    public Object doCacheEvictMethod(ProceedingJoinPoint joinPoint, CacheEvict cacheEvict) throws Throwable {        String key = cacheEvict.key();        boolean beforeInvocation = cacheEvict.beforeInvocation();        boolean allEntries = cacheEvict.allEntries();        ArmsCache aCache = ArmsCache.get(AopArms.getContext());        Object result = null;        if (allEntries){            //如果是全部清空,则key不需要有值            if (!TextUtils.isEmpty(key))                throw new IllegalArgumentException("Key cannot have value when cleaning all caches");            aCache.clear();        }        if (beforeInvocation){            //方法执行前,移除缓存            aCache.remove(key);            result = joinPoint.proceed();        }else {            //方法执行后,移除缓存,如果出现异常缓存就不会清除(推荐)            result = joinPoint.proceed();            aCache.remove(key);        }        return result;    }}
public class CacheEvictAspect {
    private static final String POINTCUT_METHOD = "execution(@cn.com.xxx.annotation.CacheEvict * *(..))";

    //切点位置,所有的CacheEvict处
    @Pointcut(POINTCUT_METHOD)
    public void onCacheEvictMethod() {
    }

    //环绕处理,并拿到CacheEvict注解值
    @Around("onCacheEvictMethod() && @annotation(cacheEvict)")
    public Object doCacheEvictMethod(ProceedingJoinPoint joinPoint, CacheEvict cacheEvict) throws Throwable {
        String key = cacheEvict.key();
        boolean beforeInvocation = cacheEvict.beforeInvocation();
        boolean allEntries = cacheEvict.allEntries();
        ArmsCache aCache = ArmsCache.get(AopArms.getContext());
        Object result = null;
        if (allEntries){
            //如果是全部清空,则key不需要有值
            if (!TextUtils.isEmpty(key))
                throw new IllegalArgumentException("Key cannot have value when cleaning all caches");
            aCache.clear();
        }
        if (beforeInvocation){
            //方法执行前,移除缓存
            aCache.remove(key);
            result = joinPoint.proceed();
        }else {
            //方法执行后,移除缓存,如果出现异常缓存就不会清除(推荐)
            result = joinPoint.proceed();
            aCache.remove(key);
        }
        return result;
    }
}


测试


 
 
public static void main(String[] args) {    removeUser();    getUser();}//移除缓存数据@CacheEvict(key = "userList")private void removeUser() {    Log.e(TAG, "removeUser: >>>>");}//获取缓存private void getUser() {    ArrayList<User> users = ArmsCache.get(this).getAsList("userList", User.class);    Log.e(TAG, "getUser: "+users);}
    removeUser();
    getUser();
}

//移除缓存数据
@CacheEvict(key = "userList")
private void removeUser() {
    Log.e(TAG, "removeUser: >>>>");
}

//获取缓存
private void getUser() {
    ArrayList<User> users = ArmsCache.get(this).getAsList("userList", User.class);
    Log.e(TAG, "getUser: "+users);
}


可以看到执行结果:


640?wx_fmt=png


/   AopArms的使用   /


引入方式


在主工程中添加依赖


 
 
//引入aspectjx插件apply plugin: 'android-aspectjx'dependencies {    ...    implementation 'cn.com.superLei:aop-arms:1.0.2'}
apply plugin: 'android-aspectjx'

dependencies {
    ...
    implementation 'cn.com.superLei:aop-arms:1.0.2'
}


项目跟目录的gradle脚本中加入


 
 
buildscript {    repositories {        mavenCentral()    }    dependencies {        //该库基于沪江aspect插件库        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'    }}
        mavenCentral()
    }
    dependencies {
        //该库基于沪江aspect插件库
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
    }
}


在Application中初始化


 
 
AopArms.init(this);


基本使用


缓存篇(可缓存任意类型)


 
 
1、插入缓存/**   * key:缓存的键   * expiry:缓存过期时间,单位s   * @return 缓存的值   */@Cache(key = "userList", expiry = 60 * 60 * 24)private ArrayList<User> initData() {    ArrayList<User> list = new ArrayList<>();    for (int i=0; i<5; i++){        User user = new User();        user.setName("艾神一不小心:"+i);        user.setPassword("密码:"+i);        list.add(user);    }    return list;}2、获取缓存private ArrayList<User> getUser() {    return ArmsCache.get(this).getAsList("userList", User.class);}3、移除缓存/**  * key:缓存的键  * beforeInvocation:缓存的清除是否在方法之前执行, 如果出现异常缓存就不会清除   默认false  * allEntries:是否清空所有缓存(与key互斥)  默认false  */@CacheEvict(key = "userList", beforeInvocation = true, allEntries = false)public void removeUser() {    Log.e(TAG, "removeUser: >>>>");}
/**
   * key:缓存的键
   * expiry:缓存过期时间,单位s
   * @return 缓存的值
   */

@Cache(key = "userList", expiry = 60 * 60 * 24)
private ArrayList<User> initData() {
    ArrayList<User> list = new ArrayList<>();
    for (int i=0; i<5; i++){
        User user = new User();
        user.setName("艾神一不小心:"+i);
        user.setPassword("密码:"+i);
        list.add(user);
    }
    return list;
}

2、获取缓存
private ArrayList<User> getUser() {
    return ArmsCache.get(this).getAsList("userList", User.class);
}

3、移除缓存
/**
  * key:缓存的键
  * beforeInvocation:缓存的清除是否在方法之前执行, 如果出现异常缓存就不会清除   默认false
  * allEntries:是否清空所有缓存(与key互斥)  默认false
  */

@CacheEvict(key = "userList", beforeInvocation = true, allEntries = false)
public void removeUser() {
    Log.e(TAG, "removeUser: >>>>");
}


640?wx_fmt=png


SharedPreferences篇(可保存对象)


 
 
1、保存key到sp@Prefs(key = "article")private Article initArticle() {    Article article = new Article();    article.author = "jerry";    article.title = "hello android";    article.createDate = "2019-05-31";    article.content = "this is a test demo";    return article;}2、从sp中移除key/** * key:sp的键 * allEntries:是否清空所有存储(与key互斥)  默认false */@PrefsEvict(key = "article", allEntries = false)public void removeArticle() {    Log.e(TAG, "removeArticle: >>>>");}3、通过key从sp中获取valuepublic void getArticle() {    Article article = ArmsPreference.get(this, "article", null);    Log.e(TAG, "getArticle: "+article);}    
@Prefs(key = "article")
private Article initArticle() {
    Article article = new Article();
    article.author = "jerry";
    article.title = "hello android";
    article.createDate = "2019-05-31";
    article.content = "this is a test demo";
    return article;
}

2、从sp中移除key
/**
 * key:sp的键
 * allEntries:是否清空所有存储(与key互斥)  默认false
 */

@PrefsEvict(key = "article", allEntries = false)
public void removeArticle() {
    Log.e(TAG, "removeArticle: >>>>");
}

3、通过key从sp中获取value
public void getArticle() 
{
    Article article = ArmsPreference.get(this"article"null);
    Log.e(TAG, "getArticle: "+article);
}    


异步篇


 
 
@Asyncpublic void asyn() {    Log.e(TAG, "useAync: "+Thread.currentThread().getName());}  
public void asyn() {
    Log.e(TAG, "useAync: "+Thread.currentThread().getName());
}  


try-catch安全机制篇


 
 
//自动帮你try-catch   允许你定义回调方法@Safe(callBack = "throwMethod")public void safe() {    String str = null;    str.toString();}//自定义回调方法(注意要和callBack的值保持一致)private void throwMethod(Throwable throwable){    Log.e(TAG, "throwMethod: >>>>>"+throwable.toString());}
@Safe(callBack = "throwMethod")
public void safe() {
    String str = null;
    str.toString();
}

//自定义回调方法(注意要和callBack的值保持一致)
private void throwMethod(Throwable throwable){
    Log.e(TAG, "throwMethod: >>>>>"+throwable.toString());
}


重试机制篇


 
 
/** * @param count 重试次数 * @param delay 每次重试的间隔 * @param asyn 是否异步执行 * @param retryCallback 自定义重试结果回调 * @return 当前方法是否执行成功 */@Retry(count = 3, delay = 1000, asyn = true, retryCallback = "retryCallback")public boolean retry() {    Log.e(TAG, "retryDo: >>>>>>"+Thread.currentThread().getName());    return false;}private void retryCallback(boolean result){    Log.e(TAG, "retryCallback: >>>>"+result);}
@Retry(count = 3, delay = 1000, asyn = true, retryCallback = "retryCallback")
public boolean retry() {
    Log.e(TAG, "retryDo: >>>>>>"+Thread.currentThread().getName());
    return false;
}

private void retryCallback(boolean result){
    Log.e(TAG, "retryCallback: >>>>"+result);
}


640?wx_fmt=png


定时任务篇


 
 
/** * @param interval 初始化延迟 * @param interval 时间间隔 * @param timeUnit 时间单位 * @param count 执行次数 * @param taskExpiredCallback 定时任务到期回调 */@Scheduled(interval = 1000L, count = 10, taskExpiredCallback = "taskExpiredCallback")public void scheduled() {    Log.e(TAG, "scheduled: >>>>");}private void taskExpiredCallback(){    Log.e(TAG, "taskExpiredCallback: >>>>");}
@Scheduled(interval = 1000L, count = 10, taskExpiredCallback = "taskExpiredCallback")
public void scheduled() {
    Log.e(TAG, "scheduled: >>>>");
}

private void taskExpiredCallback(){
    Log.e(TAG, "taskExpiredCallback: >>>>");
}


640?wx_fmt=png


延迟任务篇


 
 
//开启延迟任务(10s后执行该方法)@Delay(key = "test", delay = 10000L)public void delay() {    Log.e(TAG, "delay: >>>>>");}//移除延迟任务@DelayAway(key = "test")public void cancelDelay() {    Log.e(TAG, "cancelDelay: >>>>");}
@Delay(key = "test", delay = 10000L)
public void delay() {
    Log.e(TAG, "delay: >>>>>");
}

//移除延迟任务
@DelayAway(key = "test")
public void cancelDelay() {
    Log.e(TAG, "cancelDelay: >>>>");
}


过滤频繁点击


 
 
//value默认500ms@SingleClick(value = 2000L)private void onclick(){    Log.e(TAG, "onclick: >>>>");}
@SingleClick(value = 2000L)
private void onclick(){
    Log.e(TAG, "onclick: >>>>");
}


拦截篇(如登录)


 
 
1、在需要进行拦截的方法添加注解    @Intercept("login_intercept")    public void loginIntercept() {        Log.e(TAG, "intercept: 已登陆>>>>");    }2、(建议,统一处理)在Application中进行进行监听拦截回调public class MyApplication extends Application {    private static final String TAG = "MyApplication";    private static MyApplication mApplication;    @Override    public void onCreate() {        super.onCreate();        mApplication = this;        AopArms.init(this);        AopArms.setInterceptor(new Interceptor() {            @Override            public boolean intercept(String key, String methodName) throws Throwable {                Log.e(TAG, "intercept methodName:>>>>>"+methodName);                if ("login_intercept".equals(key)){                    String userId = ArmsPreference.get(mApplication, "userId", "");                    if (TextUtils.isEmpty(userId)){                        Toast.makeText(mApplication, "您还没有登录", Toast.LENGTH_SHORT).show();                        return true;//代表拦截                    }                }                return false;//放行            }        });    }}
    @Intercept("login_intercept")
    public void loginIntercept() {
        Log.e(TAG, "intercept: 已登陆>>>>");
    }
2、(建议,统一处理)在Application中进行进行监听拦截回调
public class MyApplication extends Application {

    private static final String TAG = "MyApplication";
    private static MyApplication mApplication;

    @Override
    public void onCreate() {
        super.onCreate();
        mApplication = this;
        AopArms.init(this);
        AopArms.setInterceptor(new Interceptor() {
            @Override
            public boolean intercept(String key, String methodName) throws Throwable {
                Log.e(TAG, "intercept methodName:>>>>>"+methodName);
                if ("login_intercept".equals(key)){
                    String userId = ArmsPreference.get(mApplication, "userId""");
                    if (TextUtils.isEmpty(userId)){
                        Toast.makeText(mApplication, "您还没有登录", Toast.LENGTH_SHORT).show();
                        return true;//代表拦截
                    }
                }
                return false;//放行
            }
        });
    }
}


以上是库的一些常用的基本用法,后续会添加更多的注解来简化Android开发,欢迎前来issues来提问或者提出你认为所需要的更多注解需求。项目地址为:

址:

https://github.com/AICareless/AopArms


推荐阅读:

测试也是Android开发的重要部分,单元测试和UI测试上手实践

Kotlin协程入门学习,看这一篇就足够了

一个App真的只需要一个Activity


欢迎关注我的公众号

学习技术或投稿


640.png?


640?wx_fmt=jpeg

长按上图,识别图中二维码即可关注


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值