Android自我提升二 揭秘IOC注入框架,轻松实现布局、属性、事件注入

基础知识讲解

什么是DIP、IOC、DI、IOC容器

DIP、IOC、DI、IOC容器的含义

IOC容器的技术剖析

IOC中最基本的技术就是“反射(Reflection)”编程
我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。

实际开发用法

依赖注入

技术解析
看到具体实现,有很多朋友一定会觉得很熟悉的,这不是butterknife么?那说明朋友们还是经常会用到这些技术,因为butterknife在3之前及3的部分都是运用的“反射”技术,还有我们常用的EventBus等等都用到了这项技术,但是一个三方框架,可能百分之八十的技术是我们用不到的。

目的与优缺点

  1. IOC 技术核心是解耦,降低模块之间的关联
  2. 优点:代码少,加速开发
  3. 缺点:反射产生性能损耗

具体开发实现

核心技术:反射

反射常用API
getMethod()与getDeclareMathods区别

  1. getMethod() 获取当前类和父类 public方法
  2. getDeclareMathods() 获取当前类所有方法

布局注解代码

package com.fly.newstart.ioc.annotetion;

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

/**
 * <pre>
 *           .----.
 *        _.'__    `.
 *    .--(Q)(OK)---/$\
 *  .' @          /$$$\
 *  :         ,   $$$$$
 *   `-..__.-' _.-\$/
 *         `;_:    `"'
 *       .'"""""`.
 *      /,  FLY  ,\
 *     //         \\
 *     `-._______.-'
 *     ___`. | .'___
 *    (______|______)
 * </pre>
 * 包    名 : com.fly.newstart.ioc
 * 作    者 : FLY
 * 创建时间 : 2019/4/24
 * 描述: 布局注入注解
 */


@Target(ElementType.TYPE) //该注解作用于什么之上 ,对应枚举标识 METHOD-标识为类之上
@Retention(RetentionPolicy.RUNTIME)//jvm在运行时通过反射获取注解的值
//RUNTIME-jvm在运行时通过反射来完成的过程
//CLASS-在编译时进行一些预操作,并且注解会在class存在
//SOURCE-源码级的,主要是做一些检查检测操作
public @interface ContentView {
    int value();
}

属性注解代码

package com.fly.newstart.ioc.annotetion;

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

/**
 * <pre>
 *           .----.
 *        _.'__    `.
 *    .--(Q)(OK)---/$\
 *  .' @          /$$$\
 *  :         ,   $$$$$
 *   `-..__.-' _.-\$/
 *         `;_:    `"'
 *       .'"""""`.
 *      /,  FLY  ,\
 *     //         \\
 *     `-._______.-'
 *     ___`. | .'___
 *    (______|______)
 * </pre>
 * 包    名 : com.fly.newstart.ioc.annotetion
 * 作    者 : FLY
 * 创建时间 : 2019/4/24
 * 描述: 属性的注解
 */

@Target(ElementType.FIELD) //该注解作用于什么之上 ,对应枚举标识 FIELD-标识为属性之上
@Retention(RetentionPolicy.RUNTIME)//jvm在运行时通过反射获取注解的值
public @interface InjectView {
    int value();
}

点击事件注解代码

package com.fly.newstart.ioc.annotetion;

import android.view.View;

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

/**
 * <pre>
 *           .----.
 *        _.'__    `.
 *    .--(Q)(OK)---/$\
 *  .' @          /$$$\
 *  :         ,   $$$$$
 *   `-..__.-' _.-\$/
 *         `;_:    `"'
 *       .'"""""`.
 *      /,  FLY  ,\
 *     //         \\
 *     `-._______.-'
 *     ___`. | .'___
 *    (______|______)
 * </pre>
 * 包    名 : com.fly.newstart.ioc.annotetion
 * 作    者 : FLY
 * 创建时间 : 2019/4/24
 * 描述: 点击事件的注解
 */

@Target(ElementType.METHOD) //该注解作用于什么之上 ,对应枚举标识 METHOD-标识为方法之上
@Retention(RetentionPolicy.RUNTIME)//jvm在运行时通过反射获取注解的值
@EventBase(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class, listenerCallBack = "onClick")
public @interface OnClick {
    int[] value();
}

