24

自定义 Android IOC 框架 - 简书

 4 years ago
source link: https://www.jianshu.com/p/d12b2752c763?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

自定义 Android IOC 框架

0.4232019.06.03 17:22:39字数 651阅读 523
webp
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的反射机制来了解你的类及各种元素上有无何种标记,针对不同的标记,就去做相应的事件。标记可以加在包,类,方法,方法的参数以及成员变量上。

/**
 * 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();
}
/**
 * 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.
 * <p>
 * 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 框架

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ioc.IocActivity">
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/dimen_35dp"
        android:text="你好,IOC"
        android:textSize="25sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
    <Button
        android:id="@+id/bt_pop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/dimen_35dp"
        android:text="弹窗"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_title"/>

</android.support.constraint.ConstraintLayout>

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK