• Android中的自定义注解(反射实现-运行时注解)


    预备知识:
    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<String, Method> methodMap = new HashMap<String, Method>(
                1);
        // 因为传进来的为activity,使用弱引用主要是为了防止内存泄漏
        private WeakReference<Object> handlerRef;
        public DynamicHandler(Object object) {
            this.handlerRef = new WeakReference<Object>(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;
        }
    }
    
  • 相关阅读:
    AGC 044 A
    example
    python3遇到的问题
    构建开发环境
    pandas处理数据
    pandas.DataFrame对象解析
    pandas再次学习
    监督式学习
    机器学习的基础概念
    赖世雄老师的音标课,旋元佑老师的语法书
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/10401415.html
Copyright © 2020-2023  润新知