Android ioc架构编写

首先说说java反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为Java的反射机制。

使用场景

  • 逆向代码 ,例如反编译
  • 与注解相结合的框架 例如Retrofit
  • 单纯的反射机制应用框架 例如EventBus 2.x
  • 动态生成类框架 例如Gson

大牛框架

Annotations、ButterKnife、eventBus、afinal、ThinkAndroid还有国内的xutils等

ViewUtils介绍

主要说明xUtils里面的ViewUtils
在BaseActivity中初始化 init
在子集的Activity中赋值

@Inject(R.id.text)
TextView text;

反射实践

首先定义注解User类

public class User {

    private String name;
    private int age;
    private  String eat(String eat){
        System.out.println("eat = "+eat);
        return eat+"真好吃";
    }

}

定义注解对象

@Target(ElementType.FIELD)//用于限制当前自定义注解类作用的对象
//@Retention(RetentionPolicy.SOURCE)//用于源码阶段 该注解类只会在源码出现,当将源码编译成字节码的时候注解信息就被清除
//@Retention(RetentionPolicy.CLASS) //该注解类会被编译到字节码中,但是当虚拟机去加载这个字节码的时候,注解信息就被清除
@Retention(RetentionPolicy.RUNTIME)//该注解类永远保留到被加载到虚拟机中
public @interface ViewInject {
    int age();
    String name();
}

然后将注解对象放到对应的注解类上

@ViewInject(name="张三",age = 12)
private String name;

这里如果将注解放到eat方法上面就会报错,因为定义的注解是FIELD类型

然后开始反射赋值,在字段上面赋值

@ViewInject(name="张三",age = 12)
private String name;

最后在开始通过Java反射赋值

//需求:获取User类中name字段上的自定义注解的值,然后将该值的age通过反射设置给User属性,将name 设置给user的name属性

User user = new User();

第一步 获取user类的字节码

//        user.getClass();
//        User.class;
//        Class.forName("xxxxx")

Class clazz = User.class;

第二步 将字节码里面的name字段获取到,其中getField的方法只能获取public方法

//  clazz.getField(name) 这个方法只能获取声明为public的方法

    Field declaredField = clazz.getDeclaredField("name");
    Field declaredField2 = clazz.getDeclaredField("age");

第三步 获取字段上面的注解对象

    ViewInject annotation = declaredField.getAnnotation(ViewInject.class);

    if(annotation!=null){
        int age = annotation.age();
        String name = annotation.name();
        printlns("age = "+ age + ",name = "+ name);

        /**
         * 通过反射将这个两个值设置给user对象
         */
        declaredField.setAccessible(true);//设置允许反射,其实就是允许暴力反射
        declaredField2.setAccessible(true);
        declaredField.set(user,name);
        declaredField2.set(user,age);
        printlns(user.toString());

    }else {
        printlns("字段上面没有定义注解");
    }


    //通过反射调用eat的方法

    Method declaredMethod = clazz.getDeclaredMethod("eat",String.class);
    declaredMethod.setAccessible(true);
    Object object = declaredMethod.invoke(user, "牛肉拉面");

    printlns((String) object);

这里注意几点:(干货)

  • getDeclaredField和getField区别:第一个是能获取所有字段,第二个是只能获取定义了public的字段,所有如果要获取所有的字段就要用到getDeclaredField
  • setAccessible作用:默认是false,设置是否能够获取私用字段,方法同理。
  • 命名value 和 id 区别:value是抽象方法,在使用时候不需要写,下面就会将到。

自己编写ioc框架

首先我们新建 ViewInject注解类,ViewUtils实现类

在ViewInject类中定义注解的抽象方法名,这里说一下,定义value 和 其他名字比如 id

如果定义了id方法,在使用的时候,需要出现 id 名称

@ViewInject(id = R.id.text)
TextView text;

而如果注解抽象方法明是 value 的话,则不需要

@ViewInject(R.id.text)
TextView text;

然后再ViewUtils类里面定义 init()方法,这里需要订单形参,网上说的好多方法参数大部分使用的是Activity,因为这样是面向父类编程,但是我不这么认为,既然面向父类编程,为啥,不彻底恩,直接用Context 上下文 做为形参,当然也不是说用Activity不对。

写了这些,可以在MainActivity中先定义上

public class MainActivity extends AppCompatActivity {


    @ViewInject(R.id.text)
    TextView text;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewUtils.init(this);

