由于本人能力有限,文中若有错误之处,欢迎指正。
转载请注明出处://www.greatytc.com/p/f57044b8050c
上一篇 3分钟快速掌握注解(Annotation) 中讲述了注解的基础知识,以及自定义注解。本篇将继续讲解注解的实例应用。
注解解析
注解在使用中一般都需要解析,而解析又分为两种:
运行时刻解析
利用 Java 的反射机制
,在运行时动态获取注解信息,改变程序的运行状态。
优点:编码简单
缺点:反射带来的性能问题编译时刻解析
编译时刻注解解析依靠的是注解处理工具apt(Annotation Processor Tool)
,在编译时获取注解信息,动态生成源码或配置文件,改变程序运行状态。
优点:无反射带来的性能问题
缺点:编码复杂,使用相对较麻烦
本篇简文主要讲述运行时刻注解解析,文中将以Android
中常见的视图注入为例,也是早期的视图注入框架的原理。
简单视图注入实例
- 首先我们需要自定义注解
InjectView
@Retention(RetentionPolicy.RUNTIME) // 在运行时刻能够获取到注解信息
@Target(ElementType.FIELD) // 指定注解作用在属性字段上
public @interface InjectView {
@IdRes // 指定返回值是一个id资源,IDE会帮我们检查,该注解存在于 com.android.support:support-annotations
int value();
}
- 写一个
InjectUtils
作为我们视图注入的入口,在这里完成注入操作
public class InjectUtils {
public static void inject(Activity activity){
Class<? extends Activity> actClass = activity.getClass();
// 注入View
injectView(activity, actClass);
}
private static void injectView(Activity activity, Class<?> actClass) {
if(actClass == null || activity == null){
return;
}
// 递归查找父类中被注解的字段
injectView(activity, actClass.getSuperclass());
// 获取Activity中所有的字段,并进行遍历
Field[] fields = actClass.getDeclaredFields();
if(fields == null || fields.length <= 0){
return;
}
for (Field field : fields) {
// 获取字段上标记的注解对象
InjectView injectView = field.getAnnotation(InjectView.class);
if(injectView == null){
continue;
}
View view = activity.findViewById(injectView.value());
try {
if(view != null){
// 给字段赋值,完成注入操作
field.setAccessible(true);
field.set(activity, view);
} else {
throw new InjectException("Invalid @InjectView for "
+ actClass.getSimpleName() + "." + field.getName());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
- 使用示例
public class MainActivity extends BaseActivity {
@InjectView(R.id.btn_ok)
private Button btnOk; // 可以是private修饰
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 在 setContentView 之后调用完成注入
InjectUtils.inject(this);
btnOk.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tvHello.setText("ok"); // tvHello可以声明在父类中
}
});
}
}
扩展
既然知道了注入原理,那我们除了可以注入Activity
中的视图,当然也可以注入Fragment、Holder...
中的视图。甚至也可以像ButterKnife
一样注入各种资源、以及事件处理。下面以setContentView()
为例,为Activity
注入布局资源。其它可以自行尝试扩展。
- 在以上基础上增加自定义注解
ContentView
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
@LayoutRes
int value();
}
- 修改
InjectUtils
public class InjectUtils {
public static void inject(Activity activity){
Class<? extends Activity> actClass = activity.getClass();
// 注入页面布局,注意一定在 injectView()之前调用!!!!
injectContentView(activity, actClass);
// 注入View
injectView(activity, actClass);
}
private static void injectContentView(Activity activity, Class<? extends Activity> actClass) {
ContentView contentView = actClass.getAnnotation(ContentView.class);
if(contentView == null){
return;
}
try {
Method setContentViewMethod = actClass.getMethod("setContentView", int.class);
setContentViewMethod.invoke(activity, contentView.value());
} catch (Exception e) {
e.printStackTrace();
}
}
private static void injectView(Activity activity, Class<?> actClass) {
// ... 保持不变
}
}
- 使用示例
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@InjectView(R.id.btn_ok)
Button btnOk;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.inject(this); // 不需要再调用setContentView方法
}
}
写在最后
这里只是拿视图注入作为切入点,讲解注解在开发中的实例应用,以及剖析了早期视图注入框架的原理。仅作为学习用途,在实际开发中请使用更加稳定、更加高效的开源库。