打造自己的框架之使用注解制作IOC组件
来源:互联网 发布:vb与sql数据库连接 编辑:程序博客网 时间:2024/05/28 15:53
一、简述
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不是这么实现的,这只是我个人的想法而已。
控件注入:实际上是框架调用了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() { @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()方法了:
// Activitypublic static void inject(Activity activity) { injectReal(activity, activity);}// v4 Fragmentpublic static void inject(Fragment container, View rootView) { injectReal(container, rootView);}// app Fragmentpublic static void inject(android.app.Fragment container, View rootView) { injectReal(container, rootView);}
作者:CSDN_LQR
链接:http://www.jianshu.com/p/47f891f88fbf
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 打造自己的框架之使用注解制作IOC组件
- 使用注解打造自己的IOC框架
- Android中使用注解打造自己的IOC框架
- 打造自己的IOC注解框架------findViewById
- Android 自己打造IOC注解框架
- 打造自己的注解框架
- 制作自己的IOC框架
- 自己动手打造一套IOC注解框架
- (源码阅读)自己动手打造一套属于自己想IOC注解框架
- Android 仿ButterKnife写自己的IOC注解框架
- Android注解使用之通过annotationProcessor注解生成代码实现自己的ButterKnife框架
- Android注解使用之通过annotationProcessor注解生成代码实现自己的ButterKnife框架
- Spring框架的IOC之注解方式和AOP方式
- Spring框架使用纯注解的方式来配置IOC
- Spring探秘--开发自己的Spring框架之IOC
- 打造自己的php框架
- 打造自己的MVC框架
- Android打造自己的网络框架----Rxlifecycle的使用
- 12-4-2017周总结
- 题目:给一个不多于5位的正整数,要求:一、求它是几位数,二、逆序打印出各位数字。(java)
- oracle——归档日志
- Win10小技巧:如何将在此处打开命令改为CMD或Powershell?
- Android Studio 中的调试技巧
- 打造自己的框架之使用注解制作IOC组件
- ehcache rmi 动态节点,代码创建,分布式配置。
- 新安装的mysql无法远程连接问题
- 深度学习实践操作—从小白到大白(九):Caffe依赖包解析
- 题目:有5个人坐在一起,问第五个人多少岁?他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第三个人,又说比第2人大两岁。 *问第2个人,说比第一个人大两岁...
- 老人机轮询紧急拨号功能
- 笔记:基于winform的应用程序,发生方通过sendmessage发送消息接收方无法成功接收消息
- 20171128
- 题目:有一分数序列:2/1,3/2,5/3,8/5,13/8,21/13...求出这个数列的前20项之和。(java)