IOC框架源码分析之xUtils

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_38703938/article/details/80352587

什么是IOC,控制反转(Inversion of Control,英文缩写为IOC),其实就是反射加注解。

听起来抽象,拿个黄油刀butterknife举例,大概知道这个控制反转是什么意思了,就是注入控件和布局或者说是设置点击监听。这里先拿xUtils开刀。

xUtils的使用

1.添加依赖

compile 'org.xutils:xutils:3.5.0'

2.给定权限

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

3.绑定activity和注入控件、设置监听

package learn.yfg.com.dbdhtest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import org.xutils.view.annotation.Event;
import org.xutils.view.annotation.ViewInject;
import org.xutils.x;

public class XUtilsActivity extends AppCompatActivity {
    @ViewInject(R.id.icon)
    private ImageView mIconIv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_xutils);
        x.view().inject(this);

        mIconIv.setImageResource(R.drawable.aaa);
    }
    /**
     * 1. 方法必须私有限定,
     * 2. 方法参数形式必须和type对应的Listener接口一致.
     * 3. 注解参数value支持数组: value={id1, id2, id3}
     * 4. 其它参数说明见{@link org.xutils.view.annotation.Event}类的说明.
     **/
    @Event(value = R.id.icon,
            type = View.OnClickListener.class/*可选参数, 默认是View.OnClickListener.class*/)
    private void iconIvClick(View view) {
        Toast.makeText(this, "图片被点击了", Toast.LENGTH_LONG).show();
    }
}

效果图


2.源码分析

首先可以很清楚的知道,这种省去了findviewbyid,也省去了拿到对应view的实例再设置点击方法。

我们决定进入源码了解一下,这种注解是怎么完成的。

1.从上到下,逐步了解一下

@ViewInject(R.id.icon)

我们可以点击进去

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {

    int value();

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

看下他有注释的,父类视图的id,那么这个value是什么呢?我们继续点击

// inject view
        Field[] fields = handlerType.getDeclaredFields();
        if (fields != null && fields.length > 0) {
            for (Field field : fields) {

                Class<?> fieldType = field.getType();
                if (
                /* 不注入静态字段 */     Modifier.isStatic(field.getModifiers()) ||
                /* 不注入final字段 */    Modifier.isFinal(field.getModifiers()) ||
                /* 不注入基本类型字段 */  fieldType.isPrimitive() ||
                /* 不注入数组类型字段 */  fieldType.isArray()) {
                    continue;
                }

                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                if (viewInject != null) {
                    try {
                        View view = finder.findViewById(viewInject.value(), viewInject.parentId());
                        if (view != null) {
                            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

它都给我们注释好了,这段代码就是用来inject view的。继续从上到下,handlerType这个,继续点进去

    @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) {
                    Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);
                    setContentViewMethod.invoke(activity, viewId);
                }
            }
        } catch (Throwable ex) {
            LogUtil.e(ex.getMessage(), ex);
        }

        injectObject(activity, handlerType, new ViewFinder(activity));
    }

类似的我们还能看到很多方法,这个是注解activity的,也就是我们这个例子用的,我们就这个看下

首先得到activity这个类,然后得到这个类里的所有view。

Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);
                    setContentViewMethod.invoke(activity, viewId);

如果有注解获取layoutId的值,利用反射调用activity的setContentView方法注入视图。这个反射的了解,改天再来个博客学习一下,现在忘了改怎么解释了,虽然以前看过。

继续回到这个注解activity的方法,看到最后一句,点击发现,又回到了注解view那个那个方法里了,也发现下面还有注解事件的方法

 // 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

所以我们继续上面注解view的方法往下看,首先得到所有声明的成员,进行遍历,特殊的不注入,其他的转为viewInject,然后在下面进行了findviewbyid,由此可见,这个ioc框架是封装了findviewbyid方法的。

field.setAccessible(true);
field.set(handler, view);

这两句是利用反射来注入属性的。

再看下面注解事件的源码,里面基本和注解view类似,不过里面涉及了动态代理的知识,这里跳过。下次再博客学习一波。

最后,源码的解析就到这里了,有兴趣交流的可以加群哦 589780530



猜你喜欢

转载自blog.csdn.net/weixin_38703938/article/details/80352587