Android 注解、反射动态代理实现 Butterknife
2019-06-19 本文已影响52人
程序员阿兵
实现的功能
实现效果
- setContentView
import android.content.Intent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
// 项目中,很多的界面,Activity。BaseActivity
// 抽取很多层的父类
// setContentView(int)
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@InjectView(R.id.btn)
private Button btn;
@InjectView(R.id.tv)
private TextView tv;
@Override
protected void onResume() {
super.onResume();
// Button btn = findViewById(R.id.btn);
Toast.makeText(this, btn.getText().toString(), Toast.LENGTH_SHORT).show();
}
- BindView
@InjectView(R.id.btn)
private Button btn;
@InjectView(R.id.tv)
private TextView tv;
- OnClick 、OnLongClick
@OnClick({R.id.btn, R.id.tv}) // 有可能注解值是没有控件注入赋值的
public void click(View btn) {
switch (btn.getId()) {
case R.id.btn:
// Toast.makeText(this, "btn click", Toast.LENGTH_SHORT).show();
startActivity(new Intent(this, RViewActivity.class));
break;
case R.id.tv:
Toast.makeText(this, "tv click", Toast.LENGTH_SHORT).show();
break;
}
}
@OnLongClick({R.id.btn, R.id.tv})
public boolean longClick(View btn) {
switch (btn.getId()) {
case R.id.btn:
Toast.makeText(this, "btn longClick", Toast.LENGTH_SHORT).show();
break;
case R.id.tv:
Toast.makeText(this, "tv longClick", Toast.LENGTH_SHORT).show();
break;
}
return true;
}
- RecyclerView 条目点击
@OnItemClick(R.id.recyclerView)
public void itemClick(View view, UserInfo info, int position) {
Toast.makeText(this, "OnItemClick\n" + info.getPassword(), Toast.LENGTH_SHORT).show();
}
@OnItemLongClick(R.id.recyclerView)
public boolean itemLongClick(View view, UserInfo info, int position) {
Toast.makeText(this, "OnItemLongClick\n" + info.getPassword(), Toast.LENGTH_SHORT).show();
return true;
}
代码实现
- setContentView
- 添加注解:
- 申明注解作用在类上:
@Target(ElementType.TYPE) // 该注解作用于类,接口或者枚举类型上
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,jvm加载时可以通过反射获取到该注解的内容
public @interface ContentView {
// int类型布局
int value();
}
- 实现添加 Layout
// 布局的注入
private static void injectLayout(Activity activity) {
// 获取类
Class<? extends Activity> clazz = activity.getClass();
// 获取类的注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView != null) {
// 获取注解的值(R.layout.xxxmain)
int layoutId = contentView.value();
try {
// 获取指定的方法(setContentView)坑:getMethod
Method method = clazz.getMethod("setContentView", int.class);
// 执行方法
method.invoke(activity, layoutId);
// 另一种写法:
// activity.setContentView(layoutId);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
上面的代码思路为:注入activity,拿到 activity的class.获取ContentView这个注解类,拿到注解值 通过上面的class 进行反射 填充 layout 的id
- BindView
- 添加注解
- 作用于属性上
@Target(ElementType.FIELD) // 属性上
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
int value();
}
- 获取注解 findViewById
// 控件的注入
private static void injectViews(Activity activity) {
// 获取类
Class<? extends Activity> clazz = activity.getClass();
// 获取类的所有属性
Field[] fields = clazz.getDeclaredFields();
// 循环,拿到每个属性
for (Field field : fields) {
// 获得属性上的注解
InjectView injectView = field.getAnnotation(InjectView.class);
// 获取注解的值
if (injectView != null) { // 并不是所有的属性都有注解
int viewId = injectView.value();
// 获取findViewById方法,并执行
try {
Method method = clazz.getMethod("findViewById", int.class);
Object view = method.invoke(activity, viewId); //变量付值
// 另一种写法:
// view = activity.findViewById(viewId);
// 还有坑:访问修饰符
field.setAccessible(true); // 设置访问权限private
field.set(activity, view); // 属性的值赋给控件,在当前Activity
// 颜色、属性等赋值过程参考如上实现
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
上面的主要思路是: 通过外面传人的activity 类,获取当前类下所有的属性,遍历所有的属性。拿到属性上的注解,获取注解值。获取当前类下findViewById,通过反射获取当前的属性View ,最终把值赋给当前控件。
- 事件监听
- 添加注解
- 作用在方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class, callBackListener = "onClick")
public @interface OnClick {
int[] value();
}
具体作用在对应的类下对应的方法上:
@Target(ElementType.ANNOTATION_TYPE) // 放在注解的上面
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
// 事件的三个成员
// 1、set方法名 setOnclickListener()
String listenerSetter();
// 2、监听的对象 View.OnclickListener
Class<?> listenerType();
// 3、回调方法 onclick(View view)
String callBackListener();
}
- 实现
// 事件的注入
public static void injectEvents(Activity activity) {
// 获取类
Class<? extends Activity> clazz = activity.getClass();
// 获取类的所有方法
Method[] methods = clazz.getDeclaredMethods();
// 遍历方法
for (Method method : methods) {
// 获取每个方法的注解(多个控件id)
Annotation[] annotations = method.getAnnotations();
// 遍历注解
for (Annotation annotation : annotations) {
// 获取注解上的注解
// 获取OnClick注解上的注解类型
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType != null) {
// 通过EventBase指定获取
EventBase eventBase = annotationType.getAnnotation(EventBase.class);
if (eventBase != null) { // 有些方法没有EventBase注解
// 事件3大成员
String listenerSetter = eventBase.listenerSetter(); //setOnClickListener
Log.e("--main--","listenerSetter:"+listenerSetter);
Class<?> listenerType = eventBase.listenerType(); //View.OnClickListener.class
String callBackListener = eventBase.callBackListener(); //onClick
Log.e("--main--","callBackListener:"+callBackListener);
// 获取注解的值,执行方法再去获得注解的值
try {
// 通过annotationType获取onClick注解的value值
Method valueMethod = annotationType.getDeclaredMethod("value");
// 执行value方法获得注解的值
int[] viewIds = (int[]) valueMethod.invoke(annotation);
// 代理方式(3个成员组合)
// 拦截方法
// 得到监听的代理对象(新建代理单例、类的加载器,指定要代理的对象类的类型、class实例)
ListenerInvocationHandler handler = new ListenerInvocationHandler(activity);
// 添加到拦截列表里面
handler.addMethod(callBackListener, method);
// 监听对象的代理对象
// ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的
// Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
// InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法
Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(),
new Class[]{listenerType}, handler);
// 遍历注解的值
for (int viewId : viewIds) {
// 获得当前activity的view(赋值)
View view = activity.findViewById(viewId);
// 获取指定的方法
Method setter = view.getClass().getMethod(listenerSetter, listenerType); //获取 setOnclickListener 里面的参数 View.Onclick
// 执行方法
setter.invoke(view, listener); //执行setOnclickListener里面的回调 onclick方法
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
}
上面的思路:注入当前类,获取类下所有的方法,获取对应方法下的注解方法。获取注解上面的注解,然后拿到注解上的三大类型,拿到最外层注解上id,通过遍历找到对应的View,通过反射执行。最后通过动态代理去拦截 执行回调的方法。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
// 将回调的onClick方法拦截,执行我们自己自定义的方法(aop概念)
public class ListenerInvocationHandler implements InvocationHandler {
// 需要拦截的对象
private Object target;
// 需要拦截的对象键值对
private HashMap<String, Method> methodHashMap = new HashMap<>();
public ListenerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method 等于拦截的onclick
if (target != null) {
// 获取需要拦截的方法名
String methodName = method.getName(); // 假如是onClick
// 重新赋值,将拦截的方法换为show
method = methodHashMap.get(methodName); // 执行拦截的方法,show
if (method != null) {
return method.invoke(target, args);
}
}
return null;
}
/**
* 将需要拦截的方法添加
* @param methodName 需要拦截的方法,如:onClick()
* @param method 执行拦截后的方法,如:show()
*/
public void addMethod(String methodName, Method method) {
methodHashMap.put(methodName, method);
}
}
你明白了吗。