1.什么是注解开发?
控制反转(Inversion of Control):减少大量重复代码的书写。如ButterKnife,xUtils...
2.效果预览
3.怎样进行注解开发(属性注解开发为例)?
3.1 创建Annotation
//注解使用时所在的地方 ElementType.FIELD -> 方法上面 ElementType.METHOD -> 属性上面ElementType.TYPE -> 类上面
@Target(ElementType.FIELD)
//检查的时间 RetentionPolicy.SOURCE -> 编码时(如override) RetentionPolicy.RUNTIME -> 运行时 RetentionPolicy.CLASS -> 编译时
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewById {
//@ViewById(R.id.tv_test) 当注解的属性只有一个时,可以命名为 value,这样在使用时可以使用快捷方式 – 直接传入值,而不是声明属性名
int value();
}
3.2 创建类实现注解内部逻辑
public class ViewUtils {
public static void inject(Activity activity) {
//传两个相同的参数?意思不一样哦
injectField(activity, activity);
}
}
/**
* 属性注解
* @param activity 用于寻找目标控件
* @param object 用于反射的类
*/
private static void injectField(Activity activity, Object object) {
//1.找到所有的属性
Field[] fields = object.getClass().getDeclaredFields();
//2.找到ViewById注解所在的属性
for (Field field : fields) {
ViewById fieldAnnotation = field.getAnnotation(ViewById.class);
//如果属性存在
if (fieldAnnotation != null) {
//3.通过注解属性中的value值找到对应的控件
int resId = fieldAnnotation.value();
View view = activity.findViewById(resId);
//如果id对应的控件存在
if (view != null) {
//可以调用属性的私有方法
field.setAccessible(true);
try {
//4.调用field的set方法重新赋值
field.set(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
3.3 在activity中使用
@ViewById(R.id.tv_test)
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
}
textView.setText("woochen123");
4.和效果图不一样?那在补充一点吧
@OnClick注解核心代码:
/**
* 点击事件注解
* @param activity
* @param object
*/
private static void injectMethod(Activity activity, Object object) {
//1.找到所在类所有的方法
Method[] methods = object.getClass().getDeclaredMethods();
//2.找到含有注解Onclick的方法
for (Method method : methods) {
OnClick methodAnnotation = method.getAnnotation(OnClick.class);
if(methodAnnotation != null){
//如果方法存在
//3.拿到其中的value值,并找到相应的控件
int[] resIds = methodAnnotation.value();
for (int resId : resIds) {
View view = activity.findViewById(resId);
if(view != null){
//如果id对应的控件存在,调用onClickListener
view.setOnClickListener(new DeclaredClickListener(object,method));
}
}
}
}
}
/**
* 自定义点击事件监听器
*/
private static class DeclaredClickListener implements View.OnClickListener {
private Method mMethod;
private Object mObject;
public DeclaredClickListener(Object object, Method method) {
mMethod = method;
mObject = object;
}
@Override
public void onClick(View v) {
try {
//可以调用私有方法
mMethod.setAccessible(true);
//默认调用一个参数
mMethod.invoke(mObject,v);
} catch (Exception e) {
try {
//如果抛出异常调用无参数的方法
mMethod.invoke(mObject,null);
} catch (Exception e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
5.知识点补充
5.1元注解:修饰其他的注解
@Documented:让注解信息出现在 document 中
@Retention : 指出注解如何存储,支持以下三种参数
- RetentionPolicy.SOURCE : 注解只保留在源码中,编译时会忽略
- RetentionPolicy.CLASS : 更高一级,编译时被编译器保留,但是运行时会被 JVM 忽略
- RetentionPolicy.RUNTIME : 最高级,运行时会被保留,可以被运行时访问
@Target :指出注解作用于(修饰)什么对象,支持以下几种参数 - ElementType.TYPE : 作用于任何类、接口、枚举
- ElementType.FIELD : 作用于一个域或者属性
- ElementType.METHOD : 作用于一个方法
- ElementType.PARAMTER : 作用于参数
- ElementType.CONSTRUCTOR : 作用于构造函数
- ElementType.LOCAL_VARIABLE : 作用于本地变量
- ElementType. ANNOTATION_TYPE : 作用于注解
- ElementType.PACKAGE : 作用于包
@Inherited :当前注解是否可以继承
5.2注解的特点:
- 形式上和接口很像,都是定义方法,但是区别在于注解中定义的方法实质上是属性,而返回值是指属性的类型
- 可以给定义的注解方法赋上默认的值
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Author {
String name() default "woochen123";
String date();
}
5.3注解处理器的类型
运行时处理器
编译时处理器
6.再说一句
本例中的代码,是从项目中简化后提出的,可能有些地方显得多余,可能有些逻辑不够完善。意思应该是表达出来了(伸手党就没办法咯-_-!)
7.小结
本例的原理是利用反射+运行时检查来实现的注解,原理与xutils的view注入相同。还有一种是编译时检查,ButterKnife是比较有代表性的, 它的实现是通过字节流在本地生成相关文件。前者由于用到反射,会在一定程度上消耗性能(相比视图的渲染,就是微不足道了);后者会将生成的文件打包进apk中,在一定程度上增加apk的体积(相比巨大的三方库的引入,这也算不上什么啦)。