一、简述

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;@Overrideprotected 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() {@Overridepublic 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;@Overrideprotected 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() {@Overridepublic 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地址

github.com/GitLqr/IocD…

使用注解打造自己的IOC框架相关推荐

  1. 教你打造 Android 中的 IOC 框架

    1.概述 首先我们来吹吹牛,什么叫IoC,控制反转(Inversion of Control,英文缩写为IoC),什么意思呢? 就是你一个类里面需要用到很多个成员变量,传统的写法,你要用这些成员变量, ...

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

    上一篇博客我们已经带大家简单的吹了一下IoC,实现了Activity中View的布局以及控件的注入,如果你不了解,请参考:Android 进阶 教你打造 Android 中的 IOC 框架 [View ...

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

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39269193,本文出自:[张鸿洋的博客] 1.概述 首先我们来吹吹牛,什么叫Io ...

  4. Android进阶:框架打造之IOC框架

    什么是IOC IOC(Inversion of Control):控制反转.开发过程中类里面需要用到很多个成员变量 传统的写法:你要用这些成员变量的时候,那么你就new出来用 IOC的写法:你要用这些 ...

  5. spring根据名称获取bean_带你从零开始手写 spring ioc 框架,深入学习 spring 源码

    IoC Ioc 是一款 spring ioc 核心功能简化实现版本,便于学习和理解原理. 创作目的 使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过 ...

  6. 字节码插桩(javassist)之插入代码块|IOC框架(Hilt)之对象注入~研究

    Hilt对象注入 | javassist插桩 研究 Hilt对象注入 javassist字节码插桩 创建buildSrc的module 重写Transform 熟悉TransformInvocatio ...

  7. JackKnife开发专题-方便快捷的IOC框架

    最近在github上看到一个很好Android端IOC框架,觉得十分小巧方便,用起来的非常简单,因此推荐给大家,主人十分勤奋,一有时间就更新框架,励志让安卓开发以后变得超级简单,用代码改变世界.希望大 ...

  8. python ioc框架_IOC 实现原理

    IOC 也就是"控制反转"了,不过更流行的叫法是"依赖注入"(DI - Dependency Injection).听起来挺高深,其实实现起来并不复杂.下面就看 ...

  9. 打怪上分! 手写Spring ioc 框架,狠狠的“撸撸”Spring 源码

    估计很多朋友使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过. 你是不是有这样的感觉呢? 但是 spring 源码存在一个问题,那就是过于抽象,导致 ...

最新文章

  1. 2nd 四人小组项目的进一步分析
  2. 自然语言处理「迷惑行为大赏」,自然语言处理太难难难了!
  3. 与应用程序松耦合的报表开发组织
  4. sql like N'%%',N 是代表什么意思 及Like语句详解
  5. MySQL查看、创建和删除索引的方法
  6. MySQL Access denied错误的缘故情由
  7. c# 睡眠3秒_C#中的闭包和意想不到的坑
  8. 【第六期】拿不到offer全额退款 !人工智能工程师培养计划招生
  9. html css纵向滑动列表,JS实现的简洁纵向滑动菜单(滑动门)效果
  10. EntityFramework中常用的数据删除方式
  11. Nginx限制对代理HTTP资源的访问速率
  12. mysql备份至cos_宝塔面板网站文件/数据库定时同步备份至腾讯云COS设置
  13. 数据可视化的优势有哪些
  14. 华中农业大学C语言实验5答案,物理实验报告册(上册)-华中农业大学实验.pdf
  15. cocos2d-x3.0 关于CCAnimate 的一些资料
  16. 宝马、奔驰、路虎、捷豹、卡宴、杜比音效DTS音乐下载
  17. golang快速实现服务端网页截图
  18. python修改像素
  19. 软件测试工程师应该如何提高自己的技能?送给迷茫的你(共勉)
  20. Android技巧之相对高度使用

热门文章

  1. python列表切片口诀-python学习之“切片操作从入门到精通”
  2. python和c++哪个好找工作-升学为主的编程学python和C++哪个好?
  3. python处理excel-python处理Excel的简单示例
  4. UVa307 - Sticks(DFS+剪枝)
  5. java设计模式:prototype模式
  6. LeetCode Balanced Binary Tree
  7. android开发过程中的错误:the file dx.jar was not loaded from the SDK folder
  8. 【Oracle】如何在查询视图时使用索引
  9. 函数:使用递归实现阶乘
  10. 基于SpringMVC下的Rest服务框架搭建【1、集成Swagger】