使用注解打造自己的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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/CSDN_LQR/article/details/78658557

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

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

打造自己的注解框架

打造自己的注解框架 github上有很多注解框架,加个注解就会完成各种各样的任务,这样的方法 既减少了代码量,又方便项目的统一管理,清晰明了 今天,我就用java中的反射+自定义注解,为...
  • u012926924
  • u012926924
  • 2015-09-04 10:06:08
  • 989

Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39269193,本文出自:【张鸿洋的博客】1、概述首先我们来吹吹牛,什么叫IoC,...
  • lmj623565791
  • lmj623565791
  • 2014-09-18 09:25:58
  • 68717

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

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

自己动手写一个web框架(一):实现IOC与MVC

最近这段时间在研究怎么写一个web框架,自己也动手写了个雏形,在这个过程中受益良多。写个文章记录下思路。另外,强烈推荐《架构探险:从零开始写Java Web框架》这本书,虽然我现在只看了第三章和第四章...
  • u012422829
  • u012422829
  • 2015-11-12 17:40:13
  • 1656

Android主流IOC框架浅析(Java注解反射原理)

Android主流IOC框架浅析
  • u010375364
  • u010375364
  • 2016-07-06 23:33:22
  • 1362

Spring中IOC配置xml实现和IOC注解实现

1.什么是spring spring 是分层的JavaSE/EE轻量级应用开源框架,已控制反转IOC和面向切面编程AOP为核心,提供了展现层SpringMVC, 和持久层Srping JDBC以及事务...
  • u014292162
  • u014292162
  • 2016-08-22 16:10:22
  • 4304

Android 自己打造IOC注解框架

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

自己动手编写IOC框架

转自: http://www.cnblogs.com/rongdi/p/4122289.html?utm_source=tuicool&utm_medium=referral终于到了激动人心的时刻了...
  • d12345678a
  • d12345678a
  • 2016-12-28 16:26:25
  • 483

SpringMVC使用注解方式配置IOC

IoC是什么Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接...
  • wtopps
  • wtopps
  • 2016-08-05 16:56:03
  • 1598
收藏助手
不良信息举报
您举报文章:使用注解打造自己的IOC框架
举报原因:
原因补充:

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