浅谈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);
    }
}

评论

您确定要删除吗?删除之后不可恢复