都说依赖注入,我就从实现的角度来一发,以android作为引子..
2016-05-19 13:35
477 查看
用过诸多的view注入的框架,例如xutils,butterknife,KJLibraray,Guice等,你了解过如何实现吗?
从零来一发, 今天老司机为新来者带带路~其他老司机略过
从demo上,我只实现两个功能@InjectView,@OnClick。前者注入view,后者注入点击事件。其他的实现角度上是一样的,大家可以一起探讨一下!
还是老套路,先分析:
1.我需要两个注解类@InjectView和@OnClick
简单提一下:
@Target的ElementType中
@Retention中:
ok,继续,
@InjectView中只接收一个int类型的值,用于表示view的id,
@OnClick中接收一个int[],表示可以接收多个view的id,绑定到同一个click执行方法上
既然有了注解,就少不了注解的解释者,
先分析一个view赋值的过程:
ok,看代码
怎么还实现了一个接口呢?
这个泛型接口接收一个AnnotatedElement的子类,它是什么呢?
在java中,Field,Method,Constructor…一切可注解的对象都实现了AnnotatedElement接口。ProcessorIntf用于给解析器提供一系列通用行为:
process方法换个角度一想,无论是@InjectView,@InjectString,@OnClick等等任何注入的操作,是不是应该都需要这几个条件呢?所以:
所以在InjectViewProcessor中是这样实现的:
如果是返回true,说明这个它可以处理,则走到
代码很简单,就简单说明下:
接着再分析@OnClick的实现:
好了,分析就到这里面,看代码:
再来分析代码:
ok,构造函数中,拿到了要回调的方法,和目标对象。这是必须的。
然后就是几个简单的判断 :
1.先拿到方法的参数,看看有没有参数 , 没有就纪录下hasParam为false,
2.有参数的话,判断是几个参数,超过两个了直接就报错吧,那多的参数我从哪里给呢。
3.ok很听话的只接收一个View,hasParam为true
ok,InjectView和OnClick到这里就已经完工了, 但是直接拿来用,却是有点不方便的,于是加一个管理者:
管理者很简单,提供了两个静态方法,一个给activity用, 一个可以给fragment,viewholder等任何对象用。其实最终用的也是同一个方法。
这里我用了一个处理器链的方式,假如后面我还要实现注入@string/xx,@color/xxx , @Service private WindowManager wm;等等等,把实现好的处理器加入到chain链中即可。
好了,长篇大论结束,最后贴下最终的调用:
ok,如果我要实现一个注入contentView怎么办呢?
留给读者。
从零来一发, 今天老司机为新来者带带路~其他老司机略过
从demo上,我只实现两个功能@InjectView,@OnClick。前者注入view,后者注入点击事件。其他的实现角度上是一样的,大家可以一起探讨一下!
还是老套路,先分析:
1.我需要两个注解类@InjectView和@OnClick
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectView { public @IdRes int value(); }
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface OnClick { public @IdRes int[] value(); }
简单提一下:
@Target的ElementType中
public enum ElementType { TYPE, //加在类上的注解 FIELD, //加在类中属性上的注解 METHOD, //加在类中方法上的注解 PARAMETER, //加在参数上的注解,可以是方法内的参数 CONSTRUCTOR, //加在构造函数上的注解 LOCAL_VARIABLE, //加在方法内变量上的注解 ANNOTATION_TYPE, //加在注解上的注解 PACKAGE //加在包名上的注解 }
@Retention中:
public enum RetentionPolicy { SOURCE, //只保留在源码中 CLASS, //保留在类字节码中 RUNTIME //保留在运行时(我们自定义注解一般都是在运行时检测的) }
ok,继续,
@InjectView中只接收一个int类型的值,用于表示view的id,
@OnClick中接收一个int[],表示可以接收多个view的id,绑定到同一个click执行方法上
既然有了注解,就少不了注解的解释者,
先分析一个view赋值的过程:
1. 首先要有一个rootView,用于findView 2. 还需要有目标view的id,这个就是@InjectView中的值 3. 需要目标对象 4. 把从rootView找到的view赋值给目标对象的目标变量
ok,看代码
public class InjectViewProcessor implements ProcessorIntf<Field>{ @Override public boolean accept(AnnotatedElement e) { return e.isAnnotationPresent(InjectView.class); } @Override public void process(Object object, View view, Field field) { InjectView iv = field.getAnnotation(InjectView.class); final int viewId = iv.value(); final View v = view.findViewById(viewId); field.setAccessible(true); try { field.set(object, v); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } }
怎么还实现了一个接口呢?
public interface ProcessorIntf<T extends AnnotatedElement> { public boolean accept(AnnotatedElement t); public void process(Object object, View view, T t); }
这个泛型接口接收一个AnnotatedElement的子类,它是什么呢?
在java中,Field,Method,Constructor…一切可注解的对象都实现了AnnotatedElement接口。ProcessorIntf用于给解析器提供一系列通用行为:
/* * 每个不同的处理器都会通过这个方法来告诉最终的调度者,这个注解是否由我来 * 处理 */ public boolean accept(AnnotatedElement t);
process方法换个角度一想,无论是@InjectView,@InjectString,@OnClick等等任何注入的操作,是不是应该都需要这几个条件呢?所以:
/*这样看来,可以把处理行为抽象成这几个参数? *第一个object是目标对象, *第二个view是根view *第三个是加上注解的那个东西 */ public void process(Object object, View view, T e);
所以在InjectViewProcessor中是这样实现的:
@Override public boolean accept(AnnotatedElement e) { //如果当前这个AnnotatedElement实例加有InjectView注解,则返回true return e.isAnnotationPresent(InjectView.class); }
如果是返回true,说明这个它可以处理,则走到
@Override public void process(Object object, View view, Field field) { InjectView iv = field.getAnnotation(InjectView.class); final int viewId = iv.value(); final View v = view.findViewById(viewId); field.setAccessible(true); try { field.set(object, v); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }
代码很简单,就简单说明下:
1.先拿到具体的注解类,并拿到里面的值比如R.id.txt等 2.从跟view中findViewById拿到指定的view 3.为防止目标属性是private的,将可访问设置为true,并赋值 -.ok,这样一个view就被注入到目标属性中了
接着再分析@OnClick的实现:
1.我需要知道要绑定点击事件的view的id,这个在@OnClick中的value指定 2.和之前的一样,也需要一个根view来拿到具体的view 3.要注入的对象,这个是必须的, 4.要把某个方法绑定为点击事件的回调,我还要知道是哪个方法 5.ok上述的条件都满足后,就可以拿到find来的view并设置setOnClickListener,在收到回调的时候,去调用指定方法,来实现间接的绑定
好了,分析就到这里面,看代码:
public class OnClickProcessor implements ProcessorIntf<Method> { @Override public boolean accept(AnnotatedElement e) { return e.isAnnotationPresent(OnClick.class); } @Override public void process(Object object,View view, Method method) { final OnClick oc = method.getAnnotation(OnClick.class); final int[] value = oc.value(); for (int id : value) { view.findViewById(id).setOnClickListener(new InvokeOnClickListener(method,object)); } } private static class InvokeOnClickListener implements View.OnClickListener { public Method method; public WeakReference<Object> obj; private boolean hasParam; InvokeOnClickListener(Method m, Object object) { this.method = m; this.obj = new WeakReference<Object>(object); final Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes == null || parameterTypes.length == 0) { hasParam = false; } else if (parameterTypes.length > 1 || !View.class.isAssignableFrom(parameterTypes[0])) { throw new IllegalArgumentException(String.format("%s方法只能拥有0个或一个参数,且只接收View", m.getName())); } else { hasParam = true; } } @Override public void onClick(View v) { //点击事件触发了 Object o = obj.get(); if (o != null) { try { if (hasParam) { method.invoke(o, v); } else { method.invoke(o); } } catch (Exception e) { e.printStackTrace(); } } } } }
再来分析代码:
//这个很简单,就是告诉管理器我响应OnClick注解 @Override public boolean accept(AnnotatedElement e) { return e.isAnnotationPresent(OnClick.class); }
/* * 这个也还是一样, * 1.先拿到具体的注解对象 ,并拿到里面的值 * 2.因为存在多个id绑定到一个方法上的情况,所以一个循环不可少 * 3.就是拿到view,设置监听事件 * 4.但是,这个InvokeOnClickListener是个什么东西呢? */ @Override public void process(Object object,View view, Method method) { final OnClick oc = method.getAnnotation(OnClick.class); final int[] value = oc.value(); for (int id : value) { view.findViewById(id).setOnClickListener(new InvokeOnClickListener(method,object)); } }
//先说下,这里面的InvokeOnClickListener是一个中间件,注册给系统,系统在得到点击事件后,通知给InvokeOnClickListener,在这个里面再调用你所指定的方法。 private static class InvokeOnClickListener implements View.OnClickListener { public Method method; public WeakReference<Object> obj; private boolean hasParam; InvokeOnClickListener(Method m, Object object) { this.method = m; this.obj = new WeakReference<Object>(object); final Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes == null || parameterTypes.length == 0) { hasParam = false; } else if (parameterTypes.length > 1 || !View.class.isAssignableFrom(parameterTypes[0])) { throw new IllegalArgumentException(String.format("%s方法只能拥有0个或一个参数,且只接收View", m.getName())); } else { hasParam = true; } } @Override public void onClick(View v) { //点击事件触发了 Object o = obj.get(); if (o != null) { try { if (hasParam) { method.invoke(o, v); } else { method.invoke(o); } } catch (Exception e) { e.printStackTrace(); } } } }
ok,构造函数中,拿到了要回调的方法,和目标对象。这是必须的。
然后就是几个简单的判断 :
1.先拿到方法的参数,看看有没有参数 , 没有就纪录下hasParam为false,
2.有参数的话,判断是几个参数,超过两个了直接就报错吧,那多的参数我从哪里给呢。
3.ok很听话的只接收一个View,hasParam为true
@Override public void onClick(View v) { //点击事件触发了 Object o = obj.get(); //为什么要用一个WeakReference,其实没有必要,因为activity消亡了,view也就消亡了,这个循环引用似乎不存在,但是我还是写下,假如有假如呢。 if (o != null) { try { if (hasParam) { //有参数,就把view传过去 method.invoke(o, v); } else { //没有参数就直接调 method.invoke(o); } } catch (Exception e) { e.printStackTrace(); } } }
ok,InjectView和OnClick到这里就已经完工了, 但是直接拿来用,却是有点不方便的,于是加一个管理者:
public class Injector { private static List<? extends ProcessorIntf<? extends AccessibleObject>> chain = Arrays.asList(new InjectViewProcessor(), new OnClickProcessor()); public static void inject(Activity act) { inject(act,act.getWindow().getDecorView()); } public static void inject(Object obj, View rootView) { final Class<?> aClass = obj.getClass(); final Field[] declaredFields = aClass.getDeclaredFields(); for (Field f : declaredFields) { doChain(obj,f,rootView); } final Method[] declaredMethods = aClass.getDeclaredMethods(); for (Method m : declaredMethods) { doChain(obj, m, rootView); } } private static void doChain(Object obj,AccessibleObject ao, View rootView) { for (ProcessorIntf p : chain) { if(p.accept(ao)) p.process(obj,rootView,ao); } } }
管理者很简单,提供了两个静态方法,一个给activity用, 一个可以给fragment,viewholder等任何对象用。其实最终用的也是同一个方法。
这里我用了一个处理器链的方式,假如后面我还要实现注入@string/xx,@color/xxx , @Service private WindowManager wm;等等等,把实现好的处理器加入到chain链中即可。
//这个就是前面已经说过的,把每个遍历到的方法或者属性,甚至是构造方法,类等等通过处理器链来询问这个注解你accept吗?接受则交给它来处理, private static void doChain(Object obj,AccessibleObject ao, View rootView) { for (ProcessorIntf p : chain) { if(p.accept(ao)) p.process(obj,rootView,ao); } } }
好了,长篇大论结束,最后贴下最终的调用:
public class TestActivity extends AppCompatActivity { @InjectView(R.id.txt) private TextView txt; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.test_activity); Injector.inject(this); } @OnClick({R.id.btnTestOne,R.id.btnTestTwo}) public void btnTestOne(Button view) { final int id = view.getId(); if (id == R.id.btnTestOne) { txt.setText("按钮一被点击"); }else{ txt.setText("按钮二被点击"); } } }
ok,如果我要实现一个注入contentView怎么办呢?
留给读者。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件