简单实现ButterKnife(运行时注解)
程序员文章站
2022-06-16 17:30:19
...
现在ButterKnife其实是基于编译时注解实现的,很大原因是出于对java反射机制效率的诟病,这里我主要是想使用运行时注解来实现ButterKnife的功能。在后面还会有一篇关于ButterKnife(8.7.0)的最新版本编译时注解的实现方式:简单实现ButterKnife(编译时注解)。
一、运行时注解
java运行时注解是基于java的反射机制,就是在运行时,动态的获取类的方法、变量等信息以及进行相关操作的一种机制。但是这种机制效率很低,因此大部分人都对此不太乐观,但是就目前Android机器的性能来说,这些都不是太大问题吧。下面就以ButterKnife中的部分功能为例用运行时注解来实现。
二、代码实现
1. 新建注解类
package com.example.davidchen.blogdemo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 绑定view控件注解
* Created by DavidChen on 2017/7/25.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
这里也没啥可多说的,Retention指定注解保留到Runtime时,Target指定注解针对变量。注解里接受一个int值,即控件id。
2. 注解处理器
package com.example.davidchen.blogdemo;
import android.app.Activity;
import android.view.View;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 工具类
* Created by DavidChen on 2017/7/25.
*/
public class ButterKnife {
public static void bind(final Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
Field[] fields = clazz.getFields();
for (Field field : fields) {
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null) {
int id = bindView.value();
if (id != -1) {
View view = activity.findViewById(id);
try {
field.set(activity, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
这里通过反射,先获取到activity的类中所有public变量,然后遍历获取含有BindView注解的变量,之后获取注解值,通过findViewById来获取view并通过field.set来给指定对象activity的改变量赋值。这样就ok了。
3. 测试
package com.example.davidchen.blogdemo;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
/**
* 测试activity
* Created by DavidChen on 2017/7/25.
*/
public class TestActivity extends AppCompatActivity {
@BindView(R.id.btn_enter)
public Button btn_enter;
@BindView(R.id.tv_result)
public TextView tv_result;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
ButterKnife.bind(this);
btn_enter.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv_result.setText("注入成功");
}
});
}
}
测试结果截图:
4. 添加OnClickListener
同样,view的点击事件我们可以通过注解来实现。
注解类:
package com.example.davidchen.blogdemo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 绑定点击事件
* Created by DavidChen on 2017/7/25.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
int[] value();
}
处理器类:
package com.example.davidchen.blogdemo;
import android.app.Activity;
import android.view.View;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 工具类
* Created by DavidChen on 2017/7/25.
*/
public class ButterKnife {
public static void bind(final Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
Field[] fields = clazz.getFields();
injectView(activity, fields);
Method[] methods = clazz.getMethods();
injectOnclick(activity, methods);
}
private static void injectOnclick(final Activity activity, Method[] methods) {
for (final Method method : methods) {
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
int ids[] = onClick.value();
for (int id : ids) {
if (id != -1) {
View view = activity.findViewById(id);
if (view != null) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
method.invoke(activity, v);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
}
}
}
}
}
}
private static void injectView(Activity activity, Field[] fields) {
for (Field field : fields) {
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null) {
int id = bindView.value();
if (id != -1) {
View view = activity.findViewById(id);
try {
field.set(activity, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
测试类:
package com.example.davidchen.blogdemo;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
/**
* 测试activity
* Created by DavidChen on 2017/7/25.
*/
public class TestActivity extends AppCompatActivity {
@BindView(R.id.btn_enter)
public Button btn_enter;
@BindView(R.id.tv_result)
public TextView tv_result;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
ButterKnife.bind(this);
}
@OnClick({R.id.btn_enter, R.id.tv_result})
public void click(View view) {
switch (view.getId()) {
case R.id.btn_enter:
tv_result.setText("注入成功");
break;
case R.id.tv_result:
Toast.makeText(TestActivity.this, "guin", Toast.LENGTH_SHORT).show();
break;
}
}
}
结果截图:
三、总结
这里其实没太多的难点,熟悉注解及相关api即可。但是,正如开头所说,毕竟其效率不太高,后面写一篇关于编译时注解实现butterknife。
推荐阅读
-
SpringBoot使用AOP+注解实现简单的权限验证的方法
-
浅谈基于SpringBoot实现一个简单的权限控制注解
-
啰嗦的 java,简洁的 lombok —— lombok 的使用及简单实现单例模式注解
-
使用springboot注解实现aop也太简单了吧
-
Springboot自定义注解实现简单的接口权限控制,替代Shiro/SpringSecurity
-
SpringBoot使用自定义注解实现简单参数加密解密(注解+HandlerMethodArgumentResolver)
-
使用注解+springEL表达式+Aspect实现简单的缓存处理
-
MyBatis使用注解实现简单的增删改查
-
自定义运行时注解、编译时注解[ButterKnife原理探析]
-
简单实现ButterKnife(运行时注解)