一、布局文件的注解
我们在 Android开发的时候,总是会写到setContentView方法,为了避免每次都写重复的代码,我们需要使用注解来代替我们做这个事情,只需要在类Activity上声明一个ContentView注解和对应的布局文件就可以了。

1. @ContentView(R.layout.activity_main)  
2. public class MainActivity extends AppCompatActivity {  
3.     @Override  
4.     protected void onCreate(Bundle savedInstanceState) {  
5.         super.onCreate(savedInstanceState);  
6.         ViewUtils.injectContentView(this);  
7.     }  
8. }

从上面可以看到,上面代码在MainActivity上面使用到了ContentView注解,下面我们来看看ContentView注解。

1. @Target(ElementType.TYPE)  
2. @Retention(RetentionPolicy.RUNTIME)  
3. public @interface ContentView {  
4.     int value();  
5. }


这个注解很简单,它有一个int的value,用来存放布局文件的id,另外它注解的对象为一个类型,需要说明的是,注解的生命周期会一直到运行时,这个很重要,因为程序是在运行时进行反射的,我们来看看ViewUtils.injectContentView(this)方法,它进行的就是注解的处理,就是进行反射调用setContentView()方法。

1. public static void injectContentView(Activity activity) {  
2. a = activity.getClass();  
3.     if (a.isAnnotationPresent(ContentView.class)) {  
4.         // 得到activity这个类的ContentView注解  
5. contentView
6.         // 得到注解的值  
7. layoutId = contentView.value();  
8.         // 使用反射调用setContentView  
9.         try {  
10. method = a.getMethod("setContentView", int.class);  
11.             method.setAccessible(true);  
12.             method.invoke(activity, layoutId);  
13.         } catch (NoSuchMethodException e) {  
14.             e.printStackTrace();  
15.         } catch (IllegalAccessException e) {  
16.             e.printStackTrace();  
17.         } catch (InvocationTargetException e) {  
18.             e.printStackTrace();  
19.         }  
20.     }  
21. }



如果对Java注解比较熟悉的话,上面代码应该很容易看懂。

二、字段的注解
除了setContentView之外,还有一个也是我们在开发中必须写的代码,就是findViewById,同样,它也属于简单但没有价值的编码,我们也应该使用注解来代替我们做这个事情,就是对字段进行注解。

1. @ContentView(R.layout.activity_main)  
2. public class MainActivity extends AppCompatActivity {  
3.     @ViewInject(R.id.btn1)  
4.     private Button mButton1;  
5.     @ViewInject(R.id.btn2)  
6.     private Button mButton2;  
7.   
8.     @Override  
9.     protected void onCreate(Bundle savedInstanceState) {  
10.         super.onCreate(savedInstanceState);  
11.         ViewUtils.injectContentView(this);  
12.         ViewUtils.injectViews(this);  
13.     }  
14. }



上面我们看到,使用ViewInject对两个Button进行了注解,这样我们就是不用写findViewById方法,看上去很神奇,但其实原理很简单。我们先来看看ViewInject注解。

1. @Target(ElementType.FIELD)  
2. @Retention(RetentionPolicy.RUNTIME)  
3. public @interface ViewInject {  
4.     int value();  
5. }

这个注解也很简单,就不说了,重点就是注解的处理了。

1. public static void injectViews(Activity activity) {  
2. a = activity.getClass();  
3.     // 得到activity所有字段  
4. fields = a.getDeclaredFields();  
5.     // 得到被ViewInject注解的字段  
6.     for (Field field : fields) {  
7.         if (field.isAnnotationPresent(ViewInject.class)) {  
8.             // 得到字段的ViewInject注解  
9. viewInject = field.getAnnotation(ViewInject.class);  
10.             // 得到注解的值  
11. viewId = viewInject.value();  
12.             // 使用反射调用findViewById,并为字段设置值  
13.             try {  
14. method = a.getMethod("findViewById", int.class);  
15.                 method.setAccessible(true);  
16. resView = method.invoke(activity, viewId);  
17.                 field.setAccessible(true);  
18.                 field.set(activity, resView);  
19.             } catch (NoSuchMethodException e) {  
20.                 e.printStackTrace();  
21.             } catch (InvocationTargetException e) {  
22.                 e.printStackTrace();  
23.             } catch (IllegalAccessException e) {  
24.                 e.printStackTrace();  
25.             }  
26.   
27.         }  
28.     }  
29. }



上面的注释很清楚,使用的也是反射调用findViewById函数。

三、事件的注解
在Android开发中,我们也经常遇到setOnClickListener这样的事件方法。同样我们可以使用注解来减少我们的代码量。


1. @ContentView(R.layout.activity_main)  
2. public class MainActivity extends AppCompatActivity {  
3.     @Override  
4.     protected void onCreate(Bundle savedInstanceState) {  
5.         super.onCreate(savedInstanceState);  
6.         ViewUtils.injectContentView(this);  
7.         ViewUtils.injectEvents(this);  
8.     }  
9.   
10.     @OnClick({R.id.btn1, R.id.btn2})  
11.     public void clickBtnInvoked(View view) {  
12.         switch (view.getId()) {  
13.             case R.id.btn1:  
14.                 Toast.makeText(this, "Button1 OnClick", Toast.LENGTH_SHORT).show();  
15.                 break;  
16.             case R.id.btn2:  
17.                 Toast.makeText(this, "Button2 OnClick", Toast.LENGTH_SHORT).show();  
18.                 break;  
19.         }  
20.     }  
21. }



