Android 仿ButterKnife写自己的IOC注解框架

xiaoxiao2021-02-28  50

1 前言

一般我们在Android 的开发中,我们在Activity中都会涉及到大量的findViewById操作,这个时候我们可以采用一些开源的框架来帮我们省很多的苦力,例如大名鼎鼎的ButterKnife就是我经常使用的。ButterKnife是一种非常流行的注解框架。主要有以下几个优点:

1.强大的View绑定和Click事件处理功能,简化代码,提升开发效率 2.方便的处理Adapter里的ViewHolder绑定问题 3.运行时不会影响APP效率,使用配置方便(采用的是编译时注解) 4.代码清晰,可读性强

注意事项: 1 必须在setContentView()之后绑定 2 由于采用了编译时注解,一代工程大量使用了之后编译时间会增加不少

关于使用可以参考下面这篇博客

http://blog.csdn.net/itjianghuxiaoxiong/article/details/50177549

下面我们要仿照ButterKnife自己来实现一个IOC注解框架,毕竟我们自己要学会慢慢造轮子。

2 IOC注解框架思路

首先我们要明白,类似于ButterKnife注解框架,它的基本思想就是利用反射区拿到对应的注解值,这个值一般是R.id.xxx。然后根据这个域去设置对应的值,如果是方法就去设置这个View的onClickListener方法。步骤如下:

对于属性注入

Bind(R.id.xxx) View view

思路如下: 利用反射去 获取Annotation –> value –> findViewById –> 反射注入属性

对于事件注入:

@OnClick(R.id.xxx) public void submit(View view) { // TODO submit data to server... }

利用反射去 获取Annotation –> value –> findViewById –> setOnclickListener –> 反射执行该方法

当然,ButterKnifer比这个功能强大很多,但是基本思想是类似的。我们今天就来一个简单的例子,比如我们只思想findViewById和设置点击事件的功能。并且,我们是运行时注解,不是编译时注解,虽然会稍微影响性能,但是这个影响非常非常小,几乎可以忽略不计。

我们的绑定view就叫它ViewById吧,事件就叫OnClick。

3 IOC 属性注入ViewById

首先我们定义一个注解ViewById

package com.qiyei.baselibrary.ioc; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Email: 1273482124@qq.com * Created by qiyei2015 on 2017/5/2. * Version: 1.0 * Description: View注解的Annotation */ /** * @Target(ElementType.FIELD) 代表Annotation的位置 FIELD属性 TYPE类上 CONSTRUCTOR 构造函数上 * @Retention(RetentionPolicy.CLASS) 什么时候生效 CLASS编译时 RUNTIME运行时 SOURCE源码资源 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ViewById { /** * --> @ViewById(R.id.xxx) * @return */ int value(); }

这里需要有两点需要注意:一是指定生效的是Filed和RUNTIME,表示我们是需要注解在Field上的,并且在运行时生效。另一个就是定义一个变量int value();

接着,我们来定义注解处理器ViewUtils。

