• Android面试基础(一)IOC(DI)框架(ViewUtils)讲解_反射和自定义注解类


    1. Android中的IOC(DI)框架

    1.1 ViewUtils简介(xUtils中的四大部分之一)

    • IOC: Inverse of Controller 控制反转。

    • DI: Dependency Inject 依赖注入

    • 完全注解方式就可以进行UI绑定和事件绑定。

    • 无需findViewById和setClickListener等。

    1.2 ViewUtils使用

    compile 'org.xutils:xutils:3.5.0'
    compile 'com.jiechic.library:xUtils:2.6.14'
    // xUtils的view注解要求必须提供id,以使代码混淆不受影响。
    @ViewInject(R.id.textView)
    TextView textView;
    
    //@ViewInject(vale=R.id.textView, parentId=R.id.parentView)
    //TextView textView;
    
    @ResInject(id = R.string.label, type = ResType.String)
    private String label;
    
    // 取消了之前使用方法名绑定事件的方式,使用id绑定不受混淆影响
    // 支持绑定多个id @OnClick({R.id.id1, R.id.id2, R.id.id3})
    // or @OnClick(value={R.id.id1, R.id.id2, R.id.id3}, parentId={R.id.pid1, R.id.pid2, R.id.pid3})
    // 更多事件支持参见ViewCommonEventListener类和包com.lidroid.xutils.view.annotation.event。
    @OnClick(R.id.test_button)
    public void testButtonClick(View v) { // 方法签名必须和接口中的要求一致
        ...
    }
    ...
    //在Activity中注入:
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ViewUtils.inject(this); //注入view和事件
        ...
        textView.setText("some text...");
        ...
    }
    //在Fragment中注入:
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.bitmap_fragment, container, false); // 加载fragment布局
        ViewUtils.inject(this, view); //注入view和事件
        ...
    }
    //在PreferenceFragment中注入:
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ViewUtils.inject(this, getPreferenceScreen()); //注入view和事件
        ...
    }
    // 其他重载
    // inject(View view);
    // inject(Activity activity)
    // inject(PreferenceActivity preferenceActivity)
    // inject(Object handler, View view)
    // inject(Object handler, Activity activity)
    // inject(Object handler, PreferenceGroup preferenceGroup)
    // inject(Object handler, PreferenceActivity preferenceActivity)

    1.3 反射:调用(设置)类私有成员和私有方法

    定义的User类:

    package cn.Douzi.Annotation_Test;
    
    public class User {
        
        @ViewInject(age=23, name="张三")
        private String name;
        
        private int age;
        
        private String eat(String eat) {
            System.out.println("eat: " + eat);
            return eat + "真好吃";
        }
    
        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return "User [name =" + name + ", age = " + age + "]";
        }
    }

    反射调用私有对象,设置该类对象:

            Class cls = User.class;
            
            /**
             * 2. 将这个字节码中的name字段获取到
             */
    //        cls.getField(name);           //这个方法只能获取声明为public的字段
            Field declaredField = cls.getDeclaredField("name");
            Field declaredFieldAge = cls.getDeclaredField("age"); 
         declaredField.setAccessible(
    true); //设置允许访问,其实就是允许暴力反射 declaredFieldAge.setAccessible(true); //给user对象的declaredField设置为name declaredField.set(user, name); declaredFieldAge.set(user, age);

    反射调用私有函数,设置该类函数:

            //通过反射调用User对象的eat方法
            Method declaredMethod = cls.getDeclaredMethod("eat", String.class);
            //暴力反射调用改方法
            declaredMethod.setAccessible(true);
            Object result = declaredMethod.invoke(user, "牛肉拉面");   # 设置函数参数列表
            
            System.out.println(result);

    1.4 反射 和 自定义注解类

    自定义注解类:

    package cn.Douzi.Annotation_Test;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import javax.lang.model.element.Element;
    
    /**
     * @Taget(ElementType.METHOD)   //用于方法
     *
     */
    @Target(ElementType.FIELD)         //用于限制当前自定义注解类作用的对象
    //@Retention(RetentionPolicy.SOURCE) //该注解只会作用在源码阶段,当源码编译成字节码时注解信息就被清除
    //@Retention(RetentionPolicy.CLASS)  //该注解只会作用在字节码阶段,但是当虚拟机加载这个字节码时,注解信息被清除
    @Retention(RetentionPolicy.RUNTIME) //该注解类一直保留到加载到虚拟机中
    public @interface ViewInject {
        
        int age();
        String name();
            
    }
    package cn.Douzi.Annotation_Test;
    
    public class User {
        
        @ViewInject(age=23, name="张三")
        private String name;
        
        private int age;
        
        private String eat(String eat) {
            System.out.println("eat: " + eat);
            return eat + "真好吃";
        }
    
        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return "User [name =" + name + ", age = " + age + "]";
        } 
    }

    1.5 测试自定义注解类

    如何获取自定义注解对象(User)中参数

            Class cls = User.class;
            
            /**
             * 2. 将这个字节码中的name字段获取到
             */
    //        cls.getField(name);           //这个方法只能获取声明为public的字段
            Field declaredField = cls.getDeclaredField("name");
            Field declaredFieldAge = cls.getDeclaredField("age");
            
            /**
             * 3. 将当前字段上的注解对象获取到
             */
            ViewInject viewInject = declaredField.getAnnotation(ViewInject.class);
            if (viewInject != null) {
                /*
                 * 4.获取自定义注解对象中的参数
                 */
                int age = viewInject.age();
                String name = viewInject.name();
                System.out.println("name: " + name + ", age = " + age);
            }

    测试是否能获取到注解对象(完整代码)

    package cn.Douzi.Annotation_Test;
    
    import java.lang.reflect.Field;
    
    public class AnnotationMainTest {
    
        public static void main(String[] args) throws NoSuchFieldException, SecurityException {
    
            //获取User类中name字段上的自定义注解的值,然后将该值的age通过反射设置给User对象的age属性
            //将name设置给User对象的name属性
            User user = new User();
            /**
             * 反射:
             * 1. 先去获取到User的字节码
             */
    //        user.getClass();
    //        User.class
    //        Class.forName("xxxx")
            Class cls = User.class;
            
            /**
             * 2. 将这个字节码中的name字段获取到
             */
    //        cls.getField(name);           //这个方法只能获取声明为public的字段
            Field declaredField = cls.getDeclaredField("name");
            
            /**
             * 3. 将当前字段上的注解对象获取到
             */
            ViewInject viewInject = declaredField.getAnnotation(ViewInject.class);
            if (viewInject != null) {
                /*
                 * 4.获取自定义注解对象中的参数
                 */
                int age = viewInject.age();
                String name = viewInject.name();
                System.out.println("name: " + name + ", age = " + age);
            }
            else {
                System.out.println("字段上无自定义注解");
            }
            
        }
    
    }

    1.6 通过暴力反射将获取到的注解值设置给User对象 (完整代码)

    package cn.Douzi.Annotation_Test;
    
    import java.lang.reflect.Field;
    
    public class AnnotationMainTest {
    
        public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
    
            //获取User类中name字段上的自定义注解的值,然后将该值的age通过反射设置给User对象的age属性
            //将name设置给User对象的name属性
            User user = new User();
            /**
             * 反射:
             * 1. 先去获取到User的字节码
             */
    //        user.getClass();
    //        User.class
    //        Class.forName("xxxx")
            Class cls = User.class;
            
            /**
             * 2. 将这个字节码中的name字段获取到
             */
    //        cls.getField(name);           //这个方法只能获取声明为public的字段
            Field declaredField = cls.getDeclaredField("name");
            Field declaredFieldAge = cls.getDeclaredField("age");
            
            /**
             * 3. 将当前字段上的注解对象获取到
             */
            ViewInject viewInject = declaredField.getAnnotation(ViewInject.class);
            if (viewInject != null) {
                /*
                 * 4.获取自定义注解对象中的参数
                 */
                int age = viewInject.age();
                String name = viewInject.name();
                System.out.println("name: " + name + ", age = " + age);
                
                /*
                 * 5. 通过反射将这个两个值设置给User对象
                 */
                declaredField.setAccessible(true);  //设置允许访问,其实就是允许暴力反射
                declaredFieldAge.setAccessible(true);
                
                //给user对象的declaredField设置为name
                declaredField.set(user, name);
                declaredFieldAge.set(user, age);
                
                System.out.println(user.toString());
                
                
            }
            else {
                System.out.println("字段上无自定义注解");
            }
            
        }
    
    }

    1.7 通过反射调用User对象的eat方法 (完整代码)

    package cn.Douzi.Annotation_Test;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class AnnotationMainTest {
    
        public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    
            //获取User类中name字段上的自定义注解的值,然后将该值的age通过反射设置给User对象的age属性
            //将name设置给User对象的name属性
            User user = new User();
            /**
             * 反射:
             * 1. 先去获取到User的字节码
             */
    //        user.getClass();
    //        User.class
    //        Class.forName("xxxx")
            Class cls = User.class;
            
            /**
             * 2. 将这个字节码中的name字段获取到
             */
    //        cls.getField(name);           //这个方法只能获取声明为public的字段
            Field declaredField = cls.getDeclaredField("name");
            Field declaredFieldAge = cls.getDeclaredField("age");
            
            /**
             * 3. 将当前字段上的注解对象获取到
             */
            ViewInject viewInject = declaredField.getAnnotation(ViewInject.class);
            if (viewInject != null) {
                /*
                 * 4.获取自定义注解对象中的参数
                 */
                int age = viewInject.age();
                String name = viewInject.name();
                System.out.println("name: " + name + ", age = " + age);
            
                /*
                 * 5. 通过反射将这个两个值设置给User对象
                 */
                declaredField.setAccessible(true);  //设置允许访问,其实就是允许暴力反射
                declaredFieldAge.setAccessible(true);
                
                //给user对象的declaredField设置为name
                declaredField.set(user, name);
                declaredFieldAge.set(user, age);
                
                System.out.println(user.toString());
            }
            else {
                System.out.println("字段上无自定义注解");
            }
            
            //通过反射调用User对象的eat方法
            Method declaredMethod = cls.getDeclaredMethod("eat", String.class);
            //暴力反射调用改方法
            declaredMethod.setAccessible(true);
            Object result = declaredMethod.invoke(user, "牛肉拉面");
            
            System.out.println(result);
        }
    
    }

    2. 自定义ViewUtils (Android)

    布局文件:

    <?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:orientation="vertical"
        tools:context="com.douzi.myviewutils.MainActivity">
    
        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!!!!"
            android:textSize="20dp"
            />
    
        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World2222222222!!!!"
            android:textSize="20dp"
            />
    
    
        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="OKKKK"
            />
    
    </LinearLayout>

    注解类:

    package com.douzi.myviewutils;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * Created by Douzi on 2019/3/11.
     */
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface ViewInject {
    
        int value();      //这里必须叫value,否则绑定的时候需要写: xx=R.id.xx
    
    }

    实现 ViewUtils的inject方法(使用反射)

    package com.douzi.myviewutils;
    
    import android.app.Activity;
    import android.view.View;
    
    import java.lang.reflect.Field;
    
    /**
     * Created by Douzi on 2019/3/11.
     */
    
    public class ViewUtils {
    
        public static void inject(Activity activity) {
    
            //绑定控件
            try {
                bindView(activity);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }
    
        private static void bindView(Activity activity) throws IllegalAccessException {
            /**
             *  1. 获取Activity的字节码
             */
            Class clazz = activity.getClass();
            /**
             *  2. 获取到该字节码中所有的Filed
             */
            Field[] declaredFields = clazz.getDeclaredFields();
    
            /**
             *  3. 判断哪些是我们想要的字段(只有添加了ViewInject注解的字段)
              */
            for (Field field : declaredFields) {
                //获取字段上面的注解
                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                if (viewInject != null) {
                    /**
                     *  4. 获取当前注解的值
                     */
                    int resId = viewInject.value();
                    /**
                     *  5. 通过调用Activity的findViewById方法,获取当前id为resId的控件
                     */
                    View view = activity.findViewById(resId);
                    /**
                     *  6. 将当前的View设置给当前的Filed
                     */
                    field.setAccessible(true);
                    //给Activity对象的filed字段设置值为view对象
                    field.set(activity, view);
    
                }
                else {
                    // nothing
                }
            }
        }
    
    }

    测试注解的功能

    package com.douzi.myviewutils;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.widget.TextView;
    
    public class MainActivity extends AppCompatActivity {
    
        @ViewInject(R.id.tv1)
        TextView textView;
    
        @ViewInject(R.id.tv2)
        TextView textView2;
    
        private int count;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ViewUtils.inject(this);
    
            textView.setText("成功了!!");
            textView2.setText("成功了2!!");
    
        }
    }

    绑定点击事件实现(实现ViewUtils的OnClick实现)

    注解类

    package com.douzi.myviewutils;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * Created by Douzi on 2019/3/11.
     */
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface OnClick {
    
        int value();
    
    }

    ViewUtils里添加函数

    public static void bindOnClick(final Activity activity) {
            /**
             *  1. 获取字节码
             */
            Class clazz = activity.getClass();
            /**
             *  2. 获取字节码中所有的方法
             */
            Method[] methods = clazz.getDeclaredMethods();
    
            /**
             *  3. 遍历方法, 找出方法上声明了OnClick注解的方法
             */
            for (final Method method : methods) {
    
                /**
                 *  4. 获取当前方法的上的注解
                 */
                OnClick onClick = method.getAnnotation(OnClick.class);
    
                if (onClick != null) {
                    /**
                     *  5. 获取注解中的值
                     */
                    int resId = onClick.value();
                    /**
                     *  6. 获取到id为resId的View
                     */
                    final View view = activity.findViewById(resId);
                    /**
                     *  7. 将当前的View绑定点击事件
                     */
                    view.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            /**
                             *  8. 通过反射调用当前的用户的方法
                             */
                            //设置可以
                            method.setAccessible(true);
    
                            try {
                                method.invoke(activity, view);
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
    
        }
    public static void inject(Activity activity) {
    
            //绑定控件
            try {
                //绑定界面
                bindView(activity);
    
                //绑定方法
                bindOnClick(activity);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }

    测试注解功能

    package com.douzi.myviewutils;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.TextView;
    import android.widget.Toast;
    
    public class MainActivity extends AppCompatActivity {
    
        @ViewInject(R.id.tv1)
        TextView textView;
    
        @ViewInject(R.id.tv2)
        TextView textView2;
    
        private int count;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ViewUtils.inject(this);
    
            textView.setText("成功了!!");
            textView2.setText("成功了2!!");
    
        }
    
        @OnClick(R.id.btn)
        private void clickMe(View view) {
            Toast.makeText(this, "点击我了", Toast.LENGTH_LONG).show();
        }
    
    }

  • 相关阅读:
    模块
    time/datetime/random/string/os/sys/shutil/zipfile/tarfile
    模块
    模块
    模块
    2.1
    1.4
    生成器 迭代器
    闭包 装饰器
    函数
  • 原文地址:https://www.cnblogs.com/douzujun/p/10508305.html
Copyright © 2020-2023  润新知