使用注解打造自己的IOC框架

原创 2017年11月28日 19:49:58

一、简述

IoC和AOP可谓是后台开发入门必学的知识(Spring相关),但这两者都仅仅只是概念而已,并非具体技术实现,同样的,Android也可以使用IoC和AOP,之前已经写过如何在Android开发中使用AOP了,有兴趣的朋友可以看我之前的博客(顺便点个关注吧),所以,本文主题便是IoC。

控制反转(Inversion of Control,英文缩写为IoC)是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。

上述源至百度百科,对于第一次接触IoC的人可能有些晦涩难懂,其实,通俗来讲,就是本来我可以做的事我现在不想做了,交给框架来做。举个实际的例子,就是ButterKnife,它就是Android上IoC的典型,实现了控件的动态注入及点击事件的绑定。所以,下面我们就来打造一个类似ButterKnife的IoC框架吧。

二、框架实现

下面是ButterKnife在GitHub上的代码示例:

class ExampleActivity extends Activity {
  @BindView(R.id.user) EditText username;
  @BindView(R.id.pass) EditText password;

  @OnClick(R.id.submit) void submit() {
    // TODO call server...
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

它包含3部分:

  • 控件注入使用@BindView注解
  • 点击事件的绑定使用@OnClick注解
  • 在onCreate()方法中调用ButterKnife.bind(this)

所以,我们要模仿ButterKnife,先从@BindView和@OnClick这两个注解入手。

1、注解

注意,不管是控件注入还是点击事件绑定,都必须跟控件的id扯上关系,所以这两个注解中都会有一个属性用于表示控件的id。代码如下:

// 控件注入注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    int value();
}

// 控件点击事件注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickView {
    int value();
}

因为我不想事件绑定的注解名为OnClick,所以这里的注解命名为ClickView,效果一样的。

其中,BindView注解用于控件的注入,即类字段,所以其Target取值ElementType.FIELD,而ClickView注解用于控件的点击事件绑定,即方法,所以其Target取值ElementType.METHOD;并且,这两个注解都是在App运行期间被框架所使用,即运行时可见,所以,Retention取值为RetentionPolicy.RUNTIME。这俩注解在编码上的使用见如下代码:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.btn_hello)
    Button mBtnHello;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @ClickView(R.id.btn_hello)
    public void sayHello() {
        Toast.makeText(getApplicationContext(), "hello", Toast.LENGTH_SHORT).show();
    }
}

但这样是不够的,因为注解可以认为只是一个标记,是静态的,它并没有实现控件注入与事件绑定的功能,控件的获取实际上还是需要findViewById()来实现,而事件的绑定同样也需要setOnClickListener()来实现,这也正是框架要为我们所做的工作。

2、控件注入与事件绑定的实现

ButterKnife不是这么实现的,这只是我个人的想法而已。

  1. 控件注入:实际上是框架调用了activity的findViewById()方法拿到id对应的控件,再通过反射的方式,对控件(类字段)进行赋值。
  2. 事件绑定:实际上也是框架调用了activity的findViewById()方法拿到id对应的控件,再调用控件的setOnClickListener()设置控件的点击事件,在这个点击事件里通过反射调用Activity中被ClickView注解的sayHello()方法而已。

下面就来动手实现它吧:

public class ViewUtil {

