Android进阶系列java
知识点总结,整理也是学习的过程,若有错误,欢迎批评指出。
本文内容涉及到注解,反射,动态代理等知识点,对这部分不太熟悉的能够看看如下文章android
本篇内容主要是对前面注解,反射及动态代理知识点的实战,至关于进行一个简单的总结,手写一个简易版本的ButterKnifeDemo,这部分用了大量的反射,确定会影响必定的性能,可是ButterKnife库的实现是经过编译期间生成辅助代码来达到View注入的目的,感兴趣的能够去看看它的源码,后面有时间,我也会整理一份出来。数组
ButterKnife
这个库学习门槛不高,在项目中使用能节省不少不必的代码,不用一直在那里findviewByid
,再结合Android ButterKnife Zelezny
这个插件,真的不要太香,好了,咱们看一下下面这段代码,基本上覆盖了ButterKnife
的使用了。app
咱们能够看到,平时的findViewById()
操做直接经过@BindView
注解替代了,各类点击事件也被
对应的注解替代,固然,要想让上面的代码能实现其对应功能,下面这行代码是关键。工具
1ButterKnife.bind(this);
复制代码
经过这行代码,对各类视图进行绑定。布局
好了,直接开整,手写一个简单的ButterKnife,实现和第三方库ButterKnife差很少的功能。
post
咱们先写一个 BindLayoutId
,经过注解来注入当前Activity的布局,不用再去经过setContentView()
方法实现。性能
要实现这个功能,咱们首先确定要定义一个注解,能做用在 Activity
上,而且能经过属性设置布局Id。学习
一、定义
BindLayoutId
注解
首先经过Target
设置这个注解使用在类上,生命周期保存到运行阶段,因为要传入一个布局id,因此成员变量定义一个Int类型。
二、注解使用
![]()
上面的操做,经过注解,绑定了id,可是这只是一个表象,目前尚未任何效果,由于咱们知道咱们设置布局都是经过setContentView(xxxx)
来完成,因此,咱们须要拿到BindLayoutId
里面的id,在经过反射执行setContentView(xxxx)
来执行真正的操做。
三、反射执行。
这一步是具体逻辑的实现,比较关键,我一步一步拆分开来讲。
首先咱们要明白这一步要作什么。
Activity
,只有经过这个Activity
,咱们才能拿到他上面的注解,并拿到注解信息,还有反射执行这个Activity
的setContentView(xxxx)
方法。 1public class MyButterKnifeUtil {
2
3 private static final String TAG = "MyButterKnifeUtil";
4 /**
5 * 对注解信息进行处理
6 *
7 * @param activity 须要操做的Activity
8 */
9 public static void injectLayoutId(Activity activity) {
10
11 }
12 }
复制代码
咱们定义了一个工具类,并定义injectLayoutId
方法,经过参数咱们能拿到须要处理的Activity
Activity
拿到了,咱们确定先要拿到这个Activity上得BindLayoutId
注解,并拿到注解上的属性及布局ID 1// 一、反射执行,先拿到须要处理的Activity的Class的对象
2Class classzz = activity.getClass();
3// 判断是否有BindLayoutId这个注解
4boolean isBindLayoutId = classzz.isAnnotationPresent(BindLayoutId.class);
5if (isBindLayoutId) {
6// 获取到注解对象
7BindLayoutId bindLayoutIdzz = (BindLayoutId)
8classzz.getAnnotation(BindLayoutId.class);
9// 拿到咱们注解对象的成员变量值,及属性id
10int layoutId = bindLayoutIdzz.value();
11LogUtil.d(TAG + "--injectLayoutId layoutId=" + layoutId);
12}
复制代码
咱们能够看到,咱们经过反射操做,就拿到了咱们设置的布局Id。
Activity
的setContentView
方法,咱们已经经过传入的参数拿到了Activity,那执行他的方法,直接经过反射不就Ok了!1try {
2// 反射拿到setContentView()方法
3Method setContentViewMethod = classzz.getMethod("setContentView", int.class);
4// 执行方法
5setContentViewMethod.invoke(activity, layoutId);
6} catch ( Exception e ) {
7LogUtil.e(TAG + "--injectLayoutId error=" + e.getMessage());
8 e.printStackTrace();
9}
复制代码
贴一下完整代码:
好了,具体执行逻辑实现了,咱们只须要在Activity里面注入就大功告成。
注入
在对应的activity中进行注入,这样,咱们的布局id就经过注解的方式添加了。
1MyButterKnifeUtil.injectLayoutId(this);
复制代码
程序运行,能够看到咱们的布局经过BindLayoutId
成功注入。
上面实现了对布局ID的注入,咱们如今来实现对控件ID的注入,基本步骤跟上面同样。
一、定义
MyBindView
注解
对控件id的注解使用在属性上,因此咱们这里@Target
使用了ElementType.FIELD
二、注解的使用
三、反射执行逻辑,思路和
BindLayoutId
基本一致,咱们新建方法injectViewId
1 public static void injectViewId(Activity activity) {
2 /**
3 * 思路:
4 * 一、咱们首先要拿到当前Activity上被MyBindView这个注解注解的全部控件
5 * 而且拿到注解中的属性信息(控件id)
6 * 二、反射执行Activity中的findViewById()方法,传入咱们的id。
7 */
8 // 一、反射执行,先拿到须要处理的Activity的Class的对象
9 Class classzz = activity.getClass();
10 // 二、拿到当前Activity中全部的成员变量
11 Field[] fields = classzz.getDeclaredFields();
12 for (Field field : fields) {
13 // 遍历获取当前成员变量是否有MyBindView注解修饰
14 boolean isMyBindView = field.isAnnotationPresent(MyBindView.class);
15 LogUtil.d(TAG + "--injectViewId isMyBindView=" + isMyBindView);
16 if (!isMyBindView) {
17 // 没有MyBindView注解修饰的成员变量直接过滤掉。
18 continue;
19 }
20 // 经过成员变量拿到MyBindView注解对象
21 MyBindView myBindViewzz = field.getAnnotation(MyBindView.class);
22 // 拿到注解的成员变量及控件Id
23 int myViewId = myBindViewzz.value();
24 LogUtil.d(TAG + "--injectViewId id=" + myViewId);
25 try {
26 // 经过反射,执行Activity中的findViewById()
27 Method method = classzz.getMethod("findViewById", int.class);
28 // 反射执行,并拿到返回的控件对象
29 // =View view=mainActivity.findViewById(valueId);
30 View view = (View) method.invoke(activity, myViewId);
31 // 赋值,上面咱们反射执行,已经经过id拿到了实际的控件对象,须要对咱们
32 // 获取到的控件的成员变量进行赋值
33 field.setAccessible(true);
34 field.set(activity, view);
35 } catch (Exception e) {
36 e.printStackTrace();
37 LogUtil.e(TAG + "--injectViewId error=" + e.getMessage());
38 }
39 }
40 }
复制代码
经过
MyButterKnifeUtil.injectViewId(this)
注入到Activity中,运行结果以下。
能够看到控件成功进行设置,说明咱们的控件注入ok。
上面两个处理比较简单了,大同小异,可是事件处理这部分相对来讲会复杂一点,其中也会涉及到动态代理部分,我尽可能每步往详细了走。
固然,在开整以前,要先分析一下咱们要作的工做。
思路整理:
一、基于咱们前面MyBindView
的思路,首先确定要经过注解拿到实际的控件对象(经过反射);
二、拿到控件后,要动态对应的处理执行各类事件(点击、长按等)。
三、执行后须要将方法回调给用户本身处理(动态代理)
上图ABCD几个参数,都是须要咱们处理的,其中拿到控件对象,咱们上一个示例已经走了一遍,要想让事件处理这部分更完善,兼容不一样的触发事件,BCD这个三个动态的参数,咱们能够新建一个注解来绑定。
注意这个注解的
@Target
为ANNOTATION_TYPE
,及注解在注解上。
新建咱们点击事件的注解,以下:
使用:
一样的,上面只是注入,真正的实现逻辑须要咱们来实现,一样在MyButterKnifeUtil
中新增方法来实现咱们的逻辑。
1public void injectListener(Activity activity) {}
2
3}
复制代码
一、首先,要获取当前Activity的全部方法,遍历获取方法上的全部注解,拿到注解的Class对象,就能够经过反射获取BaseEvent注解,来判断当前注解是不是事件处理注解,而后对其进行操做。
二、拿到了注解的Class对象,咱们能够反射获取其方法,并反射执行,拿到返回值,及设置的id数组,经过id,能够反射拿到这个控件View
三、咱们拿到了控件对象,又经过BaseEvent的属性拿到了事件的方法等各类参数,可是有一个问题,就是咱们并不能直接经过反射Activity中的方法来执行(method.invoke(activity, view))直接回调,由于咱们须要在按钮实际被点击后再回调,而这个步骤就须要用到动态代理来实现了。
咱们先建立一个动态代理类。
关于动态代理知识点,这里不作详细介绍,不清楚的能够先去了解一下,经过动态代理,当用户事件触发的时候,回调事件就会走到invoke方法来,咱们在动态代理的invoke方法中,去执行了Activity中实际的方法。
咱们将动态代理与事件进行绑定。
完整代码
1 public static void injectListener(Activity activity) {
2 Class<?> classzz = activity.getClass();
3 // 反射获取全部方法
4 Method[] methods = classzz.getDeclaredMethods();
5 // 遍历获取当前Activity中全部方法
6 for (Method method : methods) {
7 // 拿到每一个方法上的全部注解
8 Annotation[] annotations = method.getAnnotations();
9 for (Annotation annotation : annotations) {
10 // 经过annotationType方法拿到annotation的Class对象,
11 Class<?> annotationzz = annotation.annotationType();
12 // 经过annotationClass反射获取其BaseEvent注解
13 BaseEvent baseEvent = annotationzz.getAnnotation(BaseEvent.class);
14 if (baseEvent == null) {
15 continue;
16 }
17 // 拿到baseEvent注解,获取其全部成员变量。
18 String listenerSetter = baseEvent.listenerSetter();
19 Class<?> listenerType = baseEvent.listenerType();
20 String callBackMethod = baseEvent.callBackMethod();
21
22 try {
23 // 经过annotationzz反射获取其成员变量
24 Method declaredMethod = annotationzz.getDeclaredMethod("value");
25 // 反射方法执行
26 int[] ids = (int[]) declaredMethod.invoke(annotation);
27 if (ids == null) {
28 continue;
29 }
30 for (int id : ids) {
31 Method findViewById = classzz.getMethod("findViewById", int.class);
32 // 拿到具体的控件View
33 View view = (View) findViewById.invoke(activity, id);
34 LogUtil.d(TAG + "--injectListener id=" + id);
35 if (view == null) {
36 continue;
37 }
38
39 // 经过动态代理事件,将用户操做后的事件交给代理类,再在代理类中让Activity反射执行。
40 ListenerInvocationHandler listenerInvocationHandler
41 = new ListenerInvocationHandler(activity, method);
42 // 作代理对象,eg:new View.OnClickListener()对象
43 Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader()
44 , new Class[]{listenerType}, listenerInvocationHandler);
45
46 // 获取到执行方法,eg:setOnClickListener
47 Method listenerSetterMethod = view.getClass()
48 .getMethod(listenerSetter, listenerType);
49 // 方法反射执行 eg:view.setOnClickListener(new View.OnClickListener(){})
50 listenerSetterMethod.invoke(view, proxy);
51 }
52 } catch (Exception e) {
53 e.printStackTrace();
54 }
55 }
56
57 }
58 }
复制代码
逻辑处理好后,咱们进行注入,而后运行,结果以下:
若是咱们要定义长按事件,只须要更改BaseEvent里面的事件就能够了
结果:
这个简易ButterKnife的项目实战将前面的注解,反射,动态代理的知识点都用上了,这个仍是一个很是很是简单的demo了,咱们经常使用的第三方库其实用了不少不少的知识点,因此,一些小的知识点咱们也不能忽略,都要去学习整理,这样后面在看其余优秀库的源码的时候,才不会感受那么懵逼,因此,一块儿监督加油。