一、实现的功能介绍
通过注解手写Xutils的功能:
1.通过注解注入SetContentView方法。
2.通过注解注入成员变量的findViewById().
3.通过注解注入点击事件。
二、实现原理
1.通过反射获取到各个位置的注解。
2.根据注解的参数在注入类里通过this注入到相应的位置上。
三、MainActivity的调用
@SetContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
//注入注解
InjectTool.inject(this);
Toast.makeText(this,tv.getText(),Toast.LENGTH_SHORT).show();
}
@Click(R.id.tv)
public void show(){
Toast.makeText(MainActivity.this,"我是点击事件",Toast.LENGTH_SHORT).show();
}
}
四、注入函数和注解的编写。
BindView注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value() default -1;
}
Click注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Click {
int value() default -1;
}
SetContentView注解
public @interface SetContentView {
int value() default -1;
}
注入类InjectTool.java
public class InjectTool {
private static final String TAG = InjectTool.class.getSimpleName();
public static void inject(Object mainActivity) {
injectSetContentView(mainActivity);
injectBindView(mainActivity);
injectMethord(mainActivity);
}
/**
* 点击事件的注入
* @param mainActivity
*/
private static void injectMethord(Object mainActivity) {
Class mClass = mainActivity.getClass();
Method[] methods = mClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
methods[i].setAccessible(true);
Log.e("lzl",methods[i].getName());
Click click = methods[i].getAnnotation(Click.class);
if(click ==null){
continue;
}
int value = click.value();
//反射获取到view
try {
//1.先获取到得到view的方法
Method method = mClass.getMethod("findViewById",int.class);
//2.调用方法,去获取到VIEW控件
Object object = method.invoke(mainActivity,value);
//3.获取view,直接设置setOnclickListener.
int finalI = i;
Log.e("lzl","设置了监听");
((View)object).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
methods[finalI].invoke(mainActivity);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
//4.
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
/**
* 绑定控件
* @param mainActivity
*/
private static void injectBindView(Object mainActivity) {
Class<?> aClass = mainActivity.getClass();
//因为控件是对象的成员变量,所以先获取到所有的成员变量,
//又因为是成员变量可以是private所以需要用declared方法。
Field[] files = aClass.getDeclaredFields();
for (int i = 0; i < files.length; i++) {
files[i].setAccessible(true);//让jvm不要去管private 修饰的
BindView annotation = files[i].getAnnotation(BindView.class);
//获取我们需要的注解内容
if(annotation==null){
continue;
}
int viewId = annotation.value();
try {
//获取方法
Method findview = aClass.getMethod("findViewById",int.class);
//调用方法。
Object object = findview.invoke(mainActivity,viewId);
//给变量设置值。
files[i].set(mainActivity,object);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
/**
* 把布局绑定到this中
* @param mainActivity
*/
private static void injectSetContentView(Object mainActivity) {
Class<?> aClass = mainActivity.getClass();
//拿到Class上的注解
SetContentView setContentView = aClass.getAnnotation(SetContentView.class);
if(setContentView == null){
Log.d(TAG,"ContentView 为null");
return;
}
//拿到布局ID
int id = setContentView.value();
//反射设置id
try {
Method method = aClass.getMethod("setContentView",int.class);
method.invoke(mainActivity,id);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
五、兼容版本的代码
通过注解的注解来区分不同的点击事件,扩展性强。
调用代码
@OnClickCommon(R.id.bt1)
public void bt1(){
Toast.makeText(MainActivity.this,"点击事件运行",Toast.LENGTH_SHORT).show();
}
@OnLongClickCommon(R.id.bt2)
public Boolean bt2(){
Toast.makeText(MainActivity.this,"长按事件运行",Toast.LENGTH_SHORT).show();
return false;
}
注解的注解
/**
* 注解的注解
*
* 事件的三要素:1.订阅方式:是setOnClickListener() 、 setOnLongClickListener()
* 2. 事件原对象:view.OnClickListener()、 View.OnLongClickListener()
* 3. 事件执行方法:onClick、OnLongClick 最终的事件消费。
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BaseCommon {
String setCommonListener();
Class setCommonObject();
String CallBackMethord();
}
点击事件的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@BaseCommon(setCommonListener = "setOnClickListener",
setCommonObject = View.OnClickListener.class,
CallBackMethord = "onClick")
public @interface OnClickCommon {
int value() default -1;
}
长按事件的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@BaseCommon(setCommonListener = "setOnLongClickListener",
setCommonObject = View.OnLongClickListener.class,
CallBackMethord = "onLongClick")
public @interface OnLongClickCommon {
int value() default -1;
}
注入的方式
通过反射和动态代理实现。
private static void injectCommon(Object mainActivity) {
//1.获取Activity的class对象
Class<?> mMainActivityClass = mainActivity.getClass();
//2.获取Class对象里的所有方法。
Method[] declaredMethods = mMainActivityClass.getDeclaredMethods();
//3.遍历所有方法
for (Method declaredMethod : declaredMethods) {
//4.设置该方法不进行java访问检查。
declaredMethod.setAccessible(true);
//5.获取方法上的注解。
Annotation[] annotations = declaredMethod.getAnnotations();
//6.遍历方法上的注解,因为可能有多个。
for (Annotation annotation : annotations) { // 2
// 7.获取当前注解的父注解 是否有 OnBaseCommon
Class<? extends Annotation> annotationType = annotation.annotationType();
BaseCommon onBaseCommon = annotationType.getAnnotation(BaseCommon.class);
if (null == onBaseCommon) {
Log.d(TAG, "OnBaseCommon is null");
continue; // 结束本次,继续下一次
}
// 终于找到了 此注解包含 OnBaseCommon
//8. 获取事件三要数
String setCommonListener = onBaseCommon.setCommonListener();
Class setCommonObjectListener = onBaseCommon.setCommonObject();
String callbackMethod= onBaseCommon.CallBackMethord(); // 感觉3在,多个方法下才有用
// 9.动态代理完成,
try {
// 由于上面无法明确 子注解 annotationType获取到子注解
// 获取 @OnClickCommon(R.id.bt_t1) value == R.id.bt_t1
//获取子注解的值。通过反射
Method valueMethod = annotationType.getDeclaredMethod("value");
valueMethod.setAccessible(true);
int value = (int) valueMethod.invoke(annotation);
// 就是执行 findViewById 拿到 button1 == resultView
Method findViewByIdMethod = mMainActivityClass.getMethod("findViewById", int.class);
Object resultView = findViewByIdMethod.invoke(mainActivity, value);
// button1.setOnClickListener(new OnClickList)
// button1.要素1(要素2);
// 再次启用反射 button1 class setOnClickListener(事件三要数之一) setCommonObjectListener(事件三要数之一)
Method mViewMethod = resultView.getClass().getMethod(setCommonListener, setCommonObjectListener);
// 动态代理 监听 第三个要素
Object proxy = Proxy.newProxyInstance(
setCommonObjectListener.getClassLoader(), // 动态代理内部需要的 getClassLoader
new Class[]{setCommonObjectListener}, // 动态代理只监听 第二要素 的 接口
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理里边可以区分方法名字的
return declaredMethod.invoke(mainActivity, null);
}
}
);
// 事件三要素的 第三个 最终的事件消费
mViewMethod.invoke(resultView, proxy);
}catch (Exception e) {
e.printStackTrace();
}
}
}
}