Android注解基础用法

Android注解基础用法.png

注解的介绍

注解介绍

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

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

注解作用

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

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

Java 元注解

名字 描述
@Retention 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
@Documented 标记这些注解是否包含在用户文档中,即包含到 Javadoc 中去
@Target 标记这个注解的作用目标
@Inherited 标记这个注解是继承于哪个注解类
@Repeatable Java 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.png

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

核心原理:

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

实现步骤

1. 创建annotation库

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

image.png

在此库中创建一个注解BindView

image.png

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.png

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 注解知多少

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容