使用反射+注解,教你学会最简单的依赖注入

来源:互联网 发布:双机软件品牌 编辑:程序博客网 时间:2024/05/20 01:11

本文意于让人简单地使用注解+反射,用注解让控件实现findViewById()以及setOnClickListener(),不再重复写findViewById这些重复性的代码。

网上已经有很多这种框架,比如xUtils,Butterknife等,浅略地看了这两个框架的源码,前者使用的是注解和反射的方式,后者使用的是编译期生成一些代码,可以看Android Studio中的app/build/generated/source/apt,这里都是编译后,框架自动生成的代码。而这两种均能实现这些功能。区别在于,前者使用的是注解+反射,可能会一定程度影响性能,但是,在这个手机性能过剩的时代,这种影响真的可以忽略了。而后者可能在编译期会消耗一些性能,但是运行的时候不会有影响。

以上,均是个人理解。如有不同意见,可以留言一起探讨一下。

一、首先先看一个炒鸡简陋的界面。


因为只是为了能使用,就没有弄一些比较花哨的界面,只要功能实现了就好了。

打开这个界面的时候,在onStart()生命周期打印了按钮1的文本,证明此时按钮1已经是绑定成功了。


接下来是点击按钮二,只是简单的吐司一下。


这就是要讲的全部了,主要不在于界面,而是怎么去实现这个。


二、自定义注解。

关于Java的注解,网上的教程很多,在这里不再论述了,而且讲得不好倒坏了事了。

首先是控件绑定的注解类(就是代替findViewById()方法的注解)

/** * 描述:用于注解成员的注解类 * 包名:xiedroid.didemo.utils * 类名:ViewInject * * @author XieQingXiong */@Target(ElementType.FIELD)//标识这个注解类只能注解字段@Retention(RetentionPolicy.RUNTIME) //表示这个注解在运行时期起作用public @interface ViewInject {    // 注解里面的值,为 int 类型,用value表示是默认的    // 比如:使用这个注解的时候    // @ViewInject(R.id.btn1),R.id.btn1是int值,可以不用 @ViewInject(value=R.id.btn1)的形式。    // 如果不是  int value()  ,而是 int name() 的话,则使用注解时就要用 @ViewInject(name=R.id.btn1)    // 所以说 value是一个默认的值    int value();}

关于这个注解类的说明,注释我觉得已经讲清楚了。如果还不清楚的话,Google(百度也行)吧。

这个注解的使用是

@ViewInject(R.id.btn1)private Button btn1;

这样就是绑定一个Button控件了。


接下来就是控件点击事件的注解类(就是setOnClickListener()方法的注解)

/** * 描述:点击事件的注解类 * 包名:xiedroid.didemo.utils * 类名:OnClick * * @author XieQingXiong */@Target(ElementType.METHOD)//只能使用方法上@Retention(RetentionPolicy.RUNTIME)//运行期起作用作用public @interface OnClick {    int value();}

使用方法

@OnClick(R.id.btn2)public void btn2Click(View v){    //处理逻辑}


三、控件的绑定与事件的绑定

完成注解类之后,接下来就是控件的各种绑定操作了。

首先,需要进行控件绑定的话,需要在Activity的onCreate()方法中调用一个用于绑定操作的方法

protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    InjectUtils.inject(this);}

绑定的操作是在InjectUtils类中执行的,调用的jnject()方法

public static void inject(Activity activity) {    //绑定控件    bindView(activity);    //点击事件的绑定    bindEvent(activity);}

在这个方法里调用了两个方法,分别是绑定控件跟绑定事件。

先来看控件的绑定

private static void bindView(Activity activity) {    //获取class    Class<? extends Activity> clazz = activity.getClass();    //获取到这个类的所有的成员字段    Field[] declaredFields = clazz.getDeclaredFields();    //遍历所有的字段    for (int i = 0; i < declaredFields.length; i++) {        //判断这个成员字段中有没有ViewInject这个注解        ViewInject viewInject = declaredFields[i].getAnnotation(ViewInject.class);        //如果没有这个注解,跳过这个成员字段        if (viewInject == null)            continue;        //获取注解的值,在这里就是控件的id        int resId = viewInject.value();        //得到这个控件        View view = activity.findViewById(resId);        try {            //暴力反射,如果不设置这个,那么如果成员是private的话,就不能进行绑定            declaredFields[i].setAccessible(true);            //将这个控件跟activity绑定            declaredFields[i].set(activity,view);        } catch (IllegalAccessException e) {            e.printStackTrace();        }    }}

该方法的整体思路是:

1、获取到这个Activity中所有的成员字段(Field) 

2、然后遍历每个字段,看是否有对应的 ViewInject 这个自定义注解存在。

3、如果存在这个注解,获取到这个注解里面的值(控件id),并根据该值找到当前的控件(View)。

4、将这个View跟Activity绑定。

这样就完成了Activity上所有带自定义注解(ViewInject)的控件的绑定。


点击事件的绑定

private static void bindEvent(final Activity activity) {    Class<? extends Activity> clazz = activity.getClass();    //获取所有的方法    Method[] declaredMethods = clazz.getDeclaredMethods();    for (final Method method : declaredMethods) {        //获取方法上的OnClick注解        OnClick onClick = method.getAnnotation(OnClick.class);        if (onClick == null)            continue;        //获取控件的id        int resId = onClick.value();        final View view = activity.findViewById(resId);        view.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                try {                    //暴力反射                    method.setAccessible(true);                    method.invoke(activity,view);                } catch (IllegalAccessException e) {                    e.printStackTrace();                } catch (InvocationTargetException e) {                    e.printStackTrace();                }            }        });    }}

思路:

1、找到所有的方法。

2、遍历方法,看是否有自定义注解(OnClick)存在。

3、存在这个注解,获取到注解里面的值,找到对应的控件(View)。

4、调用这个View的setOnClickListener()方法,在这个监听方法里面调用Activity中有@OnClick注解的方法,这样就实现了点击事件的转换。

(注:直接在这里调用setOnClickListener()只是为了可以更清楚的理解,真正要做得更好是不会这么写死的。可能会用动态代理来实现。)


以上就完成了控件的绑定跟点击事件的绑定。

四、最后再瞄一眼ManActivity

public class MainActivity extends AppCompatActivity {    @ViewInject(R.id.btn1)    private Button btn1;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        InjectUtils.inject(this);    }    @Override    protected void onStart() {        super.onStart();        Log.e("xqx", "onStart: 得到按钮的名字 === "+btn1.getText().toString());    }    @OnClick(R.id.btn2)    public void btn2Click(View v){        Toast.makeText(this, "点击了按钮2", Toast.LENGTH_SHORT).show();    }}


这样就完成了一个很简单的依赖注入迷你框架了(请让我装下逼假装说是个框架,勿喷!)。

本文源码


0 0