对注解一直不是很了解。最近得空,学习一下自定义注解,写了一个小Demo。
编写一个替代findViewById的注解
1. 先定义一个注解
注解遵循: public @interface 注解名 {方法参数}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value() default -1;
}
2. 编写一个类,去实现注解内容
public class AnnotationView {
public static void getView(AppCompatActivity activity) {
//获取Activity中的全部成员变量
Field[] declaredFields = activity.getClass().getDeclaredFields();
//遍历成员变量
for (Field field : declaredFields) {
//判断是否存在注解
if (field.getAnnotations() != null) {
//判断注解内容是否是我们自定义的
if (field.isAnnotationPresent(ViewInject.class)) {
//如果是的话,设置该成员变量允许访问控制
field.setAccessible(true);
//获得注解实例
ViewInject ViewInject = field.getAnnotation(ViewInject.class);
try {
//为成员变量实现注解方法
field.set(activity, activity.findViewById(ViewInject.value()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
3. 使用该注解
public class MainActivity extends BaseActivity {
//使用注解
@ViewInject(R.id.text)
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//实现注解方法
AnnotationView.getView(this);
//点击测试
textView.setOnClickListener(v -> Log.d("MainActivity", this.textView.getText().toString()));
}
}
//输出 :com.example.annotation D/MainActivity: Hello World!
4. 问题
4.1 把实现注解方法放到BaseActivity中NPE了。
本想将 AnnotationView.getView() 放到BaseActivity中,这样每个Activity就都可以直接使用注解方法了,但发现会显示找不到View。
分析是因为先执行了注解,然后在onCreate中再执行了实现注解方法,此时布局还未加载,所以导致View未能执行findViewById方法。
以上分析为猜测,具体原因和解决办法还未确定。
4.2 一个解决办法
在寻找问题原因的时候,看到了另一个例子,作者将加载布局也做成了注解。此时便不再存在该问题。
这也变相印证了上面问题的原因猜测。
//布局注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LayoutInject {
int value() default -1;
}
//view注解类
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value() default -1;
}
//BaseActivity
public class InjectActivity extends AppCompatActivity {
private int mLayoutId = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
displayInjectLayout();
displayInjectView();
}
/**
* 解析注解view id
*/
private void displayInjectView() {
if (mLayoutId <=0){return ;}
Class<?> clazz = this.getClass();
Field[] fields = clazz.getDeclaredFields();//获得声明的成员变量
for (Field field : fields) {
//判断是否有注解
try {
if (field.getAnnotations() != null) {
if (field.isAnnotationPresent(ViewInject.class)) {//如果属于这个注解
//为这个控件设置属性
field.setAccessible(true);//允许修改反射属性
ViewInject inject = field.getAnnotation(ViewInject.class);
field.set(this, this.findViewById(inject.value()));
}
}
} catch (Exception e) {
// throw new InterruptedException("not found view id!");
}
}
}
/**
* 注解布局Layout id
*/
private void displayInjectLayout() {
Class<?> clazz = this.getClass();
if (clazz.getAnnotations() != null){
if (clazz.isAnnotationPresent(LayoutInject.class)){
LayoutInject inject = clazz.getAnnotation(LayoutInject.class);
mLayoutId = inject.value();
setContentView(mLayoutId);
}
}
}
}
//MainActivity
@LayoutInject(R.layout.activity_main)
public class MainActivity extends InjectActivity {
@ViewInject(R.id.text)
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 使用注解方式加载布局
// setContentView(R.layout.activity_main);
textView.setOnClickListener(v -> Log.d("MainActivity", this.textView.getText().toString()));
}
}
5. 参考引用
注解中涉及到的内容很多,我这里也仅是一个Demo,用于理解注解是如何实现的。
参考资料:
Android注解快速入门和实用解析
Android注解-看这篇文章就够了