一、概述
自从Java1.5后,其提供了一个非常强大的功能那就是注解。在普通的开发中可能不会自定义注解,甚至有些时候一个app开发下来完全不需要注解。但是想要在技术方面做一个纵深,自己封装框架,成为高级工程师,专家或者架构师,注解这块的知识是绕不开的。所以学习一下java中的注解对自己的内功修炼是非常有必要的。
现在主流的开源框架大多数都会用到注解,不同的框架注解的参与程度也不一样。像:EventBus、ButterKnife、Retrofit2等框架其注解的参与程度是比较深的,如果想要在注解方面有更快的提升,建议各位看官去看这些框架的源代码。
本节会先讲一下java&android中常用的元注解。然后通过两个小例子来系统的说一下注解的具体用法:自定义ButterKnife和获取类似retrofit中interfaceservice中的方法信息,来加强各位看官在注解方面上的理解。
二、元注解
网上将Java元注解的文章非常的多,本节只将在作者看来最重要的两个,@target和@retention。其他的大家自行学习。本节的重点在于实践,其理论只是相对较少。
1.@Target,其定义了注解可以在什么地方使用:如:类、属性、方法、参数、构造方法、局部变量、包声明等。
@Target注解有以下几种值可以使用,当然,多种值可以组合使用
a.TYPE:类、接口、枚举声明
b.FIELD:在属性上声明
c.METHOD:在方法上声明
d.PARAMETER:参数参数声明
e.CONSTRUCTOR:构造方法声明
f.ANNOTATION_TYPE:注解类型声明
g.LOCAL_VARIABLE:局部变量声明
h.PACKAGE:包声明
2@Retention,其定义了注解在那些阶段是可以用的,如:源码阶段、Class字节码阶段、运行时阶段(虚拟机)
@Retention有三种阶段可以选择:
a.RetentionPolicy.SOURCE,注解只存在源码中
b.RetentionPolicy.CLASS,注解可以一直存活到字节码阶段
c.RetentionPolicy.RUNTIME,注解可以一直存活,即使程序已经运行起来了
三、创建一个超级简单的ButterKnife
本例我们将创建一个非常简单的ButterKnife。利用bind方法进行绑定view并给view赋值,而且还可以选择性的设置view的点击事件。
类介绍:
1.BindView.java绑定view的注解
2.OnClick.java绑定点击事件的注解
3.ButterKnife.java注解工具
4.BindViewTestActivity.java测试类
BindView:
package com.yw.annotationlib; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 模仿ButterKnife实现View的绑定 * create by yangwei * on 2020-02-24 13:09 */ @Target(ElementType.FIELD)//标记该注解运行在字段上 @Retention(RetentionPolicy.RUNTIME)//标记该字段一直到程序运行时都有效 public @interface BindView { int value() default -1;//View的Id,如:R.id.btn }
OnClick.java
package com.yw.annotationlib; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * create by yangwei * on 2020-02-24 13:38 */ @Target(ElementType.METHOD)//标记注解应用在方法上 @Retention(RetentionPolicy.RUNTIME)//标记注解直到运行时都可以存活 public @interface OnClick { int value() default -1;//比较view的id值 }
ButterKnife.java
package com.yw.annotationlib; import android.app.Activity; import android.os.Build; import android.view.View; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import androidx.annotation.RequiresApi; /** * create by yangwei * on 2020-02-24 13:12 */ public class ButterKnife { /** * 绑定对象用注解标注的所有方法和属性 * * @param activity */ @RequiresApi(api = Build.VERSION_CODES.N) public static void bind(final Activity activity) { try { Class clazz = activity.getClass(); //获取class中的所有的注解 Method[] methods = clazz.getDeclaredMethods(); for (final Method method : methods) { final OnClick onClick = method.getDeclaredAnnotation(OnClick.class); if (onClick != null) { final View view = activity.findViewById(onClick.value()); method.setAccessible(true); //设置view的点击事件 view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { method.invoke(activity, view); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }); } } Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { BindView bindView = field.getDeclaredAnnotation(BindView.class); if (bindView != null) { View view = activity.findViewById(bindView.value()); field.setAccessible(true); field.set(activity, view); } } } catch (Exception e) { e.printStackTrace(); } } }
BindViewTestActivity.java
package com.yw.rxjava3demo; import android.app.Activity; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.yw.annotationlib.BindView; import com.yw.annotationlib.ButterKnife; import com.yw.annotationlib.OnClick; import com.yw.annotationlib.retrofit.FieldMap; import com.yw.annotationlib.retrofit.POST; import com.yw.annotationlib.retrofit.RetrofitAnnotationBind; import java.util.Arrays; import java.util.Map; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; /** * create by yangwei * on 2020-02-24 13:21 */ public class BindViewTestActivity extends Activity { @BindView(R.id.tv_name) TextView tv_name; @BindView(R.id.btn_click) Button btn_click; @RequiresApi(api = Build.VERSION_CODES.N) @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.bindviewtest_layout); ButterKnife.bind(this); tv_name.setText("我是杨洛峋,是一个小宝宝"); //测试参数注解和方法注解 RetrofitAnnotationBind.ServiceMethod serviceMethod = RetrofitAnnotationBind.bind(this); Log.e("获取注解中的请求方式:", serviceMethod.getMethod()); Log.e("获取请求方法中的值:", serviceMethod.getParams()); } @POST("POST") public void retrofitTest(@FieldMap Map<String,String> map){ } @OnClick(R.id.btn_click) public void onClick(View view) { switch (view.getId()) { case R.id.btn_click: Toast.makeText(BindViewTestActivity.this, "点击事件执行了", Toast.LENGTH_LONG).show(); break; } } }
xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="我是杨洛峋" /> <Button android:id="@+id/btn_click" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击我试试" /> </LinearLayout>
其最终的结果是TextView和Button按钮成功绑定上了findViewById。且Button按钮可以执行其注解标注的点击事件。
四、模仿retrofit获取注解方法参数以及方法注解参数上的值
本小节的主要内容是操作activty中的一个方法retrofitTest并的到这个方法注解的值和参数注解中的值并返回
FieldMap.java参数注解
POST.java方法注解
RetrofitAnnotationBind.java注解解析器
FieldMap.java
package com.yw.annotationlib.retrofit; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 模仿Retrofit的FieldMap参数注解 * create by yangwei * on 2020-02-24 14:03 */ @Target(ElementType.PARAMETER)//标记注解附着到参数上 @Retention(RetentionPolicy.RUNTIME)//标记注解一直到运行时都有效果 public @interface FieldMap { boolean encoded() default false; }
POST.java
package com.yw.annotationlib.retrofit; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * create by yangwei * on 2020-02-24 14:01 */ @Target(ElementType.METHOD)//在方法上运行 @Retention(RetentionPolicy.RUNTIME)//标记注解一直能存活到运行时 public @interface POST { String value() default "post_hello"; }
RetrofitAnnotationBind.java
package com.yw.annotationlib.retrofit; import android.os.Build; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import androidx.annotation.RequiresApi; /** * create by yangwei * on 2020-02-24 14:06 */ public class RetrofitAnnotationBind { /** * 拿到方法注解和参数注解 * * @param obj */ @RequiresApi(api = Build.VERSION_CODES.N) public static ServiceMethod bind(Object obj) { ServiceMethod serviceMethod = new ServiceMethod(); Class clazz = obj.getClass(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { POST post = method.getDeclaredAnnotation(POST.class); //说明此方法上有POST注解,拿到post上的值 if (post != null) { String psotValue = post.value(); serviceMethod.setMethod(psotValue); Annotation[][] annotations = method.getParameterAnnotations(); if (annotations != null) { StringBuffer sb = new StringBuffer(); for (Annotation[] paramsAnnotations : annotations) { for (Annotation annotation : paramsAnnotations) { if (annotation instanceof FieldMap) { FieldMap fieldMap = (FieldMap) annotation; sb.append(fieldMap.encoded()).append(","); } } } serviceMethod.setParams(sb.toString()); } } } return serviceMethod; } public static class ServiceMethod { private String method; private String params; public ServiceMethod() { } public ServiceMethod(String method, String params) { this.method = method; this.params = params; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getParams() { return params; } public void setParams(String params) { this.params = params; } } }
测试类中的测试方法,解析的就是这个方法
@POST("POST") public void retrofitTest(@FieldMap Map<String,String> map){ }
本例的最终结果会的到一个ServiceMethod类。此类中存储了POST注解的值和FieldMap注解中的值。
其打印结果为POST,false
//测试参数注解和方法注解 RetrofitAnnotationBind.ServiceMethod serviceMethod = RetrofitAnnotationBind.bind(this); Log.e("获取注解中的请求方式:", serviceMethod.getMethod()); Log.e("获取请求方法中的值:", serviceMethod.getParams());
总结:本节的内容讲到这里就结束了,希望大家能够对注解的使用方式有一点自己的体会。如果想要更深入的了解注解,建议大家去看下相关框架的源代码。
ps:一旦你深入了,你会发现注解其实法力还是很大的。