IOC编程实践

IOC(inversion of control)的中文解释是“控制反转”或者“依赖注入”,它的实现目的是:我们可以通过配置文件来控制程序的流程,达到程序代码的优化。通俗的讲就是将程序要做的一些事交给框架处理,达到很好的解耦性。

android中常用的视图绑定框架有多个,像Butterknife,XUtils等,今天就窥探下其原理。

分三步
一、视图绑定,就以activity为例,setContentView来指定加载的视图
定义一个注解:

package com.example.inject;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * zhiyunPlayer
 * Created by swl on 2019-09-03
 * Describe:
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface LayoutID {
    int value();  //值就是视图的id
}

使用:

@LayoutID(R.layout.activity_main)
public class MainActivity extends AppCompatActivity 

接下来就是怎么去通过注解的id注入到activity的setContentView中

 /**
     * 
     * @param obj    就是注解的类
     */
private static void injectContentView(Object obj) {
        Class<?> clazz = obj.getClass();
        LayoutID layoutID = clazz.getAnnotation(LayoutID.class);
        if(null==layoutID){
            return;
        }
        int value = layoutID.value();
        try {
            Method setContentView = clazz.getMethod("setContentView", int.class);
            setContentView.invoke(obj,value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

二、控件注解绑定
也定义一个注解:

package com.example.inject;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by swl on 2019-09-03
 * Describe:
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ViewID {
    int value() ;
}

使用:

 @ViewID(R.id.tv)
    TextView mTextView;

注解绑定:

 private static void injectView(Object obj) {
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            ViewID viewID = field.getAnnotation(ViewID.class);
            if(null==viewID){
                continue;
            }
            try {
                Method findViewById = clazz.getMethod("findViewById", int.class);
                Object view = findViewById.invoke(obj, viewID.value());
                field.setAccessible(true);
                field.set(obj,view);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

三、事件注解绑定:前两者相对比较简单的,这个稍微复杂点
先看个例子:
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});

这里有其实有4个不确定的元素,一个是控件(mTextView),一个是事件监听设置(setOnClickListener),一个是事件监听器(View.OnClickListener),一个是具体事件回调(onClick)

根据这个来定义注解:
控件还是通过ID:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Click {
    /* mTextView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

        }
    });*/
    int[] value() default -1;
}

这时要思考的就是其他3个不确定元素怎么通过注解定义出来,这时就可以使用注解的注解。
再定义一个标识另外三个不确定元素的注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventType {
    String eventMethod();  //就是setOnClikListener
    Class<?> eventClazz();  //View.OnClickListener
    String event();  //onClick
}

然后我们再修改前面的Click注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventType(eventMethod = "setOnClickListener",eventClazz = View.OnClickListener.class,event = "onClick")
public @interface Click {
    /* mTextView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

        }
    });*/
    int[] value() default -1;
}

使用:

 @Click(R.id.tv)  //多个控件,就传数组
    public void click(View view){
        Toast.makeText(this, "点击了"+view.getId(), Toast.LENGTH_SHORT).show();
    }

接下来就是怎么注入绑定了?

private static void injectEvent(Object obj) {
        Class<?> clazz = obj.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();//拿到方法上所有的注解
            for (Annotation annotation : annotations) {
                Class<? extends Annotation> annotationType = annotation.annotationType();
                EventType eventType = annotationType.getAnnotation(EventType.class);//拿到注解上的注解
                if(null==eventType){  
                    continue;
                }
                String eventMethod = eventType.eventMethod();
                Class<?> eventClazz = eventType.eventClazz();
                String event = eventType.event();

                //得到注解的value值
                try {
                    Method value = annotationType.getMethod("value");
                    int[] viewId = (int[]) value.invoke(annotation);
                    //遍历数组,通过反射方式获取对应的View
                    for (int i : viewId) {
                        Method findViewById = clazz.getMethod("findViewById", int.class);
                        Object view = findViewById.invoke(obj, i);
                        if(null==view){
                            continue;
                        }
/*
* 		这里拿到了view,设置事件监听的方法名,事件监听类以及事件回调方法名,就只要考虑怎么把这事件设置给view,转交给obj里面注解了的方法去执行,这时我们就会想到动态代理,创建一个InvacationHandler,把obj和注解的Method传进去
public class EventInvocationHandler implements InvocationHandler {
    public EventInvocationHandler(Object obj, Method methodName) {
        this.obj = obj;
        this.mMethod = methodName;
    }

    private Object obj;
    private Method mMethod;


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return mMethod.invoke(obj,args);
    }
}
*/
                        EventInvocationHandler invocationHandler = new EventInvocationHandler(obj, method);
                        Object proxyInstance = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{eventClazz}, invocationHandler);
                        //反射方式获取setOnClickListerner(View.OnClickListener())方法
                        Method viewEvent = view.getClass().getMethod(eventMethod, eventClazz);
                        //和代理对象发生关联,一旦主体对象(View.OnClickLiserner)的OnClick(View view)执行了,就会执行代理对象的invoke方法,这样就把方法执行转交给obj了
                        viewEvent.invoke(view,proxyInstance);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }

            }

        }
    }

InvocationHandler的定义:

public class EventInvocationHandler implements InvocationHandler {
    public EventInvocationHandler(Object obj, Method methodName) {
        this.obj = obj;
        this.mMethod = methodName;
    }

    private Object obj;
    private Method mMethod;


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return mMethod.invoke(obj,args);
    }
}

至此,视图、控件、事件的绑定就基本完成了,而且具有比较好的扩展性,假如新加入一个事件,比如onLongClikListener,就依样画葫芦
定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventType(eventMethod = "setOnLongClickListener",eventClazz = View.OnLongClickListener.class,event = "onLongClick")
public @interface LongClick {
    /* mTextView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

        }
    });*/
    int[] value() default -1;
}

使用也是一样

 @LongClick(R.id.tv)
    public boolean longClik(View view){   //这里注意下,这里的返回值得跟主体调用的方法得返回保持一致
        Toast.makeText(this, "长点击了"+view.getId(), Toast.LENGTH_SHORT).show();
        return true;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值