Android-自定义Annotation注解

现在市面上越来越多的基于注解的框架出现,它们可能或多或少的存在一些问题,但是确实是在某些方面大大简化了我们的开发周期和难度,也使得有些代码更加优雅。

为了进一步提升自己(装逼中。。。),就决定去了解一下到底什么是注解,它们有几种类型,它的实现原理,它是怎么起到简化代码的作用的。

我们以Android中权限的动态申请为示例,分别以运行时注解和编译时注解两种形式来实现。如有不足,还请指出!!!

基本功

注解仅仅是个标记,注解之所以起作用是对其解析后做了相应的处理。我们会通过编译时注解的处理和运行时注解的处理来进行说明。

元注解

1.@Target

表示该注解可以用在什么地方。可用的参数如下:

类型说明
CONSTRUCTOR构造器的声明
FIELD域声明(包括enum实例)
LOCAL_VARIABLE局部变量声明
METHOD方法声明
PACKAGE包声明
TYPE类、接口(包括注解类型)或enum声明
PARAMETER参数声明

2.@Retention

表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:

类型说明
SOURCE注解将被编译器丢弃(注解仅保留在源码中
CLASS注解在class文件中可用,但会被JVM丢弃(编译时注解,这是默认的)
RUNTIMEJVM将在运行期也保留注解,因此可以通过反射机制读取注解的信息(即运行时注解)

3.Documented

将此注解包含在Javadoc中

4.Inherited

默认情况下,父类的注解子类是不能继承的,不过可以通过该注解允许子类继承父类中的注解

注解元素

注解元素为注解中定义的元素,比如下例中的int类型的value:

@Target(ElementType.METHOD)
public @interface Test{
	int value();
}

注解元素可用的类型如下:

1.所有基本类型

2.String

3.Class

4.enum

5.Annotation(所以注解可以嵌套)

6.以上类型的数组

注意不允许使用任何包装类型,不过由于自动打包的存在,算不上什么限制

默认值限制

编译器对元素的默认值有着严格的限制:

1.不能有不确定的值,即元素要么具有默认值,要么在使用注解时提供元素的值

2.对于非基本类型的元素,无论是在源代码中声明,或是在注解接口中定义默认值,都不能以null作为其值

下面我们从PermissionGen的源码来分析基于运行时注解的权限处理。

运行时注解(PermissionGen)

运行时注解对应 @Retention 为 RUNTIME 的 Annotation。下面是Android中动态申请权限的示例代码:

//请求打电话的权限
public void requestPermission(){
 PermissionGen
              .with(MainActivity.this)
              .addRequestCode(100)
              .permissions(Manifest.permission.CALL_PHONE)
              .request();
}

//请求失败回调
@PermissionFail(requestCode = 100)
public void callFail(){
     Toast.makeText(this,"callFail",Toast.LENGTH_LONG).show();
}

//请求成功回调
@PermissionSuccess(requestCode = CALL)
public void callSuccess(){
     Toast.makeText(this,"callSuccess",Toast.LENGTH_LONG).show();
}

看来这一段代码,我不禁有几个疑问:
(1)PermissionSuccessPermissionFail这两个自定义的注解是怎么定义的
(2)它是怎么生效的

自定义注解

/**
 * function:运行时注解。权限申请失败
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PermissionFail {
    int requestCode();
}

/**
 * function:运行时注解。权限申请成功
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PermissionSuccess {
    int requestCode();
}

我们发现在注解类的定义中有三个地方需要注意,分别是:
(1)@Retention(RetentionPolicy.xxx):注解的保留策略,只有三种可选值,具体可以参考上面列出的表格
(2)@Target(ElementType.METHOD):当前注解作用域,我们的示例代码中表示当前注解作用于方法,其它可选值可参考上面的表格
(3)@interface:表明当前是一个自定义注解

我们发现自定义注解定义完之后,并没有其他的多余操作,那么由他标注的方法是怎么被调用的呢,我们下面关注PermissionGen这个类。

自定义注解是如何生效的

根据示例代码我们从PermissionGen的with函数开始入手:

private Object mObject;

public static PermissionGen with(Activity activity){
    return new PermissionGen(activity);
}

public static PermissionGen with(Fragment fragment){
    return new PermissionGen(fragment);
}

private PermissionGen(Object object){
    mObject = object;
}

这一步的代码是很简单的,就是调用了PermissionGen的构造函数,并保存了ActivityFragment的实例对象。有了PermissionGen对象,我们就可以继续下一步了,那就是addRequestCodepermissions

 public PermissionGen permissions(String... permissions){
     mPermissions = permissions;
     return this;
 }

 public PermissionGen addRequestCode(int requestCode){
     mRequestCode = requestCode;
     return this;
 }

就是为两个成员变量赋值,然后返回当前的PermissionGen对象。这些都是简单的,下面重点来了:

@TargetApi(value = Build.VERSION_CODES.M)
public void request(){
     requestPermissions(mObject,mRequestCode,mPermissions);
}

@TargetApi(value = Build.VERSION_CODES.M)
private static void requestPermissions(Object object, int requestCode, String[] permissions) {
   //判断当前的系统版本是否大于等于23,true表示大等于,反之为false
   if(!Utils.isOverMarshmallow()){
       doExecuteSuccess(object,requestCode);
       return ;
   }

   //获取没有通过的权限
   List<String> deniedPermissions =  Utils.findDeniedPermissions(getActivity(object),permissions);

   if(deniedPermissions.size() > 0){
       //请求权限
       if(object instanceof Activity){
           ((Activity)object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
       } else if(object instanceof Fragment){
           ((Fragment)object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
       } else {
           throw new IllegalArgumentException(object.getClass().getName() + " is not supported");
       }
   }else{
       doExecuteSuccess(object,requestCode);
   }
}

requestPermissions中,主要是做了以下判断:

(1)如果系统的API等级<=23或者要申请的权限没有被拒绝,则调用doExecuteSuccess

doExecuteSuccess中主要是通过Utils工具类来获取对应的Method,参数为Activity或Fragment实例、要寻找的注解类Class以及请求码,因为当前函数为成功的回调,所以注解类为PermissionSuccess。具体实现逻辑如下:

 public static <A extends Annotation> Method findMethodWithRequestCode(Class clazz, Class<A> annotation, int requestCode) {
		 //获取当前类的所有方法,不包括父类的 
        for(Method method : clazz.getDeclaredMethods()){
	        //检查传入的注解是否存在于当前元素
            if(method.isAnnotationPresent(annotation)){
                if(isEqualRequestCodeFromAnntation(method, annotation, requestCode)){
                    return method;
                }
            }
        }
        return null;
    }

思路还是比较清晰的,从Activity实例或Fragment实例中获取到注解为PermissionSuccess并且requestCode和我们传入一样的方法,然后利用返回的Method对象,通过invoke来调用,源码如下:

  private static void executeMethod(Object activity,Method executeMethod){
        if(executeMethod != null){
            try {
	            //判断是否有访问权限
                if (!executeMethod.isAccessible()) {
	                //没有访问权限时,关闭权限检查
                    executeMethod.setAccessible(true);
                }
                //对调特定函数
                executeMethod.invoke(activity,null);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

先是判断有没有权限,没有的话,则进行修改,然后通过invoke来调用,这样就完成了回调。

(2)要申请的权限被拒绝或部分拒绝,则将Object强转为Activity或Fragment,然后调用他们请求权限的API。我们同意或拒绝之后,下次再调用request,还是走到上面的requestPermissions逻辑,成功的已经说了,失败的话,其实本质上是一样的。

编译时注解(MPermission)

运行时注解对应 @Retention 为 CLASS 的 Annotation(同时也是默认的,在自定义注解上,不写@Retention 也默认是CLASS)。我们还是从自定义注解开始。

自定义注解

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface PermissionFail {
    int value();
}

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface PermissionSuccess {
    int value();
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ShowRequestPermissionRarionale {
    int value();
}

我们发现除了保留策略改为RetentionPolicy.CLASS,其它并没有什么不同,确实也没有什么不同,我们继续去看编译器。

自定义注解是如何生效的

编译时注解要生效,需要依赖于注解处理器(Annotation Processor)。一个注解的Annotation Processor,以Java代码(或者编译过的class)为输入,生成新的.java文件作为输出。这意味着我们可以生成新的Java代码!这些生成的Java代码是在新生成的.java文件中,新生成的.java文件会和普通的手动编写的Java源代码一样被javac编译。每一个注解处理器都继承自AbstractProcessor,它有4个函数比较重要:

//对一些工具进行初始化
public synchronized void init(ProcessingEnvironment processingEnv)
    
//你在这里定义你的注解处理器注册到哪些注解上,必须指定
//它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称
public Set<String> getSupportedAnnotationTypes()
    
//指定该注解处理器使用的JAVA版本,通常返回SourceVersion.latestSupported()
public SourceVersion getSupportedSourceVersion()
    
//真正生成java代码的地方
//annotations:请求处理的注解类型集合
//roundEnv:可以让你查询出包含特定注解的被注解元素,相当于“有关全局源码的上下文环境”
//如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们;
//如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们
public abstract boolean process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv)

具体我们来看看MPremission中的Processor,从init开始:

 @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        mMessager = env.getMessager();
        mFiler = env.getFiler();
        mElementUtils = env.getElementUtils();
    }

我们发现,该函数分别保存了三个成员变量,它们的作用是:打印辅助信息(错误、警告,其它提示)、对象辅助类(创建新的类)、元素相关信息的辅助类。在下面我们会用到,继续看支持的注解类型:

 @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotationsTypes = new LinkedHashSet<>();
        annotationsTypes.add(PermissionFail.class.getCanonicalName());
        annotationsTypes.add(PermissionSuccess.class.getCanonicalName());
        annotationsTypes.add(ShowRequestPermissionRarionale.class.getCanonicalName());
        return annotationsTypes;
    }

可以发现,我们支持了自定义的三个注解,版本的话依然是当前最新版。重点是process,在这里它主要是做了两件事:
(1)将注解添加到对应的缓存结合中
(2)代码动态生成代理类(重点)
代码如下:

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        mProxyMap.clear();
        mMessager.printMessage(Diagnostic.Kind.NOTE, "process...");
		//从roundEnv中获取到PermissionSuccess注解的元素,并且校验作用域是否是METHOD,满足添加添加到相应的缓存集合中
        if (!processAnnotations(roundEnv, PermissionSuccess.class)) {
            return false;
        }

       ...
		//遍历生成代理类
        for (String key : mProxyMap.keySet()) {
            ProxyInfo proxyInfo = mProxyMap.get(key);
            try {
                JavaFileObject jfo = mFiler.createSourceFile(
                        proxyInfo.getProxyClassFullName(),
                        proxyInfo.getTypeElement()
                );
                Writer writer = jfo.openWriter();
                writer.write(proxyInfo.generateJavaCode());
                writer.flush();
                writer.close();
            } catch (IOException e) {
                error(proxyInfo.getTypeElement(), "Unable to write injector for type %s: %s",
                        proxyInfo.getTypeElement(), e.getMessage());
            }
        }

        return true;
    }

至于动态生成代理类,大致的逻辑是根据原始类名(比如MainActivity为例)+特定后缀($ $PermissionProxy)代码创建代理类(即MainActivity$ $PermissionProxy),然后再代码生成特定函数,此处是对应三个注解的函数–grant、denied、rationale,大致格式如下:

// Generated code. Do not modify!
package learn.cp.inject;

import learn.cp.lib.*;

public class MainActivity$$PermissionProxy implements PermissionProxy<MainActivity> {
    @Override
    public void grant(MainActivity source, int requestCode) {
        switch (requestCode) {
            case 1:
                source.success();
                break;
            case 2:
                source.callSuccess();
                break;
        }
    }

    @Override
    public void denied(MainActivity source, int requestCode) {
        switch (requestCode) {
            case 1:
                source.fail();
                break;
            case 2:
                source.callFail();
                break;
        }
    }

    @Override
    public void rationale(MainActivity source, int requestCode) {
        switch (requestCode) {
        }
    }

    @Override
    public boolean needShowRationale(int requestCode) {
        switch (requestCode) {
        }
        return false;
    }

}

现在自定义注解有了,注解处理器也有了,剩下的就是调用了,具体的调用逻辑由我们的api模块来实现,此处具体为MPermissions类,当我们使用时会调用它的requestPermissions函数,在其内部会调用_requestPermissions,它的实现基本上和PermissionGen一模一样,所以就不再赘述了,我们去看一下,它授权成功的处理逻辑:

 private static void doExecuteSuccess(Object object,int requestCode){
        findPermissionProxy(object).grant(object,requestCode);
 }
    
 private static PermissionProxy findPermissionProxy(Object object){
        try {
            Class<?> clazz = object.getClass();
            Class injectorClazz = Class.forName(clazz.getName()+SUFFIX);
            return ((PermissionProxy) injectorClazz.newInstance());
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }catch (InstantiationException e){
            e.printStackTrace();
        }catch (IllegalAccessException e){
            e.printStackTrace();
        }

        throw new RuntimeException(String.format("can not find %s , something when compiler.", object.getClass().getSimpleName() + SUFFIX));
    }

大致逻辑如下:
(1)通过反射获取目标对象的Class对象,此处还是MainActivity
(2)再一次反射,获取MainActivity的代理对象(即MainActivity$ $PermissionProxy)的Class对象
(3)获取即MainActivity$ $PermissionProxy实例
(4)调用其grant
这样就走到上面代码生成的逻辑里了。然后回调source对应的函数,这里的source就是我们最开始传入的MainActivity,这样就完成了关联。

失败的逻辑也是类似的,大家可以自己去研究一下。

参考源码以及文章

鸿洋-Android 6.0 运行时权限处理完全解析
编译时注解-MPermissions
运行时注解-PermissionGen
注解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值