手写一套简易的IOC框架---FindViewById

一. 反射
1.反射的简介
JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性并且能进行修改;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

2.反射的作用
反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。
(1)进行灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;
(2)还有动态代理、工厂模式的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!
(3)获取系统隐藏类的信息,并进行修改;

3.获取Class 的三种方式
(1)通过对象调用 getClass() 方法来获取,通常应用在:传入一个类型的对象,但又不知道传入的具体是什么类,用这种方法,比如传过来一个 Object 类型的对象;
(2)直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高 ;
(3)通过 Class 对象的 forName() 静态方法来获取,是最常用的方式, 但是有时候会抛出 ClassNotFoundException的 异常;
Class c3 = Class.forName(“com.aj.Person”);
(4)通过ClassLoader获取:
ClassLoader cl = context.getClassLoader();
@SuppressWarnings(“rawtypes”)
Class SystemProperties = cl.loadClass(“android.os.SystemProperties”);

通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等查阅 API 可以看到 Class 有很多方法:
getName():获得类的完整名字。
getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors():获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
newInstance():通过类的不带参数的构造方法创建这个类的一个对象。

二. 注解

  1. @Retention代表注解的保留策略:
    (1)RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
    (2)RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,无法通过反射读取,这是默认的生命周期;
    (3)RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,可以通过反射读取;

这3个生命周期分别对应于:Java源文件(.java文件) —> .class文件 —> 内存中的字节码,生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和@SuppressWarnings,则可选用 SOURCE 注解。

  1. @Target代表注解可能出现的语法位置,即可以在哪里使用定义的注解,可选的位置如下:
    ElementType.TYPE类、接口(包括注解类型)或枚举声明
    ElementType.FIELD字段声明
    ElementType.METHOD方法声明
    ElementType.PARAMETER方法的参数声明
    ElementType.CONSTRUCTOR类的构造法声明
    ElementType.LOCAL_VARIABLE局部变量声明
    ElementType.ANNOTATION_TYPE注解声明
    ElementType.PACKAGE包声明
    ElementType.TYPE_PARAMETERJDK1.8新加的,类型参数声明
    ElementType.TYPE_USEJDK1.8新加的,类型使用声明

三. IOC注解框架
本次学习和笔记的内容就是通过使用上述的反射和注解,写一套FindViewById的IOC框架,以减少在项目中重复写相同的代码,并加深对反射和注解的理解;

  1. 注解:
    FindViewById的注解
package com.bombking.bkchat.baselib.ioc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FindViewById {
    //表示可以传值
    int value();
}

点击事件的注解

package com.bombking.bkchat.baselib.ioc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) //运行时
public @interface OnClick {
    int[] value();
}

  1. IOC工具类
package com.bombking.bkchat.baselib.ioc;

import android.app.Activity;
import android.view.View;

import com.bombking.bkchat.baselib.net.CheckNet;
import com.bombking.bkchat.baselib.net.NetManagerUtil;
import com.bombking.bkchat.utils.ToastUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ViewIoc {

    // 绑定注解
    public static void bindInit(Activity activity){
        findViewById(activity);

        onClickListener(activity);
    }

    /**
     * find view by id
     * @param activity
     */
    private static void findViewById(Activity activity){
        Class<?> clazz = activity.getClass();

        //1.获取activity中的所有属性;  clazz.getFields() -> 获取public的属性
        Field[] fields = clazz.getDeclaredFields();

        //2.遍历获取注解的属性;
        for (Field field : fields){
            FindViewById findViewById =  field.getAnnotation(FindViewById.class);

            //3.遍历所有的属性,如果注解不是空,获取注解的viewId
            if (findViewById != null){
                int viewId = findViewById.value();
                //4.findViewById,获取view;
                View view = activity.findViewById(viewId);

                //5.把view动态的注入activity属性;相当于把注解获取到的属性赋值给activity中的成员变量;
                try {
                    field.setAccessible(true);//确定可以操作私有的属性
                    field.set(activity, view);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 点击事件
     * @param activity
     */
    private static void onClickListener(final Activity activity) {
        Class<?> clazz = activity.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (final Method method : methods){
             OnClick onClick = method.getAnnotation(OnClick.class);
             final boolean isNet = method.getAnnotation(CheckNet.class) != null;//加入一个网络检查注解
             if (onClick != null){
                 //获取所有方法上面注解的View的id值;
                 int[] values = onClick.value();
                 //遍历找出所有的view;
                 for (int viewId : values){
                     View view = activity.findViewById(viewId);
                     view.setOnClickListener(new View.OnClickListener() {
                         @Override
                         public void onClick(View v) {
                             //检查网络是否可用
                             if (isNet){
                                 if (!NetManagerUtil.cureentNetIsAvailable(activity)){
                                     ToastUtils.getInstance().toastShort("当前无网络");//无网络时的操作
                                     return;
                                 }
                             }
                             //使用反射
                             method.setAccessible(true);//私有方法权限
                             try {
                                 method.invoke(activity);
                             } catch (Exception e) {
                                 try {
                                     method.invoke(activity, view);//兼容有view的情况
                                 } catch (Exception e1) {
                                     e1.printStackTrace();
                                 }
                                 e.printStackTrace();
                             }
                         }
                     });
                 }
             }
        }
    }

}
  1. 使用
 @FindViewById(R.id.btn_register)
    Button button;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ViewIoc.bindInit(this);
    }
    @OnClick({R.id.bt1,R.id.bt2})
    @CheckNet
    private void getData(){
        Toast.makeText(this,"请求后台数据",Toast.LENGTH_LONG).show();
    }

结束语:我是一个入行不久的小菜鸟,如有不正确的地方,欢迎指正,感谢你的阅读;

上一篇:Android:如何从Fragment onCreateView()访问Activity的视图(使用findViewById)?


下一篇:第一个安卓程序代码