    public static void inject(final Activity activity) {
        // 拿到Activity的class对象
        Class clazz = activity.getClass();

        // 遍历属性
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 找到有BindView注解的属性
            BindView bindView = field.getAnnotation(BindView.class);
            if (bindView != null) {
                try {
                    // 让属性可被访问(如果属性使用final和jprivate,则必须使其可访问,否则以下操作会报错)
                    field.setAccessible(true);
                    // 通过id获取到View,再对属性赋值
                    field.set(activity, activity.findViewById(bindView.value()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

        // 遍历方法
        Method[] methods = clazz.getDeclaredMethods();
        for (final Method method : methods) {
            // 找到有ClickView注解的方法
            ClickView clickView = method.getAnnotation(ClickView.class);
            if (clickView != null) {
                // 通过id获取到View,再对view设置点击事件
                activity.findViewById(clickView.value()).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        try {
                            method.setAccessible(true);
                            // 调用这个被ClickView注解的方法
                            method.invoke(activity);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    }
}

3、试试

功能既已实现,下来就来试试看,是否真的有效,在原先代码的onCreate()方法中加上ViewUtil.inject(this):

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.btn_hello)
    Button mBtnHello;

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

    @ClickView(R.id.btn_hello)
    public void sayHello() {
        Toast.makeText(getApplicationContext(), "hello", Toast.LENGTH_SHORT).show();
    }
}

如果控件注入成功,则当点击控件时,会吐司”hello”。

三、拓展

上面的测试很成功啊,不过,这个框架目前只能给Activity使用,而ButterKnife可不只如此,不管Activity还是Fragment都能通吃,所以,我们这个框架也要适用于Fragment。

1、Activity与Fragment获取控件的不同

不管是控件注入还是事件绑定,都离不开最初始的一点,那就是控件的获取,即findViewById()。Activity获取控件只需要调用自己的findViewById()方法即可,但Fragment可不是这样,先来看看Fragment是如何设置布局的:

public class MyFragment extends Fragment {
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (mRootView == null) {
            mRootView = inflater.inflate(R.layout.fragment_my, null, false);
        }
        return mRootView;
    }
}

之所以Activity可以调用自己的findViewById()方法来获取控件,是因为Activity本身就是布局,而Fragment则不是这样的,Fragment的布局是它自己的一个View(mRootView),所以要获取Fragment中的控件,就必须调用mRootView的findViewById()方法来获取。

2、代码抽取

回顾ViewUtil的inject(Activity activity)方法,其实这个activity参数在这个方法中是担任两个角色的,一个是类(容器),另一个是布局。当作为容器这个角色时,是为了使用反射获得其中的字段和方法并赋值或执行。而作为布局这个角色时,是为了通过id获取布局控件(findViewById)。再看看Fragment,是不是有点端倪了呢?Fragment就是容器角色,而它的mRootView则是布局角色,所以,inject()的方法体可以这么抽:

private static Context context;
private static void injectReal(final Object container, Object rootView) {
    if (container instanceof Activity) {
        context = (Activity) container;
    } else if (container instanceof Fragment) {
        context = ((Fragment) container).getActivity();
    } else if (container instanceof android.app.Fragment) {
        context = ((android.app.Fragment) container).getActivity();
    }

    Class clazz = container.getClass();
    // 遍历属性
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        BindView bindView = field.getAnnotation(BindView.class);
        if (bindView != null) {
            try {
                field.setAccessible(true);
                field.set(container, findViewById(rootView, bindView.value()));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    // 遍历方法
    Method[] methods = clazz.getDeclaredMethods();
    for (final Method method : methods) {
        ClickView clickView = method.getAnnotation(ClickView.class);
        if (clickView != null) {
            findViewById(rootView, clickView.value()).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {                            
                        method.setAccessible(true);
                        method.invoke(container);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

private static View findViewById(Object layout, int resId) {
    if (layout instanceof Activity) {
        return ((Activity) layout).findViewById(resId);
    } else if (layout instanceof View) {
        return ((View) layout).findViewById(resId);
    }
    return null;
}

如此抽取之后,Activity与Fragment对应的inject()方法就可以共同使用这个injectReal()方法了:

// Activity
public static void inject(Activity activity) {
    injectReal(activity, activity);
}

// v4 Fragment
public static void inject(Fragment container, View rootView) {
    injectReal(container, rootView);
}

// app Fragment
public static void inject(android.app.Fragment container, View rootView) {
    injectReal(container, rootView);
}

相当清晰,而且是可以成功的,这里就不测试了。

最后贴下Demo地址

https://github.com/GitLqr/IocDemo

版权声明:本文为博主原创文章,未经博主允许不得转载。

spring ioc原理(看完后大家可以自己写一个spring)

转自http://blog.csdn.net/it_man/article/details/4402245 最近,买了本Spring入门书:spring In Action 。大致浏览了下感...
  • A__yes
  • A__yes
  • 2016年08月13日 20:15
  • 2671

spring ioc原理(看完后大家可以自己写一个spring)

控制反转/依赖注入   最近,买了本spring入门书:spring In Action 。大致浏览了下感觉还不错。就是入门了点。Manning的书还是不错的,我虽然不像哪些只看Manning书的...
  • shenghuaDay
  • shenghuaDay
  • 2016年05月13日 23:01
  • 1648

写一个自己的Spring框架——简单实现IoC容器功能

学习过Spring的同学都知道,Spring框架的核心就是IoC和AOP。Spring可以理解为一个工厂,负责对象的创建和对象间关系的维护。IoC即控制反转,简单点说就是原来的对象是在要使用之前通过在...
  • u010994169
  • u010994169
  • 2017年05月04日 14:09
  • 1807

Android中使用注解打造自己的IOC框架

一、采用注解来注入Activity中的布局和控件: a、了解注解-Annotation i、注解是程序中的一种标记也叫做元数据,在程序中加上注解相当于...
  • u014434965
  • u014434965
  • 2016年07月03日 12:33
  • 122

打造自己的框架之使用注解制作IOC组件

一、简述IoC和AOP可谓是后台开发入门必学的知识(Spring相关),但这两者都仅仅只是概念而已,并非具体技术实现,同样的,Android也可以使用IoC和AOP,之前已经写过如何在Android开...
  • c6E5UlI1N
  • c6E5UlI1N
  • 2017年12月04日 00:00
  • 162

(源码阅读)自己动手打造一套属于自己想IOC注解框架

阅读过Xutils3和butterKnife的源码之后,稍微比对了一下,俩个都写的非常nb~~           Xutils3通过源码发现,主要是通过反射去 获得属性,函数进行注入。       ...
  • GuiM_007
  • GuiM_007
  • 2017年06月23日 10:12
  • 144

Android 自己打造IOC注解框架

Android中IOC框架就是注入控件和布局或者说是设置点击监听,网上有很多成熟的注解框架例如xUtils,afinal,butterknife等等。你可能会问,既然已经有好的框架为何还要造轮子?因为...
  • Androidtalent
  • Androidtalent
  • 2017年05月07日 21:15
  • 532

打造自己的IOC注解框架------findViewById

前言:IOC注解,目前已经有非常许多类似的jar包可以用了, 比如XUtils以及Butterkniful等这些框架都可以用来进行相应的开发。然而, 写着篇博客的目的并非让你去打造...
  • ToastIt
  • ToastIt
  • 2017年04月19日 00:57
  • 215

自己动手打造一套IOC注解框架

1.概述  这是我们的内涵段子系统架构的第一期分享,希望大家可以先去了解一下这一期的内容:2017Android进阶之路与你同行。在介绍内涵段子整个项目的时候我们也说好了会分析系统源码设计模式,第三方...
  • z240336124
  • z240336124
  • 2017年02月04日 17:05
  • 578

自己动手写一个简单的IOC框架,使用注解绑定资源和事件

自己动手写一个简单的IOC框架,使用注解绑定资源和事件 程序员都是懒惰的;事实证明,懒人才能改变世界、创造未来。 几行代码就能搞定的一个简单IOC框架,使用方式如下:public class Ma...
  • jcde_code
  • jcde_code
  • 2016年08月08日 18:38
  • 89
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用注解打造自己的IOC框架
举报原因:
原因补充:

(最多只允许输入30个字)