Android注解基础用法

image-20220620155008563

注解的介绍

注解介绍

注解是在 Java SE5 引入进来的。

注解又称为标注,用于为代码提供元数据。 作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。可以作用在类、方法、变量、参数和包等上。 你可以通俗的理解成“标签”,这个标签可以标记类、方法、变量、参数和包。

注解作用

注解单独存在时是没有意义的,需要与注解处理器一起,才能起作用

  1. 注解+APT,用于生成一些Java 文件
  2. 注解+代码埋点,用户做日志手机统计等
  3. 注解+反射,用于为View 组件 增加事件监听等

Java 元注解

名字描述
@Retention标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
@Documented标记这些注解是否包含在用户文档中,即包含到 Javadoc 中去
@Target标记这个注解的作用目标
@Inherited标记这个注解是继承于哪个注解类
@RepeatableJava 8 开始支持,标识某注解可以在同一个声明上使用多次

@Retention
表示注解保留时间长短。可选的参数值在枚举类型 java.lang.annotation.RetentionPolicy 中,取值为:

  • RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视,不会写入 class 文件;
  • RetentionPolicy.CLASS:注解只被保留到编译进行的时候,会写入 class 文件,它并不会被加载到 JVM 中;
  • RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以反射获取到它们。

@Target
用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。 可能的值在枚举类 java.lang.annotation.ElementType 中,包括:

  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上;

  • ElementType.FIELD:允许作用在属性字段上;

  • ElementType.METHOD:允许作用在方法上;

  • ElementType.PARAMETER:允许作用在方法参数上;

  • ElementType.CONSTRUCTOR:允许作用在构造器上;

  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上;

  • ElementType.ANNOTATION_TYPE:允许作用在注解上;

  • ElementType.PACKAGE:允许作用在包上。

@Target 注解的参数也可以接收一个数组,表示可以作用在多种目标类型上,如: @Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})

注解+APT 实战:简单实现ButterKnife框架

APT是注解处理器,是一种用来处理注解的工具。JVM会在编译期就运行APT去扫描处理代码中的注解。我们在拿到对应的注解时,可以生成我们需要的模板代码

image-20220620143459616

在获取View空间的时候,我们往往需要通过findViewById来拿到控件的示例,会比较繁琐,我们可以通过APT的核心原理,来生成findViewById的模板代码,这样就不需要在每个Activity中都通过这个方式去执行。

核心原理:

  1. 声明控件变量,并添加BindView注解,绑定控件对应的ID
  2. 在apt注解处理器,处理BindView注解,生成对应的模板代码Java文件(用到Writer去生成文件)
  3. 调用模板代码,实现findViewById 功能

实现步骤

1. 创建annotation库

创建一个java-library库,命名为:annotation。这个库用来存放自定义注解。

image-20220620144816082

在此库中创建一个注解BindView

image-20220620144922691

BindView代码如下:

@Retention(RetentionPolicy.CLASS)//编译时起效
@Target(ElementType.FIELD)//针对的是属性
public @interface BindView {
    int value(); //定义输入参数为整形,例如:@BindView(R.id.xxx)
}
2. 创建annotation_compiler库

此库是APT的核心处理库,用来在编译时生成处模板代码

注意: 这也是一个java-library库,继承的 AbstractProcessor 在 javax包下才能引入

在此之前,我们需要在App 模块中定义一个IBinder接口,让即将生成的模板类继承此接口

public interface IBinder<T> {
    void bind(T target);
}

这个接口的作用是,当我们通过反射的方式生成模板类实例,直接调用bind方法来实现View绑定

String name = activity.getClass().getName() + "_ViewBinding";//这个是模板类的类名
try {
    Class<?> aClass = Class.forName(name);
    IBinder iBinder = (IBinder) aClass.newInstance();//生成模板类实列
    iBinder.bind(activity);//实现findViewbyId功能
} catch (Exception e) {
    e.printStackTrace();
}

注意:记住这个IBinder所在包目录,下面生成模板时用到

创建完成后,在build.gradle中,引入如下:

plugins {
    id 'java-library'
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}

dependencies {

    compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7' //Google 注解处理器

    implementation project(path: ':annotation') //引用刚刚创建的注解
}

定义处理器代码 BindViewProcessor.java ,这个处理器的目的是生成如下模板代码