        if(text != null){
            text.setText("这是成功后赋值");
        }
    }
}

最后我们开始编写最核心的逻辑 ViewUtils 类

第一:先编写字段View的绑定

  1. 先获取字节码
  2. 获取所有的字段
  3. 遍历字段查找还有注解字段
  4. 获取注解字段的值
  5. 通过反射,获取类的对应方法,将值赋值给字段

代码实现如下

/**
 * 这是绑定视图View
 * @param context
 * @throws NoSuchMethodException
 * @throws InvocationTargetException
 * @throws IllegalAccessException
 */
private static void bindView(Context context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

    /**
     * 1 获取字节码
     */
    Class clazz = context.getClass();


    /**
     * 2 获取所以的字段
     */
    Field[] fields = clazz.getDeclaredFields();

    if(fields != null && fields.length > 0){
        for (int i =0 ;i<fields.length;i++){

            Field field = fields[i];

            /**
             * 3.遍历字段查找还有注解字段
             */
            ViewInject viewInject = field.getAnnotation(ViewInject.class);

            if(viewInject!=null){
                /**
                 * 4.获取注解字段的值
                 */
                int valueId = viewInject.value();
                if(valueId>0){
                    /**
                     * 通过反射,获取类的对应方法,将值赋值给字段
                     */
                    Method findByIdM = clazz.getMethod("findViewById",int.class);
                    findByIdM.setAccessible(true);
                    Object findByIdObj = findByIdM.invoke(context,valueId);
                    field.setAccessible(true);
                    field.set(context,findByIdObj);
                }
            }

        }
    }

}

下面开始实现绑定点击事件

  1. 先获取字节码
  2. 获取所有的方法
  3. 遍历方法,获取方法上所有的注解
  4. 遍历注解,获取注解的类型,获取注解的注解,判断是否存
  5. 通注解类型获取反射注解的方法,获取所有的字段
  6. 利用动态代理模式将注解的onclickListener进行代理设置,这里需要新建代理类
  7. 获取注解信息,并且实例化
  8. 将实例化的对象进行事件绑定

代理类

public class DayHandler implements InvocationHandler{

    private Map<String,Method> mtdMap;

    private WeakReference<Object> object;


    public DayHandler(Object object){
        this.object = new WeakReference<Object>(object);
    }

    public void addMethod(String name,Method method){
        if(mtdMap == null){
            mtdMap = new HashMap<>();
        }
        mtdMap.put(name,method);
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {

        Object handler = object.get();
        if(handler!=null){
            String name = method.getName();
            method = mtdMap.get(name);
            if(method!=null){
               return method.invoke(handler,objects);
            }

        }
        return null;
    }
}

注解类

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBus {

    String listenerSetter();

    Class listenerType();

    String listenerBackName();
}

这个

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBus(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", listenerBackName = "onClick")
public @interface OnClick {

    int[] value();
}

注解实现

private static void bindOnClick(Context context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

    Class<? extends Context> clazz = context.getClass();
    Method[] methods = clazz.getDeclaredMethods();
    //遍历所有的方法
    for (Method method : methods) {
        Annotation[] annotations = method.getAnnotations();
        //拿到方法上的所有的注解
        for (Annotation annotation : annotations) {
            Class<? extends Annotation> annotationType = annotation.annotationType();
            //拿到注解上的注解
            EventBus eventBus = annotationType.getAnnotation(EventBus.class);
            //如果设置为EventBase
            if (eventBus != null) {
                //取出设置监听器的名称,监听器的类型,调用的方法名
                String listenerSetter = eventBus
                        .listenerSetter();
                Class<?> listenerType = eventBus.listenerType();
                String methodName = eventBus.listenerBackName();
                try {
                    //拿到Onclick注解中的value方法
                    Method aMethod = annotationType.getDeclaredMethod("value");
                    //取出所有的viewId
                    int[] viewIds = (int[]) aMethod.invoke(annotation, null);
                    //通过InvocationHandler设置代理
                    DayHandler handler = new DayHandler(context);
                    handler.addMethod(methodName, method);
                    Object listener = Proxy.newProxyInstance(
                            listenerType.getClassLoader(),
                            new Class<?>[] { listenerType }, handler);
                    //遍历所有的View,设置事件
                    for (int viewId : viewIds) {
                        Method findViewById = clazz.getMethod("findViewById", int.class);
                        Object view = findViewById.invoke(context, viewId);
                        Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
                        setEventListenerMethod.invoke(view, listener);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章