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了,咱們經常使用的第三方庫其實用了不少不少的知識點,因此,一些小的知識點咱們也不能忽略,都要去學習整理,這樣後面在看其餘優秀庫的源碼的時候,纔不會感受那麼懵逼,因此,一塊兒監督加油。