最近在回顾注解和反射方面的知识。
之前在项目开发过程中,也曾经体验过ButterKnife的注解,想结合反射和注解自己写一个框架。结合着大牛的博客,和自己的理解。实现了Activity加载layout和view初始化的注解。
一.原理
package csu.lzw.reviewandroid.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by Allen_Binan on 2016/4/2.
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LBindView {
int id() default -1;
}
@Target用来表示这个注解修饰的对象是什么,FIELD表示是成员变量,例如Actvity当中的View控件。
@Retentiong表示这个注解是在什么级别保存信息。RUNTIME表示是运行时。
@interface是用于自定义注解的,它里面定义的方法的声明不能有参数,也不能抛出异常,并且方法的返回值被限制为简单类型、String、Class、emnus、@interface,和这些类型的数组。
注解@Target也是用来修饰注解的元注解,它有一个属性ElementType也是枚举类型,值为:ANNOTATION_TYPE,CONSTRUCTOR ,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER和TYPE,如@Target(ElementType.METHOD) 修饰的注解表示该注解只能用来修饰在方法上。
@RetentionRetention注解表示需要在什么级别保存该注释信息,用于描述注解的生命周期,它有一个RetentionPolicy类型的value,是一个枚举类型,它有以下的几个值:
1.用@Retention(RetentionPolicy.SOURCE)修饰的注解,指定注解只保留在源文件当中,编译成类文件后就把注解去掉;
2.用@Retention(RetentionPolicy.CLASS)修饰的注解,指定注解只保留在源文件和编译后的class 文件中,当jvm加载类时就把注解去掉;
3.用@Retention(RetentionPolicy.RUNTIME )修饰的注解,指定注解可以保留在jvm中,这样就可以使用反射获取信息了。
默认是RUNTIME,这样我们才能在运行的时候通过反射获取并做对应的逻辑处理。
同理可得,用于修饰Activity类,而加载layout布局的注解。
package csu.lzw.reviewandroid.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by Allen_Binan on 2016/4/2.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LBindContentView {
int layoutId() default -1;
}
最终,在Activity中实现的效果如下
package csu.lzw.reviewandroid;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Button;
import csu.lzw.reviewandroid.annotation.LBindContentView;
import csu.lzw.reviewandroid.annotation.LBindView;
import csu.lzw.reviewandroid.annotation.LViewBindUtils;
@LBindContentView(layoutId = R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
private static final String TAG="MainActivity";
@LBindView(id=R.id.toggleButton)
private Button tg;
@LBindView(id = R.id.toggleButton2)
private Button tg2;
@LBindView(id = R.id.toggleButton3)
private Button tg3;
@LBindView(id = R.id.toggleButton4)
private Button tg4;
@LBindView(id = R.id.toggleButton5)
private Button tg5;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LViewBindUtils.inject(this);
tg.setText("test");
tg2.setText("test2");
}
}
关键的实现在于这一步
LViewBindUtils.inject(this);
这一步里做了什么呢
对于初始化view的注解,那么获取this指向的Activity中的成员,检查成员中是否有自定义的注解,如果有的话,获取注解中声明的id,使用反射,获取Activity的findviewbyid方法,使用invode调用,并将返回值通过反射赋值给将要初始化的view.
对于加载Activity布局的view,类似地,检查activity类上有没有自定义的注解,如果有的话,通过反射,调用setcontentview.
package csu.lzw.reviewandroid.annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import android.app.Activity;
import android.util.Log;
public class LViewBindUtils
{
private static final String METHOD_SET_CONTENTVIEW = "setContentView";
private static final String METHOD_FIND_VIEW_BY_ID = "findViewById";
public static void inject(Activity activity)
{
injectContentView(activity);
injectViews(activity);
// injectEvents(activity);
}
/**
* 注入所有的view控件
*
* @param activity
*/
private static void injectViews(Activity activity)
{
Class<? extends Activity> clazz = activity.getClass();
Field[] fields = clazz.getDeclaredFields();
// 遍历所有成员变量
for (Field field : fields)
{
LBindView viewInjectAnnotation = field
.getAnnotation(LBindView.class);
if (viewInjectAnnotation != null)
{
int viewId = viewInjectAnnotation.id();
if (viewId != -1)
{
// 初始化View
try
{
Method method = clazz.getMethod(METHOD_FIND_VIEW_BY_ID,
int.class);
Object resView = method.invoke(activity, viewId);
field.setAccessible(true);
field.set(activity, resView);
} catch (Exception e)
{
e.printStackTrace();
}
}
}
}
}
/**
* 注入主布局文件
*
* @param activity
*/
private static void injectContentView(Activity activity)
{
Class<? extends Activity> clazz = activity.getClass();
// 查询类上是否存在ContentView注解
LBindContentView contentView = clazz.getAnnotation(LBindContentView.class);
if (contentView != null)// 存在
{
int contentViewLayoutId = contentView.layoutId();
try
{
Method method = clazz.getMethod(METHOD_SET_CONTENTVIEW,
int.class);
method.setAccessible(true);
method.invoke(activity, contentViewLayoutId);
} catch (Exception e)
{
e.printStackTrace();
}
}
}
}