浅谈Android IOC注解技术
引言
IOC这几个字符可能对于从事移动开发的同学比较陌生,但是如果熟悉服务端开发,或者对Spring了解一些,相信对于IOC一定有了不同程度的理解。相对于IOC而言,还有一个类似的名词AOP,AOP这里就不做延伸了,有兴趣的同学可以参考网上其它相关文章。本文主要介绍IOC注解技术,首先这里需要强调一点,IOC技术的实现并不一定都是依赖于注解技术实现的,使用配置文件的方式也可以,比如XML方式配置。不过无论是使用XML配置,还是使用注解技术,其依赖的Java最基本的技术都是“反射”。
控制反转(Inversion of Control,缩写为IOC),是面向对象编程中的一种设计原则,目的是为了降低代码之间的耦合度。依赖注入(Dependency Injection,简称DI)是控制翻转最常见的方式,还有一种方式叫“依赖查找”(Dependency Lookup)。
技术描述
Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。
采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件或者注解来指定。
在Android开发过程中,假如Activity中声明了一个TextView的成员变量,我们不需要在Activity中以findViewById的方式找到TextView,而是采用依赖注入的技术,在IOC框架中通过反射方式,将TextView变量赋值给Activity。
如果对于ButterKnife的实现原理了解,会发现本文所介绍的IOC方式相当容易,在示例中使用运行时注解的方式,相对于编译时注解,运行时注解时比编译时注解缺少了一步生成辅助代码的逻辑。
自定义IOC框架
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface BindView { int value(); } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface OnClick { int[] value(); }
两个注解在使用上面与ButterKnife中注解使用类似:
public class MainActivity extends AppCompatActivity { @BindView(R.id.textView) private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewInject.inject(this); } @OnClick(R.id.textView) public void startAction() { Log.d(TAG, textView.getText().toString()); } } public class ViewInject { public static void inject(Activity activity) { inject(activity, activity.getWindow().getDecorView()); } public static void inject(Object target, View targetView) { // 处理findViewById注解 processFindView(target, targetView); // 处理OnClick注解 processOnClick(target, targetView); } private static void processFindView(Object target, View targetView) { // 1.获取该target的所有属性 Class<?> clazz = target.getClass(); Field[] fields = clazz.getDeclaredFields(); // 2.循环判断该属性上是否有BindView注解 for (Field field : fields) { // 2.1 获取BindView注解 BindView bindView = field.getAnnotation(BindView.class); if (bindView != null) { // 该属性上有BindView // 2.2 获取 @BindView(R.id.textView) R.id.textView的值 int viewId = bindView.value(); // 3.利用activity的findViewById 动态注入给属性 View view = targetView.findViewById(viewId); // 3.1 动态注入给属性 try { field.setAccessible(true);// 私有属性可以动态注入 // activity 该属性在哪一个类 view 给该属性设置的值 field.set(target, view); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } private static void processOnClick(Object target, View targetView) { // findViewById setOnClick // 1.获取该target的所有方法 Class<?> clazz = target.getClass(); Method[] methods = clazz.getDeclaredMethods(); // 2.遍历方法获取所有的值 for (final Method method : methods) { // 2.1 获取OnClick注解 OnClick onClick = method.getAnnotation(OnClick.class); // 2.2 该方法上是否有OnClick注解 if (onClick != null) { // 2.3 获取OnClick里面所有的值 int[] viewIds = onClick.value();// @OnClick({R.id.textView,R.id.button}) // 2.4 先findViewById , setOnclick for (int viewId : viewIds) { // 先findViewById final View view = targetView.findViewById(viewId); // 后设置setOnclick view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 3.反射调用原来配置了OnClick的方法 method.setAccessible(true);// 私有的方法 try { method.invoke(target);// 调用无参的方法 } catch (Exception e) { e.printStackTrace(); try { method.invoke(target, view);// 调用有参的方法 view 代表当前点击的View } catch (Exception e1) { e1.printStackTrace(); } } } }); } } } } }
上面的两个方法都很简单,不做过多介绍了。但是第二种方法用于处理点击事件的方式,有没有发现方法定义太直接了,无法扩展到其它事件上,比方说再增加onLongClick或者OnItemClick事件,还需要重新定义与处理OnClick类似的方法,增加许多冗余代码。
接下来提供一种相对较通用的处理事件的方式,这里使用了JDK的动态代理。
Target(ElementType.ANNOTATION_TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface EventBase { //setListener Class<?> listenerType(); //new View.OnxxxListener String listenerSetter(); //回调最终执行方法 String callBackListener(); } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @EventBase(listenerSetter = "setOnLongClickListener", listenerType = View.OnLongClickListener.class, callBackListener = "onLongClick") public @interface OnLongClick { int[] value(); } private static void injectEvents(Object target, View targetView) { Class<?> clazz = target.getClass(); //获取一个类的所有方法 Method[] methods = clazz.getDeclaredMethods(); //遍历所有方法 for (Method method : methods) { Annotation[] annotations = method.getAnnotations(); //遍历所有注解 for (Annotation annotation : annotations) { Class<? extends Annotation> annotationType = annotation.annotationType(); if (annotationType != null) { EventBase eventBase = annotationType.getAnnotation(EventBase.class); if (eventBase != null) { String listenerSetter = eventBase.listenerSetter(); Class<?> listenerType = eventBase.listenerType(); String callBackListener = eventBase.callBackListener(); try { Method valueMethod = annotationType.getDeclaredMethod("value"); int[] viewIds = (int[]) valueMethod.invoke(annotation); method.setAccessible(true); ListenerInvocationHandler handler = new ListenerInvocationHandler(activity); handler.addMethods(callBackListener, method); //代理模式 Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, handler); for (int viewId : viewIds) { View view = targetView.findViewById(viewId); Method setter = view.getClass().getMethod(listenerSetter, listenerType); setter.invoke(view, listener); } } catch (Exception e) { e.printStackTrace(); } } } } } } public class ListenerInvocationHandler implements InvocationHandler { private long lastClickTime; private Object target;//需要拦截的对象 private HashMap<String, Method> map = new HashMap<>(); public ListenerInvocationHandler(Object target){ this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (target != null){ String methodName = method.getName(); method = map.get(methodName); if (method != null){ if (method.getGenericParameterTypes().length == 0){ return method.invoke(target); } return method.invoke(target,args); } } return null; } public void addMethods(String methodName, Method method){ map.put(methodName, method); } }