自定义 Android IOC 框架

image

概述

什么是 IOC

Inversion of Control,英文缩写为 IOC,意思为控制反转。

具体什么含义呢?

假设一个类中有很多的成员变量,如果你需要用到里面的成员变量,传统做法是 new 出来进行使用,但是在 IOC 的原则中,我们不要 new,因为这样的耦合度太高,我们可以在需要注入(new)的成员变量上添加注解,等待加载这个类的时候,则进行注入。

那么怎么进行注入呢?

简单的说,就是通过反射的方式,将字符串类路径变为类。

什么是反射

JAVA 并不是一种动态变成语言,为了使语言更加灵活,JAVA 引入了反射机制。JAVA 反射机制是在运行过程中,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个属性,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为 JAVA 语言的反射机制。

什么是注解

JAVA 1.5 之后引入的注解和反射,注解的实现依赖于反射。JAVA 中的注解是一种继承自接口 java.lang.annotation.Annotation 的特殊接口。
那么接口怎么能够设置属性呢?
简单来说就是 JAVA 通过动态代理的方式为你生成了一个实现了接口 Annotation 的实例,然后对该代理实例的属性赋值,这样就可以在程序运行时(如果将注解设置为运行时可见的话)通过反射获取到注解的配置信息。说的通俗一点,注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记。程序可以利用JAVA的反射机制来了解你的类及各种元素上有无何种标记,针对不同的标记,就去做相应的事件。标记可以加在包,类,方法,方法的参数以及成员变量上。

实现

定义注解

  1. 布局注解
/**
 * Created by Keven on 2019/6/3.
 *
 * 布局注解
 */
//RUNTIME 运行时检测,CLASS 编译时检测  SOURCE 源码资源时检测
@Retention(RetentionPolicy.RUNTIME)
//TYPE 用在类上  FIELD 注解只能放在属性上  METHOD 用在方法上  CONSTRUCTOR 构造方法上
@Target(ElementType.TYPE)
public @interface KevenContentViewInject {
    int value();//代表可以 Int 类型,取注解里面的参数
}
  1. 属性组件注解
/**
 * Created by Keven on 2019/6/3.
 *
 * 组件注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)//用在属性字段上
public @interface KevenViewInject {
    int value();
}
  1. 事件注解
/**
 * Created by zhengjian on 2019/6/3.
 *
 * 事件注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)//使用在方法上
public @interface KevenOnClickInject {
    //会有很多个点击事件,所以使用数组
    int[] value();
}

实现注入工具类

/**
 * Created by Keven on 2019/6/3.
 * 

* InjectUtils 注入工具类 */ public class InjectUtils { //注入方法 Activity public static void inject(Activity activity) { injectLayout(activity); injectViews(new ViewFinder(activity), activity); injectEvents(new ViewFinder(activity), activity); } //注入方法 View public static void inject(View view, Activity activity) { injectViews(new ViewFinder(view), activity); injectEvents(new ViewFinder(view), activity); } //注入方法 Fragment public static void inject(View view, Object object) { injectViews(new ViewFinder(view), object); injectEvents(new ViewFinder(view), object); } /** * 事件注入 */ private static void injectEvents(ViewFinder viewFinder, Object object) { // 1.获取所有方法 Class clazz = object.getClass(); Method[] methods = clazz.getDeclaredMethods(); // 2.获取方法上面的所有id for (Method method : methods) { KevenOnClickInject onClick = method.getAnnotation(KevenOnClickInject.class); if (onClick != null) { int[] viewIds = onClick.value(); if (viewIds.length > 0) { for (int viewId : viewIds) { // 3.遍历所有的id 先findViewById然后 setOnClickListener View view = viewFinder.findViewById(viewId); if (view != null) { view.setOnClickListener(new DeclaredOnClickListener(method, object)); } } } } } } private static class DeclaredOnClickListener implements View.OnClickListener { private Method mMethod; private Object mHandlerType; public DeclaredOnClickListener(Method method, Object handlerType) { mMethod = method; mHandlerType = handlerType; } @Override public void onClick(View v) { // 4.反射执行方法 mMethod.setAccessible(true); try { mMethod.invoke(mHandlerType, v); } catch (Exception e) { e.printStackTrace(); try { mMethod.invoke(mHandlerType, null); } catch (Exception e1) { e1.printStackTrace(); } } } } //控件注入 private static void injectViews(ViewFinder viewFinder, Object object) { //获取每一个属性上的注解 Class myClass = object.getClass(); Field[] myFields = myClass.getDeclaredFields();//先拿到所有的成员变量 for (Field field : myFields) { KevenViewInject myView = field.getAnnotation(KevenViewInject.class); if (myView != null) { int value = myView.value();//拿到属性id View view = viewFinder.findViewById(value); //将view 赋值给类里面的属性 try { field.setAccessible(true);//为了防止其是私有的,设置允许访问 field.set(object, view); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } private static void injectLayout(Activity activity) { //获取我们自定义类KevenContentViewInject 上面的注解 Class myClass = activity.getClass(); KevenContentViewInject myContentView = myClass.getAnnotation(KevenContentViewInject.class); if (myContentView!=null){ int myLayoutResId = myContentView.value(); activity.setContentView(myLayoutResId); } } }

定义 ViewFinder 类

用于注入工具类中的 findViewById

/**
 * Created by Keven on 2019/6/3.
 */
final class ViewFinder {

    private View view;
    private Activity activity;

    public ViewFinder(View view) {
        this.view = view;
    }

    public ViewFinder(Activity activity) {
        this.activity = activity;
    }

    public View findViewById(int id) {
        if (view != null) return view.findViewById(id);
        if (activity != null) return activity.findViewById(id);
        return null;
    }


    public View findViewById(int id, int pid) {
        View pView = null;
        if (pid > 0) {
            pView = this.findViewById(pid);
        }

        View view = null;
        if (pView != null) {
            view = pView.findViewById(id);
        } else {
            view = this.findViewById(id);
        }
        return view;
    }

    /*public Context getContext() {
        if (view != null) return view.getContext();
        if (activity != null) return activity;
        return null;
    }*/
}

使用 IOC 框架

布局文件



    
    

Activity 代码

//布局文件注入
@KevenContentViewInject(R.layout.activity_ioc)
public class IocActivity extends AppCompatActivity {

    //属性控件注入
    @KevenViewInject(R.id.tv_title)
    private TextView tv_title;
    @KevenViewInject(R.id.bt_pop)
    private Button bt_pop;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //注入工具绑定
        InjectUtils.inject(this);
    }
    
    //点击事件注入
    @KevenOnClickInject(R.id.bt_pop)
    public void change(){
        tv_title.setText("hello IOC");
        Toast.makeText(this,"Hello IOC",Toast.LENGTH_SHORT).show();
    }
}

当我们点击弹窗按钮时,上方 TextView 内容会改变,并且有 Toast 弹出。

image

你可能感兴趣的:(自定义 Android IOC 框架)