Xutils Ioc实现


概述

Xutils Ioc实现

什么是IOC

Ioc也叫 控制反转(Inversion of Control,英文缩写为IoC)把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。通俗点的意思就是说把创建对象的权利交给框架来实现,比如这个事情本身是我做的,但是我可以交给你来完成

Xutils Ioc使用

使用Gradle构建时添加一下依赖即可:
compile 'org.xutils:xutils:3.5.0'

需要的权限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Java代码的编写

//首先在BaseActivity里面执行注册
public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        x.view().inject(this);
    }
}

之后就可以在子类里面通过下面注解的形式来填充操作,比如设置setContentView,findViewById的操作,甚至事件的操作,

@ContentView(R.layout.activity_main)//setContentView的操作
public class MainActivity extends BaseActivity {

    @ViewInject(R.id.container)
    private ViewPager mViewPager;

    @ViewInject(R.id.toolbar)
    private Toolbar toolbar;

    @ViewInject(R.id.tabs) //findViewById的操作
    private TabLayout tabLayout;

    ...

    @Event(R.id.btn_test_db)//事件的绑定操作
    private void onTestDbClick(View view) {

    }
}

下面 分析 xUtils中的IOC实现

Xutils Ioc分析

1.@ContentView(R.layout.activity_main) 原理实现:

ContentView注解的声明为:

//标识注解用在类上面
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    //注解的参数
    int value();
}

首先我们在BaseActivity中执行了注册
public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        x.view().inject(this);
    }
}

执行了 x.view().inject(this);的操作,最终会执行到这个方法
@Override
public void inject(Activity activity) {
    //获取Activity的ContentView的注解
    Class<?> handlerType = activity.getClass();
    try {
        //获取注注解
        ContentView contentView = findContentView(handlerType);
        if (contentView != null) {
            //如果不为空,就获取到注解参数的内容
            int viewId = contentView.value();
            if (viewId > 0) {
                //获取到setContentView的 Method
                Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);
                //然后通过反射的方式来调用,这样就相当于是调用了setContentView(viewId)
                setContentViewMethod.invoke(activity, viewId);
            }
        }
    } catch (Throwable ex) {
        LogUtil.e(ex.getMessage(), ex);
    }

    //对于ViewInject 已经 Event 注解原理实现的地方,后面会介绍到:
    injectObject(activity, handlerType, new ViewFinder(activity));
}

findContentView(handlerType)的实现:
//首先从当前类中查找 是否有 ContentView.class 注解,如果没有,再从父类中查找 
private static ContentView findContentView(Class<?> thisCls) {
    if (thisCls == null || IGNORED.contains(thisCls)) {
        return null;
    }
    ContentView contentView = thisCls.getAnnotation(ContentView.class);
    if (contentView == null) {
        return findContentView(thisCls.getSuperclass());
    }
    return contentView;
}

所以对于@ContentView(R.layout.activity_main) 所做的事无非就是获取到注解上面传递的参数,然后通过反射的方式调用setContentView来设置布局

分析 @ViewInject(R.id.toolbar)
private Toolbar toolbar;

ViewInject注解的声明为:

//标识这个注解可以用在成员变量上
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {

    int value();

    /* parent view id */
    int parentId() default 0;
}

前面分析到了这个地方  injectObject(activity, handlerType, new ViewFinder(activity)); ,现在就可以接着分析了,下面是这个函数的关键代码

