前面两篇文章讲解了注解的基础知识和运行时注解的解析方法。下面我们用学到的知识来手动写一个属于自己的注解框架。
在写自己的注解框架前,让我们先来看下一些好的框架,相信大家对xutils应该不会太陌生。xutils主要包括网络,数据库,IOC注入,网络图片使用,那么我们这里主要看看xutils3.0的IOC注解:https://github.com/wyouflf/xUtils3
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.tv)
private TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
x.view().inject(this);mTv.setText("MMMMMM");/** * 1. 方法必须私有限定, * 2. 方法参数形式必须和type对应的Listener接口一致. * 3. 注解参数value支持数组: value={id1, id2, id3} * 4. 其它参数说明见{@link org.xutils.view.annotation.Event}类的说明. **/ @Event(value = R.id.tv,
}type = View.OnClickListener.class/*可选参数, 默认是View.OnClickListener.class*/)private void tvClick(View view) { Toast.makeText(this, "我被点击了", Toast.LENGTH_LONG).show(); }}
上面的列子很简单,就是通过注解实例化了TextView控件,并且给它添加了点击事件。那xutls实现原理是什么呢?可能很多人都已经猜到了,不错就是上一篇中我们讲到的注解和发射来实现的。让我们一起来看下源码吧!看一下x.view().inject(this);到底做了什么?
@Override
public void inject(Activity activity) {
//获取Activity的ContentView的注解
Class<?> handlerType = activity.getClass();
try {
// 找到ContentView这个注解,在activity类上面获取
ContentView contentView = findContentView(handlerType);
if (contentView != null) {
int viewId = contentView.value();
if (viewId > 0) {
// 如果有注解获取layoutId的值,利用反射调用activity的setContentView方法注入视图
Method setContentViewMethod =
handlerType.getMethod("setContentView", int.class);
setContentViewMethod.invoke(activity, viewId);
}
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
// 处理 findViewById和setOnclickListener的注解
injectObject(activity, handlerType, new ViewFinder(activity));
}知道了原理,现在我们就开始手动打造我们自己的框架.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
/**
* @ViewInject(R.id.xxx)
* @return
*/
int value();
}public class ViewUtils {
public static void inject(Activity activity){
inject(new ViewFinder(activity),activity);
}
public static void inject(View view){
inject(new ViewFinder(view),view);
}
public static void inject(View view,Object object){
inject(new ViewFinder(view),object);
}
/**
* 兼容
* @param viewFinder
* @param object
*/
public static void inject(ViewFinder viewFinder,Object object){
injectField(viewFinder,object);
injectEvent(viewFinder,object);
}
private static void injectField(ViewFinder viewFinder,Object object) {
Class<?> clazz = object.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field:fields) {
ViewInject annotation = field.getAnnotation(ViewInject.class);
if(annotation!=null) {
int viewId = annotation.value();
View view = viewFinder.findViewById(viewId);
if (view!=null) {
try {
//private
field.setAccessible(true);
field.set(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnClick {
/**
*
* @return
*/
int[] value();
}private static void injectEvent(ViewFinder viewFinder, Object object) {
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method:methods) {
OnClick annotation = method.getAnnotation(OnClick.class);
if(annotation!=null){
int[] value = annotation.value();
for (int val: value) {
View view = viewFinder.findViewById(val);
boolean isCheckNet = method.getAnnotation(CheckNet.class)!=null;
if(view!=null){
view.setOnClickListener(new DeclaredOnClickListener(method,object,isCheckNet));
}
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener{
private Method mMethod;
private Object mObject;
private boolean isCheckNet;
public DeclaredOnClickListener(Method method, Object object,boolean isCheckNet) {
this.mMethod=method;
this.mObject=object;
this.isCheckNet=isCheckNet;
}
@Override
public void onClick(View v) {
try {
mMethod.setAccessible(true);
mMethod.invoke(mObject,v);
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject,null);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckNet {
}private static class DeclaredOnClickListener implements View.OnClickListener{
private Method mMethod;
private Object mObject;
private boolean isCheckNet;
public DeclaredOnClickListener(Method method, Object object,boolean isCheckNet) {
this.mMethod=method;
this.mObject=object;
this.isCheckNet=isCheckNet;
}
@Override
public void onClick(View v) {
//检查网络
if(isCheckNet){
if(networkAvaliable(v.getContext())){
Toast.makeText(v.getContext(),"",Toast.LENGTH_LONG).show();
return;
}
}
try {
mMethod.setAccessible(true);
mMethod.invoke(mObject,v);
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject,null);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}有兴趣可以扩展其他功能。