使用注解打造自己的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;@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不是这么实现的,这只是我个人的想法而已。
- 控件注入:实际上是框架调用了activity的findViewById()方法拿到id对应的控件,再通过反射的方式,对控件(类字段)进行赋值。
- 事件绑定:实际上也是框架调用了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框架相关推荐
- 教你打造 Android 中的 IOC 框架
1.概述 首先我们来吹吹牛,什么叫IoC,控制反转(Inversion of Control,英文缩写为IoC),什么意思呢? 就是你一个类里面需要用到很多个成员变量,传统的写法,你要用这些成员变量, ...
- Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)
上一篇博客我们已经带大家简单的吹了一下IoC,实现了Activity中View的布局以及控件的注入,如果你不了解,请参考:Android 进阶 教你打造 Android 中的 IOC 框架 [View ...
- Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39269193,本文出自:[张鸿洋的博客] 1.概述 首先我们来吹吹牛,什么叫Io ...
- Android进阶:框架打造之IOC框架
什么是IOC IOC(Inversion of Control):控制反转.开发过程中类里面需要用到很多个成员变量 传统的写法:你要用这些成员变量的时候,那么你就new出来用 IOC的写法:你要用这些 ...
- spring根据名称获取bean_带你从零开始手写 spring ioc 框架,深入学习 spring 源码
IoC Ioc 是一款 spring ioc 核心功能简化实现版本,便于学习和理解原理. 创作目的 使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过 ...
- 字节码插桩(javassist)之插入代码块|IOC框架(Hilt)之对象注入~研究
Hilt对象注入 | javassist插桩 研究 Hilt对象注入 javassist字节码插桩 创建buildSrc的module 重写Transform 熟悉TransformInvocatio ...
- JackKnife开发专题-方便快捷的IOC框架
最近在github上看到一个很好Android端IOC框架,觉得十分小巧方便,用起来的非常简单,因此推荐给大家,主人十分勤奋,一有时间就更新框架,励志让安卓开发以后变得超级简单,用代码改变世界.希望大 ...
- python ioc框架_IOC 实现原理
IOC 也就是"控制反转"了,不过更流行的叫法是"依赖注入"(DI - Dependency Injection).听起来挺高深,其实实现起来并不复杂.下面就看 ...
- 打怪上分! 手写Spring ioc 框架,狠狠的“撸撸”Spring 源码
估计很多朋友使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过. 你是不是有这样的感觉呢? 但是 spring 源码存在一个问题,那就是过于抽象,导致 ...
最新文章
- 2nd 四人小组项目的进一步分析
- 自然语言处理「迷惑行为大赏」,自然语言处理太难难难了!
- 与应用程序松耦合的报表开发组织
- sql like N'%%',N 是代表什么意思 及Like语句详解
- MySQL查看、创建和删除索引的方法
- MySQL Access denied错误的缘故情由
- c# 睡眠3秒_C#中的闭包和意想不到的坑
- 【第六期】拿不到offer全额退款 !人工智能工程师培养计划招生
- html css纵向滑动列表,JS实现的简洁纵向滑动菜单(滑动门)效果
- EntityFramework中常用的数据删除方式
- Nginx限制对代理HTTP资源的访问速率
- mysql备份至cos_宝塔面板网站文件/数据库定时同步备份至腾讯云COS设置
- 数据可视化的优势有哪些
- 华中农业大学C语言实验5答案,物理实验报告册(上册)-华中农业大学实验.pdf
- cocos2d-x3.0 关于CCAnimate 的一些资料
- 宝马、奔驰、路虎、捷豹、卡宴、杜比音效DTS音乐下载
- golang快速实现服务端网页截图
- python修改像素
- 软件测试工程师应该如何提高自己的技能?送给迷茫的你(共勉)
- Android技巧之相对高度使用
热门文章
- python列表切片口诀-python学习之“切片操作从入门到精通”
- python和c++哪个好找工作-升学为主的编程学python和C++哪个好?
- python处理excel-python处理Excel的简单示例
- UVa307 - Sticks(DFS+剪枝)
- java设计模式:prototype模式
- LeetCode Balanced Binary Tree
- android开发过程中的错误:the file dx.jar was not loaded from the SDK folder
- 【Oracle】如何在查询视图时使用索引
- 函数:使用递归实现阶乘
- 基于SpringMVC下的Rest服务框架搭建【1、集成Swagger】