// inject view
Field[] fields = handlerType.getDeclaredFields();
if (fields != null && fields.length > 0) {
    for (Field field : fields) {
        //获取到成员变量的class类型,判断注入的字段的是否合法,这里不允许有静态字段,final字段,基本类型,数组类型
        Class<?> fieldType = field.getType();
        if (
            /* 不注入静态字段 */     Modifier.isStatic(field.getModifiers()) ||
            /* 不注入final字段 */    Modifier.isFinal(field.getModifiers()) ||
            /* 不注入基本类型字段 */  fieldType.isPrimitive() || /* 不注入数组类型字段 */  fieldType.isArray())
        {
            continue;
        }

        //如果viewInject不为空,则说明当前的字段field字段含有ViewInject的注解
        ViewInject viewInject = field.getAnnotation(ViewInject.class);
        if (viewInject != null) {
            try {
                //viewInject.value() 获取到注解的值,通过持有Activity的引用调用findViewById来获取到对应的View
                View view = finder.findViewById(viewInject.value(), viewInject.parentId());
                if (view != null) {
                    //然后将获取到的view,通过反射的方式给这个字段赋值,这样这个字段就有值了,可以正常使用了
                    field.setAccessible(true);
                    field.set(handler, view);
                } else {
                        throw new RuntimeException("Invalid @ViewInject for "+ handlerType.getSimpleName() + "." + field.getName());
                    }
                } catch (Throwable ex) {
                    LogUtil.e(ex.getMessage(), ex);
            }
        }
    }
} // end inject view

finder.findViewById(viewInject.value(), viewInject.parentId()); 函数的实现:

先来看看ViewFinder主要是用来执行findViewById,至于找的过程也是很简单的。通过持有Activity,或者View的 引用,调用对应的findViewById的方式来获取,至于初始化的地方为:
injectObject(activity, handlerType, new ViewFinder(activity)),发现没有这里传递进了activity的引用

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 findViewByInfo(ViewInfo info) {
        return findViewById(info.value, info.parentId);
    }

    //可以发现findViewById还是通过传递进来的activity或者View findViewById来实现的。。。这里为什么不用反射,估计是因为反射的性能不好,所以用原始的方式来调用就最好
    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;
    }
}

所以对于@ViewInject(R.id.toolbar) 所做的事无非就是通过持有要注册Activity对象引用,然后通过findViewById的方式来获取到对应view,然后获取到ViewInject注解的值,
找到对应的成员变量,然后通过反射的方式将这个view的值设置到这个字段上面,这样就完成了赋值操作,其实findViewById完全也可以通过反射的方式来获取,但是这样性能就比较低
由于是对于大量的findViewById操作


3 分析事件的绑定 @Event(R.id.btn_test_db)//事件的绑定操作
private void onTestDbClick(View view) {
}

Event注解的声明为:

/**
 * 事件注解.
 * 被注解的方法必须具备以下形式:
 * 1. private 修饰
 * 2. 返回值类型没有要求
 * 3. 参数签名和type的接口要求的参数签名一致.
 * Author: wyouflf
 * Date: 13-9-9
 * Time: 下午12:43
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {

    /**
     * 控件的id集合, id小于1时不执行ui事件绑定.
     *
     * @return
     */
    int[] value();

    /**
     * 控件的parent控件的id集合, 组合为(value[i], parentId[i] or 0).
     *
     * @return
     */
    int[] parentId() default 0;

    /**
     * 事件的listener, 默认为点击事件.
     *
     * @return
     */
    Class<?> type() default View.OnClickListener.class;

    /**
     * 事件的setter方法名, 默认为set+type#simpleName.
     *
     * @return
     */
    String setter() default "";

    /**
     * 如果type的接口类型提供多个方法, 需要使用此参数指定方法名.
     *
     * @return
     */
    String method() default "";
}