长按事件注解代码

package com.fly.newstart.ioc.annotetion;

        import android.view.View;

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

/**
 * <pre>
 *           .----.
 *        _.'__    `.
 *    .--(Q)(OK)---/$\
 *  .' @          /$$$\
 *  :         ,   $$$$$
 *   `-..__.-' _.-\$/
 *         `;_:    `"'
 *       .'"""""`.
 *      /,  FLY  ,\
 *     //         \\
 *     `-._______.-'
 *     ___`. | .'___
 *    (______|______)
 * </pre>
 * 包    名 : com.fly.newstart.ioc.annotetion
 * 作    者 : FLY
 * 创建时间 : 2019/4/24
 * 描述: 长按事件的注解
 */

@Target(ElementType.METHOD) //该注解作用于什么之上 ,对应枚举标识 METHOD-标识为方法之上
@Retention(RetentionPolicy.RUNTIME)//jvm在运行时通过反射获取注解的值
@EventBase(listenerSetter = "setOnLongClickListener", listenerType = View.OnLongClickListener.class, listenerCallBack = "onLongClick")
public @interface OnLongClick { //注解对应方法需要boolean返回值
    int[] value();
}

事件注解的注解代码

package com.fly.newstart.ioc.annotetion;

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

/**
 * <pre>
 *           .----.
 *        _.'__    `.
 *    .--(Q)(OK)---/$\
 *  .' @          /$$$\
 *  :         ,   $$$$$
 *   `-..__.-' _.-\$/
 *         `;_:    `"'
 *       .'"""""`.
 *      /,  FLY  ,\
 *     //         \\
 *     `-._______.-'
 *     ___`. | .'___
 *    (______|______)
 * </pre>
 * 包    名 : com.fly.newstart.ioc.annotetion
 * 作    者 : FLY
 * 创建时间 : 2019/4/24
 * 描述: 事件注解的注解 用于封装点击事件规律的对象
 */

@Target(ElementType.ANNOTATION_TYPE) //该注解作用于什么之上 ,对应枚举标识 ANNOTATION_TYPE-标识为注解之上
@Retention(RetentionPolicy.RUNTIME)//jvm在运行时通过反射获取注解的值
public @interface EventBase {

    //setxxxListener
    String listenerSetter();

    //new View.xxxListener
    Class<?> listenerType();

    //回调执行方法:onxxx()
    String listenerCallBack();

}

注入管理类代码

package com.fly.newstart.ioc;

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

import com.fly.newstart.ioc.annotetion.ContentView;
import com.fly.newstart.ioc.annotetion.EventBase;
import com.fly.newstart.ioc.annotetion.InjectView;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


/**
 * <pre>
 *           .----.
 *        _.'__    `.
 *    .--(Q)(OK)---/$\
 *  .' @          /$$$\
 *  :         ,   $$$$$
 *   `-..__.-' _.-\$/
 *         `;_:    `"'
 *       .'"""""`.
 *      /,  FLY  ,\
 *     //         \\
 *     `-._______.-'
 *     ___`. | .'___
 *    (______|______)
 * </pre>
 * 包    名 : com.fly.newstart.ioc
 * 作    者 : FLY
 * 创建时间 : 2019/4/24
 * 描述: 注入管理类
 */

public class InjectManager {
    /**
     * 注解的初始注入
     *
     * @param activity
     */
    public static void inject(Activity activity) {

        //布局的注入
        injectLayout(activity);

        //控件的注入
        injectView(activity);

        //事件的注入
        injectEvents(activity);

    }

