目前在 友友用车 项目中使用到了ButterKnife框架,这是一个通过注解的方式简化程序员代码量,自动映射xml布局文件与对象关系的框架。使用了这个框架之后很大程度上简化程序员的工作量,提高了工作效率,让程序员们不在编写findViewById之类的代码,其github上的地址 ButterKnife。 最近也研究了一下ButterKnife的实现原理,下面我就将讲解一下其实现的机制。
这里首先简单介绍一下他的使用方式;Android注解Butterknife的使用及代码分析
(一)使用方式
1)在activity中如何使用
@InjectView(R.id.feedback_content_edit) EditText feedContent; // 意见反馈功能 @InjectView(R.id.feedback_contact_edit) EditText feedContact; // 联系方式 @InjectView(R.id.b3_button) Button feedOk; // 提交按钮 @OnClick(R.id.open_car_door) public void openCarDorClick() { dosomething; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_feedback); ButterKnife.inject(this); initView(); }
是不是很简单?其实这就是butterKnife的基本用法了
通过注解(@InjectView)的方式将xml布局文件中定义的组件与Activity中定义的组件对象对应起来
通过注解(@OnClick)实现对布局文件的点击事件
在onCreate方法中通过静态方法:ButterKnife.inject(this);真正的将xml布局组件与activity中的组件对象映射起来;
2)在Fragment中如何使用
public class SimpleFragment extends Fragment { @InjectView(R.id.fragment_text_view) TextView mTextView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_simple, container, false); ButterKnife.inject(this, view); mTextView.setText("TextView in Fragment are found!"); return view; } }
注意这里需要调用ButterKnife的重载方法:
public static void inject(Object target, View source) { inject(target, source, ButterKnife.Finder.VIEW); }
3)在Adapter的ViewHolder是如何使用的
static class ViewHolder { @InjectView(R.id.person_name) TextView name; @InjectView(R.id.person_age) TextView age; @InjectView(R.id.person_location) TextView location; @InjectView(R.id.person_work) TextView work; public ViewHolder(View view) { ButterKnife.inject(this, view); } }
可以发现ButterKnife的主要使用作用是简化我们加载xml布局文件的写法,通过注解的方式将内存对象与布局文件绑定,这对于懒程序员真是一个偷懒的利器啊。
那么ButterKnife的实现原理是怎样的呢?我们能否自己实现一个简单的ButterKnife框架呢?带着这两个问题,我们开始今天的自定义ButterKnife之旅。
预备知识点:
java注解相关
下面是一段关于java中注解的说明:
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。
具体关于java注解方面的内容我们可以参考:Java注解Annotation详解
java反射相关
下面一段是百度百科中关于java反射的说明:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
具体关于java反射方面的内容我们可以参考:JAVA中的反射机制
在了解了java的注解和反射机制之后我们可以开始我们的自定义实现ButterKnife之旅了。
(二)绑定View组件;
自定义注解
@Target(ElementType.FIELD) // 标识改注解应用在成员变量上 @Retention(RetentionPolicy.RUNTIME) // 标识生命周期是运行时 public @interface ViewBinder { int id() default -1; }
@interface是用于自定义注解的,它里面定义的方法的声明不能有参数,也不能抛出异常,并且方法的返回值被限制为简单类型、String、Class、emnus、@interface,和这些类型的数组。
关于注解方面的内容,可自行查询学习;
创建View解析类
/** * View解析类,主要用于解析含有注解的View成员变量 */ public class ViewBinderParser { /** * 初始化解析 * @param object */ public static void inject(Object object) { // 创建解析对象并开始执行解析方法 ViewBinderParser parser = new ViewBinderParser(); try { parser.parser(object); } catch (Exception e) { e.printStackTrace(); } } /** * 开始执行解析方法 * @param object * @throws Exception */ public void parser(final Object object) throws Exception{ View view = null; // 获取目标对象字节码 final Class<?> clazz = object.getClass(); // 获取目标对象定义的成员变量 Field[] fields = clazz.getDeclaredFields(); // 循环遍历成员变量 for (Field field : fields) { if (field.isAnnotationPresent(ViewBinder.class)) { ViewBinder inject = field.getAnnotation(ViewBinder.class); int id = inject.id(); if (id < 0) { throw new Exception("id must not be null!!!"); } if (id > 0) { field.setAccessible(true); if (object instanceof View) { view = ((View) object).findViewById(id); } else if (object instanceof Activity) { view = ((Activity) object).findViewById(id); } field.set(object, view);// 给我们要找的字段设置id } } } } }
这里可以看出,主要是通过创建一个解析对象,然后通过反射方法获取定义的成员变量,判断是否注解属性,如果是的话,则将注解的id值通过findViewById获取并给View对象赋值;
在activity执行解析方法
/** * 测试Activity,主要用于测试通过注册实现View组件的初始化过程 */ public class MainActivity extends AppCompatActivity { @ViewBinder(id = R.id.button1) public Button button1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ViewBinderParser.inject(MainActivity.this); Log.i("tag", button1.getText() + " ####"); } }); } }
可以发现当我们点击Button2的时候我们执行了Log.i方法,并将button1的text打印出来了,正式我们在布局文件中初始化的时候设置的text字符串,从而说明我们通过注解的方式实现了button1组件的初始化工作,初始化过程可能有一些地方有待优化,但这个其实就是butterKnife框架实现组件初始化工作的核心流程。
既然我们已经实现了组件的初始化工作,下面我们将尝试的绑定View组件的事件点击事件。
(三)绑定View的OnClick事件
(一)自定义OnClick注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface OnClick { int id() default -1; }
和设置View组件的初始化注解类似,这里定义了组件点击事件OnClick的注解。
(二)创建解析类,解析方法
/** * 解析成员方法 * @param clazz * @throws Exception */ public void parserMethod(Class<?> clazz, final Object object) throws Exception{ View view = null; // 获取目标对象定义的成员方法 Method[] methods = clazz.getDeclaredMethods(); // 循环遍历成员变量 for (final Method method : methods) { if (method.isAnnotationPresent(OnClick.class)) { OnClick inject = method.getAnnotation(OnClick.class); int id = inject.id(); if (id < 0) { throw new Exception("id must not be null!!!"); } if (id > 0) { if (object instanceof View) { view = ((View) object).findViewById(id); } else if (object instanceof Activity) { view = ((Activity) object).findViewById(id); } view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { method.invoke(object, null); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }); } } } }
(三)在activity中测试解析
/** * 测试Activity,主要用于测试组件的点击事件 */ public class MainActivity extends AppCompatActivity { @ViewBinder(id = R.id.button1) public Button button1; /** * 执行button1的点击事件 */ @OnClick(id = R.id.button1) public void button1OnClick() { Log.i("tag", "这是一个测试的例子"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewBinderParser.inject(MainActivity.this); } }
可以发现我们在Activity中为button1OnClick方法绑定了组件id,这样就实现了button1组件的事件绑定定义,然后我们在Activity的onCreate方法执行了ViewBinderParser.inject方法,这样就真正实现了对组件的事件绑定,当我们点击button1的时候执行了Log.i方法,并打印:”这是一个测试的例子”,说明我们组件绑定操作执行成功了。O(∩_∩)O哈哈~
总结:
本文主要分析了一下ButterKnife的简单实用与实现原理,并实际实现了一个简单的ButterKnife框架。
已经将自己实现的ButterKnife框架上传至 我的github中 感兴趣的同学可以star和follow。
自己实现简单的ButterKnife框架主要涉及到了java中的注解和反射相关知识。
作者:一片枫叶_刘超