// inject event 方法实现
//获取到所有的方法
Method[] methods = handlerType.getDeclaredMethods();
if (methods != null && methods.length > 0) {
    for (Method method : methods) {

    //如果方法为静态或者为私有的,就会跳过
    if (Modifier.isStatic(method.getModifiers()) || !Modifier.isPrivate(method.getModifiers())) {
        continue;
    }

    //检查当前方法是否是event注解的方法
    Event event = method.getAnnotation(Event.class);
    if (event != null) {
        try {
            // id数组参数
            int[] values = event.value();
            int[] parentIds = event.parentId();
            int parentIdsLen = parentIds == null ? 0 : parentIds.length;
            //循环所有id,生成ViewInfo并添加代理反射
            for (int i = 0; i < values.length; i++) {
                int value = values[i];
                if (value > 0) {
                    ViewInfo info = new ViewInfo();
                    info.value = value;
                    info.parentId = parentIdsLen > i ? parentIds[i] : 0;
                    method.setAccessible(true);
                    EventListenerManager.addEventMethod(finder, info, event, handler, method);
                }
            }
        } catch (Throwable ex) {
            LogUtil.e(ex.getMessage(), ex);
        }
    }
  }
} // end inject event

EventListenerManager.addEventMethod(finder, info, event, handler, method);函数的实现为:

public static void addEventMethod(
            //根据页面或view holder生成的ViewFinder
            ViewFinder finder,
            //根据当前注解ID生成的ViewInfo
            ViewInfo info,
            //注解对象
            Event event,
            //页面或view holder对象
            Object handler,
            //当前注解方法
            Method method) {
        try {

            //根据id获取到的View,这里是通过finder来获取到,上面已经分析过了finder.findViewByInfo,无非就是持有Acvitity的引用然后调用findViewById找到对应的View
            View view = finder.findViewByInfo(info);

            if (view != null) {
                // 注解中定义的接口,比如Event注解默认的接口为View.OnClickListener
                Class<?> listenerType = event.type();
                // 默认为空,注解接口对应的Set方法,比如setOnClickListener方法
                String listenerSetter = event.setter();
                if (TextUtils.isEmpty(listenerSetter)) {
                    listenerSetter = "set" + listenerType.getSimpleName();
                }

                String methodName = event.method();

                boolean addNewMethod = false;
                /*
                 *根据View的ID和当前的接口类型获取已经缓存的接口实例对象,
                 *比如根据View.id和View.OnClickListener.class两个键获取这个View的OnClickListener对象
                 */
                Object listener = listenerCache.get(info, listenerType);
                DynamicHandler dynamicHandler = null;
                /*
                 * 如果接口实例对象不为空
                 * 获取接口对象对应的动态代理对象
                 * 如果动态代理对象的handler和当前handler相同
                 * 则为动态代理对象添加代理方法
                 */
                if (listener != null) {
                    dynamicHandler = (DynamicHandler) Proxy.getInvocationHandler(listener);
                    //判断当前是否已经添加过,如果为true的化,为之前已经添加过
                    addNewMethod = handler.equals(dynamicHandler.getHandler());
                    if (addNewMethod) {
                        dynamicHandler.addMethod(methodName, method);
                    }
                }

                // 如果还没有注册此代理
                if (!addNewMethod) {
                    dynamicHandler = new DynamicHandler(handler);
                    dynamicHandler.addMethod(methodName, method);
                    // 生成的代理对象实例,比如View.OnClickListener的实例对象
                    listener = Proxy.newProxyInstance(
                            listenerType.getClassLoader(),
                            new Class<?>[]{listenerType},
                            dynamicHandler);
                    //缓存起来        
                    listenerCache.put(info, listenerType, listener);
                }

                //相当于给view调用setOnClickListner(listener),
                Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
                setEventListenerMethod.invoke(view, listener);
            }
        } catch (Throwable ex) {
            LogUtil.e(ex.getMessage(), ex);
        }
}
我们看看DynamicHandler的实现为:
public static class DynamicHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ....
        try {
                return method.invoke(handler, args);
            } catch (Throwable ex) {
                throw new RuntimeException("invoke method error:" + handler.getClass().getName() + "#" + method.getName(), ex);
            }
        ...    
    }    
}

可以发现事件的绑定主要是通过注解id,找到对应的View,然后根据注解上面要设置的listener的三要素,找到对应的Method,然后利用反射的形式设置给这个view一个监听
只不过这个监听对象是通过动态代理获取到的,当真正触发操作的时候,再利用反射的方式回调到我们创建的方法上面

