Ioc注入框架 实现类bufferknife注解功能

什么是IoC(控制反转)?

  •     控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找
  •    通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

 bufferknife通过给布局、视图、事件添加注解省去繁琐的代码 如:

@BindViews({ R.id.button}) 

privite Button button ; 

@OnClick(R.id.button )   //给 button1 设置一个点击事件  
    public void showToast(){  
        Toast.makeText(this, "is a click", Toast.LENGTH_SHORT).show();  
    }  

接下来我们通过注解实现类似bufferknife的功能

主要原理:

  通过在activity执行onCreate()方法时进行依赖注入,对当前actvity的注解进行解析、判断,再通过反射获取对象和方法分别进行     setContentView()设置布局、findViewById()绑定控件、设置监听setOnXXXClick()等操作。 

主要流程:

  1. 观察我们的Actvity中需要通过这样几个注解来实现setContentView()设置布局、findViewById()绑定控件、设置监听setOnXXXClick()等功能。按照我们的思路我们需要对这些注解进行解析,那么在哪里解析呢,注解又该传什么参数呢?

@SetContentView(R.layout.activity_main)

public class MainActivity extends BaseActivity {

    @BindView(R.id.btn_b1)
    private Button btn;

    @BindView(R.id.tv_t1)
    private TextView tv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    @OnClick({R.id.btn_b1,R.id.tv_t1})
    public void onClick(View view){
        switch (view.getId()){
            case R.id.btn_b1:
                Toast.makeText(MainActivity.this,"点击了按钮",Toast.LENGTH_SHORT).show();
                break;

            case R.id.tv_t1:
                Toast.makeText(MainActivity.this,"点击了tv",Toast.LENGTH_SHORT).show();
                break;

        }
    }
    @OnLongClick({R.id.btn_b1,R.id.tv_t1})
    public boolean onLongClick(View view){
        switch (view.getId()){
            case R.id.btn_b1:
                Toast.makeText(MainActivity.this,"长按了按钮",Toast.LENGTH_SHORT).show();
                break;

            case R.id.tv_t1:
                Toast.makeText(MainActivity.this,"长按了tv",Toast.LENGTH_SHORT).show();
                break;

        }
        return false;
    }
}

2.我们选择继承一个BaseActivity,在onCreate()方法里实现解析操作

public class BaseActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectManager.inject(this);
    }
}

4.我们从最简单的SetContentView方法开始,首先需要定一个SetContentView注解

@Target(ElementType.TYPE)//注解作用的范围 1.TYPE:类 2.METHOD:方法 
//3.FIELD:字段 不符合的编译器将报错 4.ANNOTATION_TYPE: 元注解:作用与注解之上
@Retention(RetentionPolicy.CLASS)//注解生效的时期 1.RUNTIME 程序运行的时候  
//2.SOURCE 源码阶段当你打出代码的时候  3.CLASS 预编译阶段
public @interface SetContentView {
    int value();
}

3.那么 InjectManager.inject(this) 方法又需要实现什么呢

public class InjectManager {

    public static void inject(Activity activity){

        //注入布局
        injectLayout(activity);
        //注入控件
        injectView(activity);
        //注入事件
        injectEvent(activity);
    }

    private static void injectLayout(Activity activity) {
        try {
            //获取当前Activity类
            Class<? extends Activity> clazz = activity.getClass();
            //获取SetContentView类型的注解对象
            SetContentView setContentView=clazz.getAnnotation(SetContentView.class);
            //判空
            if (setContentView!=null) {
                //拿到传入int类型的R.id.xxx
                int layoutId = setContentView.value();
                //找到Activity的setContentView(int layoutId)方法
                Method method = clazz.getMethod("setContentView", int.class);
                //执行该方法
                method.invoke(activity, layoutId);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void injectView(Activity activity) {

        //todo

    }

    private static void injectEvent(Activity activity) {
       
        //todo
    }

}

到此我们已经实现类布局的设置

 

4.接下来我们需要实现控件的绑定,定义注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    int value();
}

5.InjectManager类中injectView(activity)方法  实现控件绑定

private static void injectLayout(Activity activity) {
        try {
            //获取当前Activity类
            Class<? extends Activity> clazz = activity.getClass();
            //获取SetContentView类型的注解对象
            SetContentView setContentView=clazz.getAnnotation(SetContentView.class);
            //判空
            if (setContentView!=null) {
                //拿到传入int类型的R.id.xxx
                int layoutId = setContentView.value();
                //找到Activity的setContentView(int layoutId)方法
                Method method = clazz.getMethod("setContentView", int.class);
                //执行该方法
                method.invoke(activity, layoutId);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

6.事件监听的注解 事件可以又很多种 onClick() onLongClick()等   所以我们需要先通过元注解获取不同监听的不同监听方法和对象

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {

    //1.setOnXXXListener  方法名

    String listenerSetter();

    //2.View.OnXXXClickListener 监听对象

    Class<?> listenerType();
}

OnClick.class 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnClickListener",listenerType = View.OnClickListener.class)
public @interface OnClick {
    int[] value();
}

 OnLongClick.class 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
//向EventBase注解传入当前需要的监听方法和对象
@EventBase(listenerSetter = "setOnLongClickListener",listenerType = View.OnLongClickListener.class)
public @interface OnLongClick {
    int[] value();
}

7.注入事件 injectEvent(activity)

private static void injectEvent(Activity activity) {
        try {
            Class<? extends Activity> clazz = activity.getClass();
            //获取当前Activity的所有方法
            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 eventBase=annotationType.getAnnotation(EventBase.class);
                        if (eventBase.annotationType()!=null) {
                            //得到监听需要的方法和对象
                            String listenerSetter = eventBase.listenerSetter();
                            Class<?> listenerType = eventBase.listenerType();

                            //得到传入的所有的R.id.xxx
                            Method valueMethod = annotationType.getDeclaredMethod("value");
                            int[] ids = (int[]) valueMethod.invoke(annotation);

                            //设置代理
                            Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),//类加载器
                                    new Class[]{listenerType}, new ClickInvocationHandler(activity,method));//类接口
                            //遍历所有控件
                            for (int id : ids) {
                                //设置监听前还是需要绑定控件的
                                Object view = activity.findViewById(id);
                                Method setListener = view.getClass().getMethod(listenerSetter, listenerType);
                                //ClickInvocationHandler的invoke方法 会对proxy中传入的接口所有方法进行拦截
                                setListener.invoke(view, proxy);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

8.InvocationHandler.class

public class ClickInvocationHandler implements InvocationHandler {

    Object target;
    Method method;
//    String methodName;

    /**
     *
     * @param target 目标类
     * @param method 要执行的方法
     */
    public ClickInvocationHandler(Object target,Method method) {//String methodName,
        this.target = target;
        this.method = method;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        //对原来接口中的onXXclick方法进行拦截   换成执行我们传入自己的方法 参数不变
        return this.method.invoke(target,objects);
    }
}

~~~最后附上github地址:demo

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值