Android IOC 依赖注入框架

概述

对于Android从业者来说,绝大多数人都听说或者用过IOC框架,典型的第三方库有butterknife和xutils;它们的优势很明显,可以让你避免写一堆findViewById()代码,通过注解帮你注入所有的控件。举个例子:一个Activity需要setContentView()方法设置布局,你可以通过注解该Activity来代替;该Activity有许多控件,需要findViewById()来一个一个初始化,你可以通过注解该控件来代替;具体实现如下:

@ContentView(value = R.layout.activity_main)  
public class MainActivity extends BaseActivity  
{  
    @ViewInject(R.id.id_btn)  
    private Button mBtn1;  
    @ViewInject(R.id.id_btn02)  
    private Button mBtn2;
    ...

整体来讲,Android IOC框架可以通过注解+反射+动态代理来实现;注解对需要用的控件进行标注;代码执行时,利用反射技术获取被标注的类、属性和方法,解析注解内容,生成对应的类、属性并执行对应的方法;应用动态代理实现动作的监听及触发。下面是时候表演真正的技术了。

布局注入

  • 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
    int value();
}

解释下注解内容:@interface表示注解关键字;@Target表示该注解应用的对象,典型值有TYPE(类)、FIELD(属性)、METHOD(方法)、ANNOTATION_TYPE(其他注解);@Retention表示注解在什么情况下可用,典型的值有RUNTIME(运行时)、SOURCE(源代码)、CLASS(源码和class文件);
ContentView用于对类进行注解,主要用于设置Activity的布局文件,使用方式如下

@ContentView(value = R.layout.activity_main)  
public class MainActivity
  • 执行注入
private static void InjectLayout(Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        int layoutId = contentView.value();
        activity.setContentView(layoutId);
    }

通过反射获取注解内容并执行setContentView()方法。至此,布局注入完成。

属性注入

  • 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
    int value();
}

ViewInject用于注入属性,使用方式如下:

@ViewInject(R.id.id_btn)  
private Button mBtn1; 
  • 执行注入
private static void InjectView(Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field f: fields){
            ViewInject viewInject = f.getAnnotation(ViewInject.class);
            if (viewInject == null){
                continue;
            }
            int id = viewInject.value();
            View view = activity.findViewById(id);
            try {
                f.setAccessible(true);
                f.set(activity,view);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

利用发射获取注解内容并执行findViewById()方法初始化属性。

事件注入

  • 定义基类注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE) //用在另一个注解上
public @interface EventBase {
    //事件三要素

    /**
     * 设置事件监听的方法
     * @return
     */
    String listenerSetter();

    /**
     * 事件监听的类型
     * @return
     */
    Class<?> listenerType();

    /**
     * 设置事件监听的回调方法
     * @return
     */
    String callbackMethod();
}

EventBase 定义事件的三要素,同时也是基类注解,事件注解必须应用该注解。

  • 定义事件注解
@Retention(RetentionPolicy.RUNTIME)//运行时存在类上
@Target(ElementType.METHOD)  //用在方法上
//注解的注解,指定事件的三要素(拓展性强,当需要拓展时,不需要修改该类,直接添加一个onLongClick注解即可)
@EventBase(listenerSetter = "setOnClickListener",listenerType = View.OnClickListener.class,callbackMethod = "onClick")
public @interface Onclick {
    int[] value();
}
  • 执行事件
private static void InjectEvent(Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method m : methods){
            Annotation[] annotations = m.getAnnotations();
            for (Annotation annotation : annotations){
                //每一个注解代表一个事件类型
                Class<?> annotationType = annotation.annotationType();
                EventBase eventBase = annotationType.getAnnotation(EventBase.class);
                //获取事件的三要素
                if (eventBase == null){
                    continue;
                }
                String listenerSetter = eventBase.listenerSetter();
                Class<?> listenerType = eventBase.listenerType();
                String callbackMethod = eventBase.callbackMethod();
                Map<String,Method> methodMap = new HashMap<>();
                methodMap.put(callbackMethod,m);
                //获取View
                try {
                    Method valueMethod = annotationType.getDeclaredMethod("value");
                    int[] viewIds = (int[]) valueMethod.invoke(annotation);
                    for (int id : viewIds){
                        View view = activity.findViewById(id);
                        if (view == null){
                            continue;
                        }
                        //执行setXXX方法
                        /*btn1.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                            }
                        });*/
                        //应用动态代理模式
                        Method setListenerMtd = view.getClass().getMethod(listenerSetter,listenerType);
                        //listenerType代理对象
                        InvocationHandler handler = new EventInvocationHandler(activity,methodMap);
                        Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),new Class<?>[]{listenerType},handler);
                        setListenerMtd.invoke(view,proxy);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

同样是通过反射机制获取注解内容,不同的是,应用动态代理的方式实现事件的监听和触发。关于动态代理机制的原理在此不做详细介绍,下面附上InvocationHandler类的实现:

/**
 * 用于实现事件监听时的代理
 */

public class EventInvocationHandler implements InvocationHandler {

    private Activity mActivity;
    private Map<String,Method> methodMap;

    public EventInvocationHandler(Activity activity, Map<String,Method> methodMap) {
        this.mActivity = activity;
        this.methodMap = methodMap;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //如果是Onclick方法,进行拦截,使用代理,执行其他方法
        Method mtd = methodMap.get(method.getName());
        if (mtd != null){
            return mtd.invoke(mActivity,args);
        }
        return method.invoke(proxy,args);
    }
}

受xUtils库的启发,IOC框架不仅仅能实现布局、属性、事件的注入,还能应用于各种事情;举个例子:用注解标注一个方法,当有网络时则执行该方法,没有网络时给出提示并且不执行方法。理解了该框架的思想后就能做各种事情了。

完整代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值