预备知识:
Java注解基础
Java反射原理
Java动态代理

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

@ContentView(R.layout.activity_main)public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ViewUtils.injectContentView(this);    }}

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

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface ContentView {    int value();}

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

    public static void injectContentView(Activity activity) {        Class a = activity.getClass();        if (a.isAnnotationPresent(ContentView.class)) {            // 得到activity这个类的ContentView注解            ContentView contentView = (ContentView) a.getAnnotation(ContentView.class);            // 得到注解的值            int layoutId = contentView.value();            // 使用反射调用setContentView            try {                Method method = a.getMethod("setContentView", int.class);                method.setAccessible(true);                method.invoke(activity, layoutId);            } catch (NoSuchMethodException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            } catch (InvocationTargetException e) {                e.printStackTrace();            }        }    }

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

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

@ContentView(R.layout.activity_main)public class MainActivity extends AppCompatActivity {    @ViewInject(R.id.btn1)    private Button mButton1;    @ViewInject(R.id.btn2)    private Button mButton2;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ViewUtils.injectContentView(this);        ViewUtils.injectViews(this);    }}

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

@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface ViewInject {    int value();}

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

    public static void injectViews(Activity activity) {        Class a = activity.getClass();        // 得到activity所有字段        Field[] fields = a.getDeclaredFields();        // 得到被ViewInject注解的字段        for (Field field : fields) {            if (field.isAnnotationPresent(ViewInject.class)) {                // 得到字段的ViewInject注解                ViewInject viewInject = field.getAnnotation(ViewInject.class);                // 得到注解的值                int viewId = viewInject.value();                // 使用反射调用findViewById,并为字段设置值                try {                    Method method = a.getMethod("findViewById", int.class);                    method.setAccessible(true);                    Object resView = method.invoke(activity, viewId);                    field.setAccessible(true);                    field.set(activity, resView);                } catch (NoSuchMethodException e) {                    e.printStackTrace();                } catch (InvocationTargetException e) {                    e.printStackTrace();                } catch (IllegalAccessException e) {                    e.printStackTrace();                }            }        }    }

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

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

@ContentView(R.layout.activity_main)public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ViewUtils.injectContentView(this);        ViewUtils.injectEvents(this);    }    @OnClick({R.id.btn1, R.id.btn2})    public void clickBtnInvoked(View view) {        switch (view.getId()) {            case R.id.btn1:                Toast.makeText(this, "Button1 OnClick", Toast.LENGTH_SHORT).show();                break;            case R.id.btn2:                Toast.makeText(this, "Button2 OnClick", Toast.LENGTH_SHORT).show();                break;        }    }}

布局文件如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    android:background="#70DBDB"    android:orientation="vertical"    tools:context="statusbartest.hpp.cn.statusbartest.MainActivity">    <Button        android:id="@+id/btn1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="Test1"/>    <Button        android:id="@+id/btn2"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="Test2"/>LinearLayout>

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

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

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

@Target(ElementType.ANNOTATION_TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface EventBase {    Class listenerType();    String listenerSetter();    String methodName();}

下面来看看注解的处理。

    public static void injectEvents(Activity activity) {        Class a = activity.getClass();        // 得到Activity的所有方法        Method[] methods = a.getDeclaredMethods();        for (Method method : methods) {            // 得到被OnClick注解的方法            if (method.isAnnotationPresent(OnClick.class)) {                // 得到该方法的OnClick注解                OnClick onClick = method.getAnnotation(OnClick.class);                // 得到OnClick注解的值                int[] viewIds = onClick.value();                // 得到OnClick注解上的EventBase注解                EventBase eventBase = onClick.annotationType().getAnnotation(EventBase.class);                // 得到EventBase注解的值                String listenerSetter = eventBase.listenerSetter();                Class<?> listenerType = eventBase.listenerType();                String methodName = eventBase.methodName();                // 使用动态代理                DynamicHandler handler = new DynamicHandler(activity);                Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[] { listenerType }, handler);                handler.addMethod(methodName, method);                // 为每个view设置点击事件                for (int viewId : viewIds) {                    try {                        Method findViewByIdMethod = a.getMethod("findViewById", int.class);                        findViewByIdMethod.setAccessible(true);                        View view  = (View) findViewByIdMethod.invoke(activity, viewId);                        Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);                        setEventListenerMethod.setAccessible(true);                        setEventListenerMethod.invoke(view, listener);                    } catch (NoSuchMethodException e) {                        e.printStackTrace();                    } catch (InvocationTargetException e) {                        e.printStackTrace();                    } catch (IllegalAccessException e) {                        e.printStackTrace();                    }                }            }        }    }

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

public class DynamicHandler implements InvocationHandler {    private final HashMap methodMap = new HashMap(            1);    // 因为传进来的为activity,使用弱引用主要是为了防止内存泄漏    private WeakReference handlerRef;    public DynamicHandler(Object object) {        this.handlerRef = new WeakReference(object);    }    public void addMethod(String name, Method method) {        methodMap.put(name, method);    }    // 当回到OnClickListener的OnClick方法的时候,它会调用这里的invoke方法    @Override    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {        // 得到activity实例        Object handler = handlerRef.get();        if (handler != null) {            // method对应的就是回调方法OnClick,得到方法名            String methodName = method.getName();            // 得到activtiy里面的clickBtnInvoked方法            method = methodMap.get(methodName);            if (method != null) {                // 回调clickBtnInvoked方法                return method.invoke(handler, objects);            }        }        return null;    }}   

基本的看注释就应该差不多了。

欢迎关注微信公众号:DroidMind
精品内容独家发布平台


呈现与博客不一样的技术干货

更多相关文章

  1. Android的嵌入式关系型SQLite数据库使用
  2. Android笔记---使用HttpClient发送POST和GET请求
  3. 关于LayoutInflate和View-infalte的参数意义以及区别总结
  4. 多个Fragment嵌套
  5. android textview在code(代码)中设置图片 和XML设置图片
  6. 通过抢红包插件学习Accessibility Service
  7. 自己处理Webview时出现的问题的汇总
  8. 使ImageView充满整个控件的方法
  9. Android(安卓)跨进程通信基础

随机推荐

  1. Android(安卓)Studio 配置一键生成 JNI
  2. Android build.gradle buildConfigField
  3. Android-基本控件(SeekBar 可拖动 滚动条
  4. VMware安装Android虚拟机及adb调试
  5. Android Fragment简记
  6. android平台上的sqllite_简介
  7. android特效展示一:ListView
  8. android WiFi ASSOC_REJECT 流程跟踪
  9. Ubuntu 16.04编译Android,make 版本过高
  10. 针对Android 模拟器启动慢的问题