自定义 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.
 * <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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和...
    九尾喵的薛定谔阅读 3,149评论 0 2
  • 2.1 我们的理念是:让别人为你服务 IoC是随着近年来轻量级容器(Lightweight Container)的...
    好好学习Sun阅读 2,702评论 0 11
  • 要实现的功能: 将对象的实例化交给自定的ioc容器. 通过注解的方式对接口进行依赖注入 通过getBean("us...
    lisongyu阅读 491评论 0 0
  • 作为一名Android程序员,对于上面这种机械化的代码你一定写到想吐了,或许多数时候你只是copy ,paste,...
    尹star阅读 28,535评论 13 78
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,459评论 0 4