    /**
     * 布局的注入
     *
     * @param activity
     */
    private static void injectLayout(Activity activity) {
        //获取类
        Class<? extends Activity> cla = activity.getClass();
        //获取类上的注解
        ContentView contentView = cla.getAnnotation(ContentView.class);
        //获取注解的值
        if (contentView != null) {
            int layoutId = contentView.value();
            // 执行方法:setContentView(R.layout.activity_ioc);
            // 方法一:activity.setContentView(layoutId);
            // 方法二:反射获取,高大上
            try {
                //setContentView 是父类的方法不能使用getDeclareMathods(),需要使用getMethod()
                Method method = cla.getMethod("setContentView", int.class);
                //执行方法
                method.invoke(activity, layoutId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 控件的注入
     *
     * @param activity
     */
    private static void injectView(Activity activity) {
        //获取类
        Class<? extends Activity> cla = activity.getClass();
        //获取类的所有属性
        Field[] fields = cla.getDeclaredFields();
        // 循环 拿到每个属性
        if (fields == null || fields.length < 1) return;
        for (Field field : fields) {
            // 获取每个属性的注解
            InjectView injectView = field.getAnnotation(InjectView.class);
            if (injectView != null) { //并不是所有属性都有注解
                //获取注解的值
                int viewId = injectView.value();
                //执行方法:mBtn01 = findViewById(R.id.btnO1)
                try {
                    Method method = cla.getMethod("findViewById", int.class); //findViewById 需要赋值
                    //执行方法 获取返回值
                    Object view = method.invoke(activity, viewId);
                    //设置私有属性的访问权限,默认为false
                    field.setAccessible(true);
                    //给属性赋值
                    field.set(activity, view);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
    }

    /**
     * 事件的注入
     *
     * @param activity
     */
    private static void injectEvents(Activity activity) {
        //获取类
        Class<? extends Activity> cla = activity.getClass();
        //获取类的所有方法,事件注解肯定是当前类
        Method[] methods = cla.getDeclaredMethods();
        if (methods == null || methods.length < 1) return;
        for (Method method : methods) {
            //获取每个方法的注解
            Annotation[] annotations = method.getAnnotations();
            //一个可能有多个注解
            if (annotations == null || annotations.length < 1) continue;
            for (Annotation annotation : annotations) {
                //获取注解上的注解类型
                Class<? extends Annotation> annotationType = annotation.annotationType();
                if (annotationType != null) {
                    //通过EventBase注解,获取3个重要的规律
                    EventBase eventBase = annotationType.getAnnotation(EventBase.class);
                    //事件的3个规律
                    String listenerSetter = eventBase.listenerSetter();
                    Class<?> listenerType = eventBase.listenerType();
                    String listenerCallBack = eventBase.listenerCallBack();
                    //获取注解的值
                    try {
                        // 通过运行annotationType获取OnClick注解的value值
                        Method valueMethod = annotationType.getDeclaredMethod("value");
                        //运行OnClick注解的value方法获取value值
                        int[] viewIds = (int[]) valueMethod.invoke(annotation);

                        //打包之后,代理处理后续工作,可以兼容多种事件方式,不需要写接口
                        //创建拦截器
                        ListenerInvocationHandler handler = new ListenerInvocationHandler(activity);
                        handler.addMethod(listenerCallBack, method);
                        //创建代理
                        Object listene = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, handler);
                        if (viewIds == null || viewIds.length < 1) continue;
                        for (int viewId : viewIds) {
                            //控件的赋值,保证控件没有赋值也可使用
                            View view = activity.findViewById(viewId);
                            //获取set方法
                            Method setter = view.getClass().getMethod(listenerSetter, listenerType);
                            //执行方法,传入代理
                            setter.invoke(view, listene);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

事件处理拦截器代码

package com.fly.newstart.ioc;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * <pre>
 *           .----.
 *        _.'__    `.
 *    .--(Q)(OK)---/$\
 *  .' @          /$$$\
 *  :         ,   $$$$$
 *   `-..__.-' _.-\$/
 *         `;_:    `"'
 *       .'"""""`.
 *      /,  FLY  ,\
 *     //         \\
 *     `-._______.-'
 *     ___`. | .'___
 *    (______|______)
 * </pre>
 * 包    名 : com.fly.newstart.ioc
 * 作    者 : FLY
 * 创建时间 : 2019/4/24
 * 描述: 事件处理拦截器
 */
public class ListenerInvocationHandler implements InvocationHandler {

    //需要拦截的对象
    private Object target;

    //需要拦截的方法集合
    private HashMap<String, Method> methodhMap = new HashMap<>();

    public ListenerInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (target != null) {
            //获取需要拦截的方法名
            String methodName = method.getName();
            //重新赋值,将拦截的方法换为了自定义的方法
            method = methodhMap.get(methodName);//从集合中判断是否需要拦截
            if (method != null) {//确定找到了需要拦截的的方法,才执行自定义方法
                if (method.getParameterTypes().length == 0) { //判断有无参数
                    return method.invoke(target);
                } else return method.invoke(target, args);
            }
        }
        return null;
    }

    /**
     * 将需要拦截的方法加入集合
     *
     * @param methodName 需要拦截的方法名
     * @param method     执行自定义的方法
     */
    public void addMethod(String methodName, Method method) {
        methodhMap.put(methodName, method);
    }
}

测试Activity代码

package com.fly.newstart.ioc.text;

import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.fly.newstart.R;
import com.fly.newstart.common.base.BaseActivity;
import com.fly.newstart.ioc.annotetion.ContentView;
import com.fly.newstart.ioc.annotetion.InjectView;
import com.fly.newstart.ioc.annotetion.OnClick;
import com.fly.newstart.ioc.annotetion.OnLongClick;

// setContentView(R.layout.activity_ioc);
@ContentView(R.layout.activity_ioc)
public class IOCActivity extends BaseActivity {

    @InjectView(R.id.btnO1) //mBtn01 = findViewById(R.id.btnO1)
    private Button mBtn01;

    @InjectView(R.id.btnO2)
    private Button mBtn02;

    @Override
    protected void onResume() {
        super.onResume();
        Toast.makeText(this, mBtn01.getText().toString(), Toast.LENGTH_SHORT).show();
    }

    // mBtn01.setOnClickListener(new View.OnClickListener() {@Override public void onClick(View v) { //  }});
    @OnClick({R.id.btnO1, R.id.btnO2})
    public void show(View view) {
        Toast.makeText(this, "shwo(View view)", Toast.LENGTH_SHORT).show();
    }

    // mBtn01.setOnLongClickListener(new View.OnLongClickListener() {@Override public boolean onLongClick(View v) {   return false;  }});
    @OnLongClick({R.id.btnO1, R.id.btnO2})
    public boolean showLong(View view) {
        Toast.makeText(this, "shwoLong(View view)", Toast.LENGTH_SHORT).show();
        return false;
    }


//    @OnClick({R.id.btnO1,R.id.btnO2}) //proxy 代理
//    public void show(){
//        Toast.makeText(this, mBtn01.getText().toString(), Toast.LENGTH_LONG).show();
//    }

    public void initView() {
        //用注解实现,需要代理完成,监听回调的过程
        //Android监听事件是有规律的
        //setxxxListener
        //new View.xxxListener
        //回调执行方法:onxxx()
        //将规律打包成一个对象,用代理去完成这个事件
        //还需要用到AOP  面向切面的技术
        mBtn01.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

        mBtn02.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });


        mBtn01.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return false;
            }
        });
    }
}

baseActivity代码

package com.fly.newstart.common.base;

import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import com.fly.newstart.R;
import com.fly.newstart.ioc.InjectManager;

/**
 *           .----.
 *        _.'__    `.
 *    .--(Q)(OK)---/$\
 *  .' @          /$$$\
 *  :         ,   $$$$$
 *   `-..__.-' _.-\$/
 *         `;_:    `"'
 *       .'"""""`.
 *      /,  FLY  ,\
 *     //         \\
 *     `-._______.-'
 *     ___`. | .'___
 *    (______|______)
 * </pre>
 * 包    名 : com.fly.newstart.common.base
 * 作    者 : FLY
 * 创建时间 : 2017/8/7
 * <p>
 * 描述: Activity的基类
 */

public class BaseActivity extends AppCompatActivity {
  


    @Override
    protected  void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 帮助所有子类完成注入工作
        InjectManager.inject(this);
    }

  
}

难点剖析

布局注入与属性注入都比较简单,只是运用反射的基本知识,但是方法的注入就比较难以理解,其中还运用了Prixy(代理)和InvocationHandler(拦截)的技术。

代理的原理与用法

代理的原理与用法

Android监听事件规律-三部曲
Android监听事件的三部曲

将规律打包成一个对象,用代理去完成这个事件,但是这样任然不能完成我们需要的功能,还需要用到AOP 面向切面的技术。

AOP原理和拦截

AOP原理和拦截

小结

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

推荐阅读更多精彩内容