布局文件如下:

1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3. xmlns:tools="http://schemas.android.com/tools"
4. android:layout_width="match_parent"
5. android:layout_height="match_parent"
6. android:paddingBottom="@dimen/activity_vertical_margin"
7. android:paddingLeft="@dimen/activity_horizontal_margin"
8. android:paddingRight="@dimen/activity_horizontal_margin"
9. android:paddingTop="@dimen/activity_vertical_margin"
10. android:background="#70DBDB"
11. android:orientation="vertical"
12. tools:context="statusbartest.hpp.cn.statusbartest.MainActivity">
13. <Button
14. android:id="@+id/btn1"
15. android:layout_width="wrap_content"
16. android:layout_height="wrap_content"
17. android:text="Test1"/>
18.   
19. <Button
20. android:id="@+id/btn2"
21. android:layout_width="wrap_content"
22. android:layout_height="wrap_content"
23. android:text="Test2"/>
24. </LinearLayout>



可以看到,上面我们没有对Button调用setOnClickListener,但是当我们点击按钮的时候,就会回调clickBtnInvoked方法,这里我们使用的就是注解来处理的。下面先来看看OnClick注解。

1. @Target(ElementType.METHOD)  
2. @Retention(RetentionPolicy.RUNTIME)  
3. @EventBase(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", methodName = "onClick")  
4. public @interface OnClick {  
5.     int[] value();  
6. }


可以看到这个注解使用了一个自定义的注解。

1. @Target(ElementType.ANNOTATION_TYPE)  
2. @Retention(RetentionPolicy.RUNTIME)  
3. public @interface EventBase {  
4.     Class listenerType();  
5.     String listenerSetter();  
6.     String methodName();  
7. }



下面来看看注解的处理。

1. public static void injectEvents(Activity activity) {  
2. a = activity.getClass();  
3.     // 得到Activity的所有方法  
4. methods = a.getDeclaredMethods();  
5.     for (Method method : methods) {  
6.         // 得到被OnClick注解的方法  
7.         if (method.isAnnotationPresent(OnClick.class)) {  
8.             // 得到该方法的OnClick注解  
9. onClick = method.getAnnotation(OnClick.class);  
10.             // 得到OnClick注解的值  
11. viewIds = onClick.value();  
12.             // 得到OnClick注解上的EventBase注解  
13. eventBase = onClick.annotationType().getAnnotation(EventBase.class);  
14.             // 得到EventBase注解的值  
15. listenerSetter = eventBase.listenerSetter();  
16. <?> listenerType = eventBase.listenerType();  
17. methodName = eventBase.methodName();  
18.             // 使用动态代理  
19. handler = new
20. listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[] { listenerType }, handler);  
21.             handler.addMethod(methodName, method);  
22.             // 为每个view设置点击事件  
23.             for (int viewId : viewIds) {  
24.                 try {  
25. findViewByIdMethod = a.getMethod("findViewById", int.class);  
26.                     findViewByIdMethod.setAccessible(true);  
27. view
28. setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);  
29.                     setEventListenerMethod.setAccessible(true);  
30.                     setEventListenerMethod.invoke(view, listener);  
31.                 } catch (NoSuchMethodException e) {  
32.                     e.printStackTrace();  
33.                 } catch (InvocationTargetException e) {  
34.                     e.printStackTrace();  
35.                 } catch (IllegalAccessException e) {  
36.                     e.printStackTrace();  
37.                 }  
38.             }  
39.   
40.         }  
41.   
42.     }  
43. }



这个代码相对上面的要复杂一些,它使用到了动态代理,关于动态代理的基本用法可以看看前面我提到的预备知识。

    1. public class DynamicHandler implements InvocationHandler {  
    2. <String, Method> methodMap = new HashMap<String, Method>(  
    3.             1);  
    4.     // 因为传进来的为activity,使用弱引用主要是为了防止内存泄漏  
    5. <Object>
    6.     public DynamicHandler(Object object) {  
    7. this.handlerRef = new WeakReference<Object>(object);  
    8.     }  
    9.   
    10.     public void addMethod(String name, Method method) {  
    11.         methodMap.put(name, method);  
    12.     }  
    13.     // 当回到OnClickListener的OnClick方法的时候,它会调用这里的invoke方法  
    14.     @Override  
    15.     public Object invoke(Object o, Method method, Object[] objects) throws Throwable {  
    16.         // 得到activity实例  
    17. handler = handlerRef.get();  
    18.         if (handler != null) {  
    19.             // method对应的就是回调方法OnClick,得到方法名  
    20. methodName = method.getName();  
    21.             // 得到activtiy里面的clickBtnInvoked方法  
    22. method = methodMap.get(methodName);  
    23.             if (method != null) {  
    24.                 // 回调clickBtnInvoked方法  
    25.                 return method.invoke(handler, objects);  
    26.             }  
    27.         }  
    28.         return null;  
    29.     }  
    30. }