package com.example.annotation;
import com.example.annotation.IBinder;
public class MainActivity_ViewBinding implements IBinder<com.example.annotation.MainActivity> {
    @Override
    public void bind(com.example.annotation.MainActivity target) {
        target.mTvText = (android.widget.TextView) target.findViewById(2131231186);
    }
}

以上的 IBinder就是我们在App中创建的,在com.example.annotation包下面,我们需要一点点将以上目标代码拼装在一起

所有代码处理,都在 process方法中实现

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

    private static final String TAG = BindViewProcessor.class.getSimpleName();

    //1.支持的版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    //2.能用来处理哪些注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    //3.定义一个用来生成APT目录下面的文件的对象
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        //filter 用于后续写文件
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //打印测试
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "===>" + set);
        //获取APP中所有用到了BindView注解的对象
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);

        // TypeElement -->类
        // ExecutableElement --> 方法
        // VariableElement--> 属性
        //开始对elementsAnnotatedWith进行分类:我们可能很多activity中都定义有 BindView注解,用activity作为key,List作为注解列表
        Map<String, List<VariableElement>> map = new HashMap<>();
        for (Element element : elementsAnnotatedWith) {
            VariableElement variableElement = (VariableElement) element;
            //例如MainActivity
            String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
            Class aClass = variableElement.getEnclosingElement().getClass();
            List<VariableElement> variableElements = map.get(activityName);
            if (variableElements == null) {
                variableElements = new ArrayList<>();
                map.put(activityName, variableElements); //Activity跟注解列表是一对多的关系
            }
            variableElements.add(variableElement);
        }

        //开始遍历map, 生成每个activity对应的模板代码
        if (map.size() > 0) {
            Writer writer = null;
            Iterator<String> iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
                String activityName = iterator.next();
                //对应Activity下面的注解列表
                List<VariableElement> variableElements = map.get(activityName);
                //获取Activity所在包名
                TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
                String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
                try {
                    //文件名:MainActivity_ViewBinding
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
                    writer = sourceFile.openWriter();
                    //        package com.example.annotation;
                    writer.write("package " + packageName + ";\n");
                    //        import com.example.annotation.IBinder;
                    writer.write("import " + packageName + ".IBinder;\n");
                    //        public class MainActivity_ViewBinding implements IBinder<com.example.annotation.MainActivity> {
                    writer.write("public class " + activityName + "_ViewBinding implements IBinder<" +
                            packageName + "." + activityName + ">{\n");
                    //            @Overrid
                    //            public void bind(com.example.annotation.MainActivity target) {
                    writer.write(" @Override\n" +
                            " public void bind(" + packageName + "." + activityName + " target){");

                    // target.mTvText = (android.widget.TextView) target.findViewById(2131231186);
                    for (VariableElement variableElement : variableElements) { //这里可能有多个注解的View控件,因此需要循环遍历
                        //得到名字
                        String variableName = variableElement.getSimpleName().toString();
                        //得到ID
                        int id = variableElement.getAnnotation(BindView.class).value();
                        //得到类型
                        TypeMirror typeMirror = variableElement.asType();
                        writer.write("target." + variableName + "=(" + typeMirror + ")target.findViewById(" + id + ");\n");
                    }

                    writer.write("\n}}");

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (writer != null) {
                        try {
                            writer.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return false;
    }
}

创建完成处理器后,执行build方法,会生成模板代码:

image-20220620153708121

3. 创建辅助类,完成一键绑定

转到App模块,App的build.gradle中做如下依赖:

implementation project(path: ':annotation') //注解
annotationProcessor  project(path: ':annotation_compiler') //注解处理器

创建MyButterknife辅助类,其中IBinder是我们第2步骤中定义的接口,所有模板类都会继承此接口

public class MyButterknife {
    public static void bind(Activity activity) {
        String name = activity.getClass().getName() + "_ViewBinding";
        try {
            Class<?> aClass = Class.forName(name);
            IBinder iBinder = (IBinder) aClass.newInstance(); //通过反射,生成模板类的实例
            iBinder.bind(activity); //调用这个方法,会去执行对应模板类的bind方法,实现findViewById的功能
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在MainActivity中实现代码如下:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tvText)
    TextView mTvText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyButterknife.bind(this); //一键实现findViewById功能,
        mTvText.setText("Test"); 
    }
}

以上代码已上传 GitHub

参考

Android 注解知多少

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值