一、布局文件的注解
我们在
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. }