前面两篇文章讲解了注解的基础知识和运行时注解的解析方法。下面我们用学到的知识来手动写一个属于自己的注解框架。
在写自己的注解框架前,让我们先来看下一些好的框架,相信大家对xutils应该不会太陌生。xutils主要包括网络,数据库,IOC注入,网络图片使用,那么我们这里主要看看xutils3.0的IOC注解:https://github.com/wyouflf/xUtils3
xutils注解使用:
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);
/** * 1. 方法必须私有限定, * 2. 方法参数形式必须和type对应的Listener接口一致. * 3. 注解参数value支持数组: value={id1, id2, id3} * 4. 其它参数说明见{@link org.xutils.view.annotation.Event}类的说明. **/ @Event(value = R.id.tv,mTv.setText("MMMMMM"); }
private void tvClick(View view) { Toast.makeText(this, "我被点击了", Toast.LENGTH_LONG).show(); }}type = View.OnClickListener.class/*可选参数, 默认是View.OnClickListener.class*/)
上面的列子很简单,就是通过注解实例化了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)); }
xutils相对来说应该不难理解吧,其实就是我们利用类的反射循环获取属性的注解值然后通过findViewById之后,动态的注入到控件属性里面;事件注入也是类似首先findViewById然后利用动态代理去反射执行方法。
知道了原理,现在我们就开始手动打造我们自己的框架.
1.控件注入:
@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(); } } } } } }
2.点击事件:
@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(); } } } }
3.扩展其他功能
比如:检测网络是否正常:
@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(); } } } }
有兴趣可以扩展其他功能。
下面两篇将讲解,在编译期使用注解,和仿ButterKnife基本功能