注解

作者: 壹元伍角叁分 | 来源:发表于2021-05-30 22:52 被阅读0次

    一:什么是注解?
    注解只是个标签,本身并没有意义,需要结合其他场景使用
    1、元注解:

    //注解作用的地方:类、方法等,可以使用多个
    @Target({ElementType.TYPE, ElementType.FIELD})
    
    //注解保留的时间,三种:
    //SOURCE(仅仅保留在源码阶段,被编译器忽略),
    //CLASS(被编译器保留、但jvm会忽略),
    //RUNTIME(jvm保留,运行时可以使用,通过反射)
    @Retention(RetentionPolicy.SOURCE)
    
    //语法检查:IDE或者IDE插件实现检查
    @IntDef
    

    2、如果只有一个属性,且名称为value,则使用时可以不用表明名称

    @Target({ElementType.TYPE, ElementType.FIELD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Lance {
        String value();//如有多个属性,或者名称不为value,则必须注明,否则报错
    }
    

    使用:

    @Lance("value")
    public class MainActivity extends AppCompatActivity {
    }
    

    3、可以设定默认值;如未设定,使用时,必须设置

    public @interface Lance {
     int index() default 1;
    }
    

    使用:

    @Lance //不用传参,使用默认值
    public class MainActivity extends AppCompatActivity {
    }
    

    二、注解的应用场景

    级别 技术 说明
    源码 APT 在编译器能够获取注解与注解声明的类,包括类中所有成员信息,一般用于生产额外的辅助类
    字节码 字节码增强 在编译出class后,通过修改class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解
    运行时 反射 在程序运行期间,通过反射技术东台获取注解及其元素,从而完成不同的逻辑判定

    1、RetentionPolicy.SOURCE(源码)
    APT:Annotation Processor Tools(注解处理程序)
    javac收集到所有的注解信息,打包成一个节点(Element),然后调用APT进行

    实际使用:
    某个方法需要限定方法传入的类型,之前是用枚举实现,但枚举的使用,在生成class文件时,实际上是生成了若干个对象,而一个对象由对象头+成员组成,至少12个字节,比较占用内存。如果需要对应用运行内存进行一个优化,可以考虑使用常量+注解代替枚举类型。

    public class InitialPenSize {
        @InitialPenSize
        private int mInitialPenSize;
    
        public static final int FINE = 0;//细,1f,
        public static final int NORMAL = 1;//正常,2.5f,
        public static final int THICK = 2;//粗, 5f,
        public static final int SUPER_THICK = 3;//超粗,9f
    
        @Target({ElementType.FIELD, ElementType.PARAMETER})
        @IntDef({FINE, NORMAL, THICK, SUPER_THICK})
        @Retention(RetentionPolicy.SOURCE)
        public @interface InitialPenSize {
        }
    
        public void setInitialPenSize(@InitialPenSize int penSize) {//限定了传入的类型
            this.mInitialPenSize = penSize;
        }
    }
    

    使用:

    InitialPenSize initialPenSize = new InitialPenSize();
    initialPenSize.setInitialPenSize(InitialPenSize.FINE);
    

    2、RetentionPolicy.CLASS(字节码)
    字节码增强,在字节码中写代码

    3、RetentionPolicy.RUNTIME(运行时)
    (1)注解+反射完成findViewById,Butter Knife原来的实现方式:

    class InjectViewHelper {
        @Target(ElementType.FIELD)
        @Retention(RetentionPolicy.RUNTIME)
        @interface InjectView {
            @IdRes int value();
        }
    
        public static void inject(Activity activity) {
            Class<? extends Activity> aClass = activity.getClass();
            //Field[] fields = aClass.getFields();//获取自己及其父类的所有成员(不包括private,只能是public)
            Field[] declaredFields = aClass.getDeclaredFields();//获取自己的成员(包括private,不包括父类的)
    
            //那怎么获取到父类的private成员属性呢?
            //Field[] parentDeclaredFields = aClass.getSuperclass().getDeclaredFields();
    
            for (Field field : declaredFields) {
                if (field.isAnnotationPresent(InjectView.class)) {//判断属性是否被InjectView注解声明
                    InjectView annotation = field.getAnnotation(InjectView.class);
                    int valueId = annotation.value();//获取到注解上设置的值
    
                    View viewById = activity.findViewById(valueId);
                    field.setAccessible(true);//设置访问权限,允许操作private属性
                    try {
                        field.set(activity, viewById);//赋值
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    使用:

    public class MainActivity extends AppCompatActivity {
     @InjectViewHelper.InjectView(R.id.text_view)
        private TextView mTextView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            InjectViewHelper.inject(this);//初始化
            mTextView.setText("设置成功");
    }
    

    (2)通过注解+反射实现页面跳转的参数注入

    //如果页面跳转需要携带多种参数,则在SecondActivity中会获取多次
     StudentBean[] studentBeanArray = {new StudentBean(13), new StudentBean(14)};
     Intent intent = new Intent(MainActivity.this, SecondActivity.class)
                    .putExtra("extra_string1", "extra1")
                    .putExtra("extra_boolean", true)
                    .putExtra("extraInt", 100)
                    .putExtra("extra_student_bean", new StudentBean(10))
                    .putParcelableArrayListExtra("extra_student_list", extraStudentBeanList)
                    .putExtra("extra_student_array", studentBeanArray);
     startActivity(intent);
    
    class InjectIntentHelper {
        @Target(ElementType.FIELD)
        @Retention(RetentionPolicy.RUNTIME)//运行时
        public @interface InjectIntent {
            String extraName() default "";//设置默认值,如果传入的key和变量名一样,可以不写
        }
        
        public static void inject(Activity activity) {
            Bundle extras = activity.getIntent().getExtras();
            Class<? extends Activity> aClass = activity.getClass();
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field field : declaredFields) {
                boolean annotationPresent = field.isAnnotationPresent(InjectIntent.class);
                if (annotationPresent) {
                    InjectIntent annotation = field.getAnnotation(InjectIntent.class);
                    String extraName = annotation.extraName();
    
                    //如果传入的key和变量名一样,且未声明,这边则去判断是否有值,没有则去获取变量名
                    String extraKey = TextUtils.isEmpty(extraName) ? field.getName() : extraName;
                    Object object = extras.get(extraKey);
    
                    //这边需要注意:如果传入的类型是实现了Parcelable的对象数组,则需要特殊处理一下,其他类型不行
                    Class<?> type = field.getType();
                    Class<?> componentType = type.getComponentType();
                    //判断是否是array数组并且是数组中对象实现了Parcelable接口
                    if (type.isArray() && Parcelable.class.isAssignableFrom(componentType)) {
                        Object[] objectArray = (Object[]) object;
                        object = Arrays.copyOf(objectArray, objectArray.length, (Class<? extends Object[]>) type);
                    }
    
                    field.setAccessible(true);
                    try {
                        field.set(activity, object);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
      public static class SecondActivity extends AppCompatActivity {
            @InjectIntentHelper.InjectIntent(extraName = "extra_string1")
            private String extraString1;
            @InjectIntentHelper.InjectIntent(extraName = "extra_boolean")
            private boolean extraBoolean;
            @InjectIntentHelper.InjectIntent//这边可以省略不写
            private int extraInt;
            @InjectIntentHelper.InjectIntent(extraName = "extra_student_bean")
            private StudentBean extraStudentBean;
            @InjectIntentHelper.InjectIntent(extraName = "extra_student_list")
            private List<StudentBean> extraStudentList;
            @InjectIntentHelper.InjectIntent(extraName = "extra_student_array")
            private StudentBean[] extraStudentArray;
    
            @Override
            protected void onCreate(@Nullable Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
    
                InjectIntentHelper.inject(this);//初始化
            }
        }
    

    三、反射获取泛型的真实类型

    相关文章

      网友评论

          本文标题:注解

          本文链接:https://www.haomeiwen.com/subject/bugmsltx.html