打造一套IOC注解框架

相信大家对findViewById()都不陌生,有人对这个很是苦恼,有时候往往要在一个类中写特别多的findViewById。好在后面有了xUtils,butterknife等框架让我们轻松很多。大家有没有看过这类第三方开源框架的源码。

我们在这里就看一下xUtils的源码,看看是怎么实现的?

这里给一下xUtils3  github地址点击打开链接

先看一下使用方法

public class MainActivity extends AppCompatActivity {

    @ViewInject(R.id.icon)
    private ImageView mImageView ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        x.view().inject(this);
        mImageView .setImageResource(R.drawable.icon);
    }

    /**
     * 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();
    }
}

我这里就是设置一张图片和一个点击事件,其实主要解决的就是我们不再需要findViewById()和setOnClickListener(),我们来看一下源码到底是怎么实现的: 

这里只是部分源码 如果想看详细的源码请下载xUtils3  源码

 @Override
    public void inject(Activity activity) {
        //获取Activity的ContentView的注解
        Class<?> handlerType = activity.getClass();
        try {
            // 找到ContentView这个注解,在activity类上面获取
            ContentView contentView = findContentView(handlerType);
            if (contentView != null) {
                int viewId = contentView.value();
                if (viewId > 0) {
                   // 如果有注解获取layoutId的值,利用反射调用activity的setContentView方法注入视图
                    Method setContentViewMethod = 
                        handlerType.getMethod("setContentView", int.class);
                    setContentViewMethod.invoke(activity, viewId);
                }
            }
        } catch (Throwable ex) {
            LogUtil.e(ex.getMessage(), ex);
        }
        // 处理 findViewById和setOnclickListener的注解
        injectObject(activity, handlerType, new ViewFinder(activity));
    }
private static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {
     // .......

        // 从父类到子类递归
        injectObject(handler, handlerType.getSuperclass(), finder);

        // inject view  注入控件View
        Field[] fields = handlerType.getDeclaredFields();
        if (fields != null && fields.length > 0) {
            for (Field field : fields) {
              // ......
               // 获取viewInject 注解
                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                if (viewInject != null) {
                    try {
                       // 其实最终还是调用findViewById的方法
                        View view = finder.findViewById(viewInject.value(),
                             viewInject.parentId());
                        if (view != null) {
                            // 利用反射 把View注入到该属性中
                            field.setAccessible(true);
                            field.set(handler, view);
                        } else {
                           // ......
                        }
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                }
            }
        } // end inject view

        // inject event  注入事件
        Method[] methods = handlerType.getDeclaredMethods();
        if (methods != null && methods.length > 0) {
            for (Method method : methods) {
          // ......
                //检查当前方法是否是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 动态代理执行相应的方法
                                EventListenerManager.addEventMethod(
                                    finder, info, event, handler, method);
                            }
                        }
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                }
            }
        } // end inject event

    }

这里其实就是我们利用类的反射循环获取属性的注解值然后通过findViewById之后,动态的注入到控件属性里面;事件注入也是类似首先findViewById然后利用动态代理去反射执行方法。


接下来我们自己来实现一套IOC注解框架吧,采用的方式反射加注解和Xutils类似
/**
 * Created by Cwm on 2018/1/17.
 * <p>
 * View注解的Annotation
 *
 * @Target({ElementType.FIELD})代表注解的位置 FIELD为属性  TYPE为类上使用 CONSTRUCTOR构造函数使用
 * @Retention(RetentionPolicy.CLASS)什么时候生效 CLASS编译时  RUNTIME运行时  SOURCE源码资源
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ViewById {
    int value();
}

/**
 * Created by Cwm on 2018/1/17.
 * <p>
 * 监测网络Annotation
 *
 * @Target({ElementType.FIELD})代表注解的位置 FIELD为属性  TYPE为类上使用 CONSTRUCTOR构造函数使用
 * @Retention(RetentionPolicy.CLASS)什么时候生效 CLASS编译时  RUNTIME运行时  SOURCE源码资源
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface CheckNet {
    String value() default "亲 您的网络不太给力哦...";
}
IOC 注入 ViewUtils
public class ViewUtils {

    //activity注解
    public static void inject(Activity activity) {
        inject(new ViewFinder(activity), activity);
    }

    //fragment注解
    public static void inject(View view, Object object) {
        inject(new ViewFinder(view), object);
    }

    //自定义view注解
    public static void inject(View view) {
        inject(new ViewFinder(view), view);
    }

    //兼容上面三个方法  object-->反射需要执行的类
    private static void inject(ViewFinder finder, Object object) {
        injectFiled(finder, object);//注入属性
        injectEvent(finder, object);//注入事件

    }

    /**
     * 注入属性
     *
     * @param finder
     * @param object
     */
    private static void injectFiled(ViewFinder finder, Object object) {
        //1、获取类里面的所有属性
        Class<?> aClass = object.getClass();//获取class
        Field[] fields = aClass.getDeclaredFields();//获取所有的属性
        //2、获取ViewById里面的value值
        if (fields != null && fields.length > 0) {
            for (Field field : fields) {
                //获取该属性中ViewById的注解对象
                ViewById viewById = field.getAnnotation(ViewById.class);
                if (viewById != null) {
                    int value = viewById.value();//获取ViewId
                    //3、findById找到View
                    View view = finder.findViewById(value);
                    field.setAccessible(true);//不管什么修饰符都可以操作
                    if (view != null) {
                        try {
                            //4、动态注入View
                            field.set(object, view);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }


            }
        }
    }

    /**
     * 注入事件
     *
     * @param finder
     * @param object
     */
    private static void injectEvent(ViewFinder finder, Object object) {

        //1、获取类里面的所有方法
        Class<?> aClass = object.getClass();//获取class
        Method[] methods = aClass.getDeclaredMethods();//获取所有的方法
        if (methods != null && methods.length > 0) {

            for (Method method : methods) {
                Onclick onclick = method.getAnnotation(Onclick.class);//获取该注解对象
                CheckNet checkNet = method.getAnnotation(CheckNet.class);
                boolean isCheckNet = checkNet != null ? true : false;
                String isCheckNetvalue = null;
                if (isCheckNet) {
                    isCheckNetvalue = checkNet.value();//自定义吐司字符串/取默认的
                }
                if (onclick != null) {
                    int[] values = onclick.value();//获取onclick的value值
                    if (values != null && values.length > 0) {
                        for (int value : values) {
                            View view = finder.findViewById(value);//绑定控件
                            if (view != null) {
                                view.setOnClickListener(new DeclaredOnClickListener(object, method, isCheckNet, isCheckNetvalue));//view点击事件
                            }
                        }
                    }
                }
            }

        }
    }

    //继承点击事件
    public static class DeclaredOnClickListener implements View.OnClickListener {

        private Object mObject;//操作类
        private Method mMethod;//操作方法
        private boolean mIsCheckNet;
        private String isCheckString;

        public DeclaredOnClickListener(Object object, Method method, boolean checkNet, String CheckString) {
            mObject = object;
            mMethod = method;
            mIsCheckNet = checkNet;
            isCheckString = CheckString;
        }

        @Override
        public void onClick(View view) {

            //判断是否执行网络检测
            if (mIsCheckNet) {
                if (!isNetworkAvailable(view.getContext())) {
                    Toast.makeText(view.getContext(), isCheckString, Toast.LENGTH_SHORT).show();
                    return;
                }
            }

            //这样好处:即使点击事件里面逻辑操作有错误也不会崩溃,只会打印错误,去LogCat找错误
            mMethod.setAccessible(true);//可以越过所有修饰符
            try {
                mMethod.invoke(mObject, view);//方法反射执行
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    mMethod.invoke(mObject, null);//如果view为空调用该方法
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
    }


    //检测网络
    private static NetworkInfo getNetworkInfo(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
                Context.CONNECTIVITY_SERVICE);
        return cm.getActiveNetworkInfo();
    }

    /**
     * 检查是否有网络
     */
    public static boolean isNetworkAvailable(Context context) {
        NetworkInfo info = getNetworkInfo(context);
        return info != null && info.isAvailable();
    }

/**
 * Created by Cwm on 2018/1/17.
 * View  findViewById辅助类
 */

public class ViewFinder {

    private Activity mActivity;
    private View mView;

    public ViewFinder(Activity activity) {
        mActivity = activity;
    }

    public ViewFinder(View view) {
        mView = view;
    }

    /**
     * 最终执行绑定控件方法
     * @param viewId
     * @return
     */
    public View findViewById(int viewId) {

        return mActivity != null ? mActivity.findViewById(viewId) : mView.findViewById(viewId);
    }

使用方法:(这里没有上传检测网络的使用方法,可以自己使用下)

public class MainActivity extends AppCompatActivity {

    @ViewById(R.id.icon)
    private ImageView mIcon;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewUtils.inject(this);
        mIconIv.setImageResource(R.drawable.icon);
    }

    @OnClick(R.id.icon)
    private void onClick(View view) {
        Toast.makeText(this, "图片点击了"+i, Toast.LENGTH_LONG).show();
    }
}
以上就是自定义简易的IOC注解框架  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值