手写Xutils的Ioc

用来填充布局的注解
@Retention(RetentionPolicy.RUNTIME)
//标识注解用在类上面
@Target({ElementType.TYPE})
public @interface ContentView
{
    //注解的参数,默认值为-1
    int value() default -1;
}

用来变量的赋值操作的注解
@Retention(RetentionPolicy.RUNTIME)
//标识方法要作用在成员变量上面
@Target(ElementType.FIELD)
public @interface ViewJect
{
    int value() default -1;
}

用来标识一个事件的三要素 注解,注意这个注解是做用到另一个注解上面的
@Retention(RetentionPolicy.RUNTIME)
//标识这个注解要在另一个注解上面使用
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase
{
    /**
     * 事件的名称, 比如 setOnClickListener
     * @return
     */
    String eventStr();

    /**
     * 事件的类型 ,比如 new View.OnClickListener()
     * @return
     */
    Class<?> eventType();


    /**
     * 事件的回调方法 比如  onClick
     * @return
     */
    String  eventMethod();

}

@Retention(RetentionPolicy.RUNTIME)
//声明这个注解要在方法上面使用
@Target(ElementType.METHOD)
@EventBase(eventStr = "setOnClickListener",eventType = View.OnClickListener.class,eventMethod = "onClick")
public @interface onClick
{
    int[] value();//定义整形数组属性,用来存储多个的点击事件,这里可以传递多个view的Id进来
}

@Retention(RetentionPolicy.RUNTIME)
//声明这个注解要在方法上面使用
@Target(ElementType.METHOD)
@EventBase(eventStr = "setOnLongClickListener",eventType = View.OnLongClickListener.class,eventMethod = "onLongClick")
public @interface onLongClick
{
    int[] value();//定义整形数组属性,用来存储多个的点击事件,这里可以传递多个view的Id进来
}


基类的使用
public class BaseActivity extends AppCompatActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        //注入
        InjectUtils.inject(this);
    }
}

子类的使用
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity
{

    @ViewJect(R.id.but1)
    public Button mBut1;

    @ViewJect(R.id.but2)
    public Button mBut2;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        Toast.makeText(MainActivity.this,"---->"+ mBut1,Toast.LENGTH_LONG).show();
    }

    @onClick({R.id.but1,R.id.but2})
    public void MyClick(View view)
    {
        Toast.makeText(MainActivity.this,"---->"+ mBut1,Toast.LENGTH_LONG).show();
    }
}

/**
 * Project Name: MtXutilsIoc
 * File Name:    InjectUtils.java
 * ClassName:    InjectUtils
 *
 * Description: IOC容器 控制反转(Inversion of Control,英文缩写为IoC)把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的专用术语。
 * 它包括依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。
 *
 * @author Zhangyuhui
 * @date 2018年06月01日 9:19
 *
 * Copyright (c) 2018年, 4399 Network CO.ltd. All Rights Reserved.
 */
public class InjectUtils
{

    /**
     * 提供注入的方法
     * @param object 要注入的对象
     */
    public static void inject(Object object)
    {
        injectContentView(object);
        injectView(object);
        injectEvent(object);
    }

