当下Java后端的SpringBoot微服务框架大火,缘由离不开注解的使用,其简单易配置的注解方式使得更多的社区为其编写适用于SpringBoot的框架,也就是注解逐渐取代了传统的xml配置方式。那么注解在Android中也一样的获得了升华,著名的框架有ButterKnife、 Dagger二、Retrofit等等。今天带来一款Android中比较实用的注解框架AopArms,其用法简单,里面编写了Android开发中经常使用的一套注解,如日志、拦截(登陆)、异步处理、缓存、SP、延迟操做、定时任务、重试机制、try-catch安全机制、过滤频繁点击等,后续还会有更多更强大的注解功能加入。android
AOP(Aspect-Oriented Programming,面向切面编程),可谓是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。AOP技术利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减小系统的重复代码,下降 模块间的耦合度,并有利于将来的可操做性和可维护性。Android相关AOP经常使用的方法有 JNI HOOK 和 静态织入,本文以静态织入的方式之一AspectJ来进行讲解使用。(在编译期织入,切面直接以字节码的形式编译到目标字节码文件中,这要求使用特殊的 Java 编译器。)git
apply plugin: 'android-aspectjx'
dependencies {
...
compile 'org.aspectj:aspectjrt:1.8.9'
}
复制代码
项目跟目录的gradle脚本中加入github
buildscript {
repositories {
mavenCentral()
}
dependencies {
//此处推荐该库,由沪江出品,可免去配置各类复杂任务
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
}
}
复制代码
@Aspect:声明切面,标记类
@Pointcut(切点表达式):定义切点,标记方法
@Before(切点表达式):前置通知,切点以前执行
@Around(切点表达式):环绕通知,切点先后执行
@After(切点表达式):后置通知,切点以后执行
@AfterReturning(切点表达式):返回通知,切点方法返回结果以后执行
@AfterThrowing(切点表达式):异常通知,切点抛出异常时执行
复制代码
切点表达式的示例:编程
如:execution(@com.xxx.aop.TimeLog *.(..))
切点表达式的组成:
execution(@注解 访问权限 返回值的类型 包名.函数名(参数))
复制代码
下面简单列举两个简单例子:json
@Before("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注解的方法,在方法执行先后分别输出日志
复制代码
@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注解的方法,并获取注解中的值,去作相应处理。
复制代码
一、定义注解Cache以下:后端
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cache {
String key(); //缓存的key
int expiry() default -1; // 过时时间,单位是秒
}
复制代码
二、编写Aspect实现缓存
@Aspect
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);
}
复制代码
一、定义注解CacheEvict以下:bash
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheEvict {
//须要移除的key
String key();
// 缓存的清除是否在方法以前执行, 默认表明缓存清除操做是在方法执行以后执行;若是出现异常缓存就不会清除
boolean beforeInvocation() default false;
//是否清空全部缓存
boolean allEntries() default false;
}
复制代码
二、编写Aspect实现app
@Aspect
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);
}
复制代码
能够看到执行结果:
一、在主工程中添加依赖
//引入aspectjx插件
apply plugin: 'android-aspectjx'
dependencies {
...
implementation 'cn.com.superLei:aop-arms:1.0.3'
}
复制代码
二、项目跟目录的gradle脚本中加入
buildscript {
repositories {
mavenCentral()
}
dependencies {
//该库基于沪江aspect插件库
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
}
}
复制代码
三、在Application中初始化
AopArms.init(this);
复制代码
一、缓存篇(可缓存任意类型)
一、插入缓存
/**
* 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;
}
二、获取缓存
private ArrayList<User> getUser() {
return ArmsCache.get(this).getAsList("userList", User.class);
}
三、移除缓存
/**
* key:缓存的键
* beforeInvocation:缓存的清除是否在方法以前执行, 若是出现异常缓存就不会清除 默认false
* allEntries:是否清空全部缓存(与key互斥) 默认false
*/
@CacheEvict(key = "userList", beforeInvocation = true, allEntries = false)
public void removeUser() {
Log.e(TAG, "removeUser: >>>>");
}
复制代码
一、保存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;
}
二、从sp中移除key
/**
* key:sp的键
* allEntries:是否清空全部存储(与key互斥) 默认false
*/
@PrefsEvict(key = "article", allEntries = false)
public void removeArticle() {
Log.e(TAG, "removeArticle: >>>>");
}
三、经过key从sp中获取value
public void getArticle() {
Article article = ArmsPreference.get(this, "article", null);
Log.e(TAG, "getArticle: "+article);
}
复制代码
三、异步篇
@Async
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());
}
复制代码
五、重试机制篇
/**
* @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);
}
复制代码
六、定时任务篇
/**
* @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: >>>>");
}
复制代码
七、延迟任务篇
//开启延迟任务(10s后执行该方法)
@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: >>>>");
}
复制代码
九、拦截篇(如登陆)
一、在须要进行拦截的方法添加注解
@Intercept("login_intercept")
public void loginIntercept() {
Log.e(TAG, "intercept: 已登录>>>>");
}
二、(建议,统一处理)在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;//放行
}
});
}
}
复制代码
十、动态受权篇
一、开启请求权限
/**
* @param value 权限值
* @param rationale 拒绝后的下一次提示(开启后,拒绝后,下一次会先提示该权限申请提示语)
* @param requestCode 权限请求码标识
*/
@Permission(value = {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, rationale = "为了更好的体验,请打开相关权限")
public void permission(View view) {
Log.e(TAG, "permission: 权限已打开");
}
二、请求拒绝注解回调
@PermissionDenied
public void permissionDenied(int requestCode, List<String> denyList){
Log.e(TAG, "permissionDenied: "+requestCode);
Log.e(TAG, "permissionDenied>>>: "+denyList.toString());
}
三、请求拒绝且不在提示注解回调
@PermissionNoAskDenied
public void permissionNoAskDenied(int requestCode, List<String> denyNoAskList){
Log.e(TAG, "permissionNoAskDenied: "+requestCode);
Log.e(TAG, "permissionNoAskDenied>>>: "+denyNoAskList.toString());
//前往设置页面打开权限
AopPermissionUtils.showGoSetting(this, "为了更好的体验,建议前往设置页面打开权限");
}
复制代码
十一、测试方法耗时篇
@TimeLog
public void getUser(View view) {
ArrayList<User> users = ArmsCache.get(this).getAsList("userList", User.class);
Log.e(TAG, "getUser: " + users);
}
复制代码
以上是库的一些经常使用的基本用法,后续会添加更多的注解来简化Android开发,欢迎前来issues来提问或者提出你认为所须要的更多注解需求。
GitHub地址:AopArms