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

一、简述

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自己动手做框架是一种非常有挑战性的任务,尤其是对于 ORM(对象关系映射)、MVC(模型-视图-控制器)和IOC(控制反转)这三个框架而言。以下是对这三个框架的简要介绍,以及它们的实现过程。 首先,ORM(对象关系映射)框架是用于将数据库中的表映射为对象的一种技术。在实现ORM框架时,需要定义对象和表之间的映射关系,并提供相应的API来进行数据库的增删改查操作。还需要处理对象之间的关联关系(如一对一、一对多等),并提供事务管理、缓存等功能。 其次,MVC(模型-视图-控制器)框架是一种用于开发Web应用程序的架构模式,将应用程序分为模型、视图和控制器三层。在实现MVC框架时,需要定义模型、视图和控制器的接口,并提供相应的实现。模型负责处理数据逻辑,视图负责展示用户界面,而控制器则负责接收用户请求并进行处理,最后将结果返回给视图进行展示。 最后,IOC(控制反转)框架是一种通过依赖注入来管理对象之间的依赖关系的技术。在实现IOC框架时,需要定义依赖注入的规则,并提供相应的实现。可以使用反射机制来自动实例化对象,并将依赖的对象注入到目标对象中。还需要提供对象的生命周期管理(如单例模式、原型模式等)和对象的配置方式(如XML配置、注解配置等)。 总结来说,自己动手实现ORM、MVC和IOC框架需要掌握相关的技术和知识,并具备良好的设计和编码能力。除了理论知识外,还需要具备实际项目的经验,以便能够更好地设计和实现这些框架。同时,也需要不断学习和改进,跟上技术的发展潮流,以确保框架的高效和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值