    /**
     * 注入事件
     * @param object 要注入的对象
     */
    private static void injectEvent(Object object)
    {
        Class<?> objectClass = object.getClass();
        //注意这里只是用来检查公有的方法,不会去检查私有的方法
        Method[] methods = objectClass.getMethods();
        for(Method method : methods)
        {
            //获取当当前方法上面的所有的注解,因为一个方法上面有可能有多个注解,这是有可能的
            Annotation[] annotations = method.getAnnotations();
            //遍历方法上面的每一个注解
            for (Annotation annotation:annotations)
            {
                //获取到当前注解的类型,注解也是一个类,获取到注解的类型,也就可以获取到当前class上面有么有这个注解,获取注解的类型要用annotionType(),
                //而不能用getClass
                Class<? extends Annotation> annotationClass = annotation.annotationType();
                //获取到当前的注解类型再去获取这个类型上面的EventBase这个注解
                EventBase eventBase = annotationClass.getAnnotation(EventBase.class);
                if(eventBase != null)
                {

                    //获取到annotationClass 注解的value数组,里面存储了当前要设置事件的对象id
                    try
                    {
                        //获取到value的method,这里为什么不直接强转为onClick注解,是因为方便扩展,因为当有一个事件是onLongClick的时候,所以不能写死
                        Method valueMethod = annotationClass.getMethod("value");
                        int[] viewId= (int[]) valueMethod.invoke(annotation);
                        for (int id:viewId)
                        {
                            //得到id对应的View 对象
                            Method findViewById=objectClass.getMethod("findViewById",int.class);
                            View view= (View) findViewById.invoke(object,id);
                            if(view==null)
                            {
                                continue;
                            }
                            //类似以 setOnClickListener
                            String eventStr = eventBase.eventStr();
                            //类似于  View.OnClickListener.class
                            Class<?> eventType = eventBase.eventType();
                            //类似于 onClick
                            String eventMethod = eventBase.eventMethod();
                            //获取到view上面的 setOnClickListener MethodId
                            Method enventMethod = view.getClass().getMethod(eventStr,eventType);

                            EventInvoketion eventInvoketion = new EventInvoketion(method,object);
                            Object newProxyInstance = Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class[]{eventType}, eventInvoketion);
                            //设置对应的setOnClickListener 监听
                            enventMethod.invoke(view,newProxyInstance);
                        }
                    }
                    catch (Exception e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    /**
     * 注入对象
     * @param object
     */
    private static void injectView(Object object)
    {
        Class<?> objectClass = object.getClass();
        //首先一定要是Activity的子类
        if(object instanceof Activity)
        {
            //注意这里获取的是public 的成员变量,如果是私有的这里不能实现
            Field[] declaredFields = objectClass.getFields();
            for (Field field : declaredFields)
            {
                //判断是否含有ViewJect注解
                ViewJect viewJect = field.getAnnotation(ViewJect.class);
                if(viewJect != null)
                {
                    int value = viewJect.value();
                    try
                    {
                        Method method = objectClass.getMethod("findViewById", int.class);
                        View view = (View) method.invoke(object, value);
                        //在通过反射的方式设置值到成员变量上
                        field.setAccessible(true);
                        field.set(object,view);
                    }
                    catch (Exception e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 注入ContentView实现
     * @param object
     */
    private static void injectContentView(Object object)
    {
        Class<?> objectClass = object.getClass();
        //首先一定要是Activity的子类
        if(object instanceof Activity)
        {
            ContentView contentView = objectClass.getAnnotation(ContentView.class);
            if(contentView != null)
            {
                //代表这个类有这个注解的存在,获取到注解上面的值
                int value = contentView.value();
                try
                {
                    //得到setContentView的MethodId
                    Method method = objectClass.getMethod("setContentView", int.class);
                    //使用反射的方式调用这个方法
                    method.invoke(object,value);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
}

/**
 * Project Name: MtXutilsIoc
 * File Name:    EventInvoketion.java
 * ClassName:    EventInvoketion
 *
 * Description: 动态代理反射回去
 *
 * @author Zhangyuhui
 * @date 2018年06月01日 10:26
 *
 * Copyright (c) 2018年, 4399 Network CO.ltd. All Rights Reserved.
 */
public class EventInvoketion implements InvocationHandler
{
    private Method methodId;
    private Object object;

    public EventInvoketion(Method methodId, Object object)
    {
        this.methodId = methodId;
        this.object = object;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable
    {
        return methodId.invoke(object,objects);
    }
}

运行结果:

结果显示

结果显示


文章作者: AheadSnail
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 AheadSnail !
评论
  目录