package com.qiyei.baselibrary.ioc; import android.app.Activity; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.util.Log; import android.view.View; import android.widget.Toast; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Email: 1273482124@qq.com * Created by qiyei2015 on 2017/5/2. * Version: 1.0 * Description: */ public class ViewUtils { private static final String TAG = ViewUtils.class.getSimpleName(); /** * Activity中使用 * @param activity */ public static void inject(Activity activity){ inject(new ViewFinder(activity)); } /** * 实际处理者 * @param finder */ private static void inject(ViewFinder finder){ injectField(finder); } /** * 注入域 * @param finder */ private static void injectField(ViewFinder finder){ //获取类里面所有的属性 Class<?> clazz = finder.findClass(); Field[] fields = clazz.getDeclaredFields(); Log.d(TAG,"name:" + clazz.getSimpleName() + " fields.length:" + fields.length); //依次遍历并获取域上的ViewById注解 for (Field field : fields){ //获取ViewById注解 ViewById viewById = field.getAnnotation(ViewById.class); if (viewById != null){ //获取id值,对应R.id.xxx int id = viewById.value(); if (id != 0){ View view = finder.finderViewById(id); if (view != null){ //能够注入所有修饰符 private public protect field.setAccessible(true); try { //动态的注入找到的View field.set(finder.finderObject(),view); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } } }

这里可以看到,我们首先定义了一个方法inject(Activity activity),这个方法我们会调用一个我们的ViewUtils辅助类。ViewFinder主要是辅助我们找到View,Object(反射时调用的对象的),class等

/** * Email: 1273482124@qq.com * Created by qiyei2015 on 2017/5/2. * Version: 1.0 * Description: View的findViewById的辅助类 */ public class ViewFinder { /** * 所保存的activity实例 */ private Activity mActivity; /** * class实例 */ private Class<?> mClass; public ViewFinder(Activity activity){ mActivity = activity; mClass = activity.getClass(); } /** * 根据id找到对应的View * @param id * @return */ public View finderViewById(int id){ return mActivity != null ? mActivity.findViewById(id) : null; } /** * 返回对应的对象 * @return */ public Object finderObject(){ if (mActivity != null){ return mActivity; } return null; } /** * 返回对应的Class实例 * @return */ public Class<?> findClass(){ return mClass; } }

ViewFinder主要有以下几个功能: 1 找到View 2 找到Object 3 找到Object对应的class

整个IOC框架比较重要的点: 1 在反射的时候,我们需要调用getDeclaredFields()获取所有的变量,包括public 和private的 2 在ViewFinder中对mClass赋值一定是activity.getClass()而不是Activity.class。因为我们我要获取的反射对象的类是我们的自定义Activity(继承Activity)。如果传入的是Activity类型的class将会报空指针,不信可以试试。

我们在MainActivty 中代码如下:

public class MainActivity extends AppCompatActivity { static { System.loadLibrary("native-lib"); } @ViewById(R.id.test_tv1) private TextView mTextView1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewUtils.inject(this); // Example of a call to a native method TextView tv = (TextView) findViewById(R.id.sample_text); //tv.setText(stringFromJNI()); tv.setText(""); mTextView1.setText("这是IOC注解生成的"); } /** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */ public native String stringFromJNI(); }

因为我建工程的时候选择了支持c++,所以会多了一个c++的提示消息。将它屏蔽了。效果如下:

4 IOC 事件注入OnClick

有了前面注入域的例子,我们要实现点击事件的注入也很简单,步骤如下: 1 通过反射拿到所有的方法 2 根据注解OnClick找到这个方法 3 根据id值找到对应的View 4 设置这个View的onClickListener 5 在onClickListener中反射调用这个方法

OnClick注解定义如下:

package com.qiyei.baselibrary.ioc; /** * Email: 1273482124@qq.com * Created by qiyei2015 on 2017/5/3. * Version: 1.0 * Description: 方法上设置点击事件的注解 */ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Target(ElementType.FIELD) 代表Annotation的位置 FIELD属性 TYPE类上 CONSTRUCTOR 构造函数上 * @Retention(RetentionPolicy.CLASS) 什么时候生效 CLASS编译时 RUNTIME运行时 SOURCE源码资源 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface OnClick { int[] value(); }

因为我们要支持多个id绑定到一个方法上,所以定义了一个数组变量

ViewUtils部分代码如下:

/** * 实际处理者 * @param finder */ private static void inject(ViewFinder finder){ injectField(finder); injectEvent(finder); } /** * 注入事件 * @param finder */ private static void injectEvent(ViewFinder finder){ //获取所有的方法 Class<?> clazz = finder.findClass(); Method[] methods = clazz.getDeclaredMethods(); //依次遍历并获取方法的ViewById注解 for (Method method : methods){ //获取OnClick注解 OnClick onClick = method.getAnnotation(OnClick.class); if (onClick != null){ //获取id值,对应R.id.xxx int[] ids = onClick.value(); for (int id : ids){ View view = finder.finderViewById(id); if (view != null){ view.setOnClickListener(new DeclaredOnClickListener(method,finder.finderObject()); } } } } } /** * 设置OnClickListener的监听器 */ private static class DeclaredOnClickListener implements View.OnClickListener{ /** * 反射时方法调用的对象 */ private Object mObject; /** * 反射的方法 */ private Method mMethod; public DeclaredOnClickListener(Method method , Object object){ mMethod = method; mObject = object; } @Override public void onClick(View v) { //反射该方法 try { mMethod.setAccessible(true); mMethod.invoke(mObject,v); } catch (Exception e) { e.printStackTrace(); try { mMethod.invoke(mObject,new Object[]{}); } catch (IllegalAccessException e1) { e1.printStackTrace(); } catch (InvocationTargetException e1) { e1.printStackTrace(); } } } } ....

注意:这里支持了注解方法有参和无参两种类型,也就是说我们的注解方法支持一下两种写法:

@OnClick(R.id.test_tv1) private void textClick(View view){ Toast.makeText(this,"点击了:" + view.getId(),Toast.LENGTH_LONG).show(); } @OnClick(R.id.test_tv1) private void textClick(){ Toast.makeText(this,"点击了:",Toast.LENGTH_LONG).show(); }

接下来我们在MainActivity 中修改如下:

@OnClick(R.id.test_tv1) private void textClick(View view){ Toast.makeText(this,"点击了:" + view.getId(),Toast.LENGTH_LONG).show(); }

运行效果如下:

5 IOC功能扩展与总结

上面我们已经初步的建立了一个IOC框架的原型,但是还远远不够完善,我们需要再完善一下。

功能扩展 1 支持Fragment与View中使用,因此我们需要增加两个方法,并且需要修改ViewFinder 2 增加监测网络的功能两个,比如有些需求中,没有网络的条件下我们是无法去点击某个按钮的,因此需要在点击事件中增加监测网络的功能 3 增加生成ViewHolder 的功能 4 增加其他事件,例如长按事件,双击事件等

这些功能会逐步的添加到IOC框架中,欢迎访问我的githubhttps://github.com/qiyei2015/EssayJoke

总结: IOC框架主要是省去了我们去findViewById和设置点击事件。所用的知识点主要是反射和注解。我们可以参照XUtils,ButterKnife等框架去完善它,慢慢的提高我们自己的编程能力

参考 http://www.jianshu.com/p/2570c2de028b http://blog.csdn.net/itjianghuxiaoxiong/article/details/50177549

转载请注明原文地址: https://www.6miu.com/read-56964.html

最新回复(0)