美文网首页
Android 自定义一个ioc框架,快速完成开发

Android 自定义一个ioc框架,快速完成开发

作者: 曾大稳丶 | 来源:发表于2017-05-04 11:14 被阅读0次

    ioc框架现在成熟的已经有很多了,xutils,butterknife等等都是ioc框架,利用这些框架我们可以快速的进行开发而不必重复写一些代码。但是在实际开发中我们往往有自己的需求,这时候我们就需要自定义一个ioc框架,根据需求我们可以在里面加入不同的注解简化我们的操作。下面我们自己仿照butterknife的功能来自定义一个findViewByIdonClick()的ioc框架。
    首先我们定义一个注解,使用@interface表示一个注解,代码如下:

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ViewById {
        int value();
    }
    

    其中value()是我们使用注解的时候的参数值:

        @ViewById(R.id.test_tv)
        private TextView mTestTv;
    

    就是上述代码中的R.id.test_tv
    @Target(ElementType.XXX)代表的是放在那个位置:
    @Target(ElementType.TYPE) 接口、类、枚举、注解
    @Target(ElementType.FIELD) 字段、枚举的常量
    @Target(ElementType.METHOD) 方法
    @Target(ElementType.PARAMETER)方法参数
    @Target(ElementType.CONSTRUCTOR) 构造函数
    @Target(ElementType.LOCAL_VARIABLE)局部变量
    @Target(ElementType.ANNOTATION_TYPE)注解
    @Target(ElementType.PACKAGE)

    @Retention(RetentionPolicy.XXX)代表什么时候检测:
    RetentionPolicy.RUNTIME代表运行时检测,class文件中存在
    RetentionPolicy.CLASS代表编译时检测,存在于class文件中,运行时无法获取
    RetentionPolicy.SOURCE代表在源文件中有效(在.java文件中有效)

    接下来我们需要通过一个类来获取这个注解,并且通过反射来拿到相应的View,最后在赋值给该Filed:
    代码如下:

      package ioc.zzw.com.ioclibrary;
    
    import android.app.Activity;
    import android.view.View;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * Created by zzw on 2017/5/4.
     * ioc注入工具类
     */
    
    public class ViewUtils {
    
        public static void inject(Activity activity) {
            inject(new ViewHelper(activity), activity);
        }
    
        //为了兼容View
        public static void inject(View v) {
            inject(new ViewHelper(v), v);
        }
    
        //为了兼容Fragment
        public static void inject(View v, Object o) {
            inject(new ViewHelper(v), o);
        }
    
    
        /**
         * 最终都调用这个方法
         *
         * @param helper View的帮助类  通过这个类根据id找到相应View
         * @param o      相关对象  Activity  view 等  从那个类传进来的
         */
        private static void inject(ViewHelper helper, Object o) {
            injectField(helper, o);
            injectMethod(helper, o);
        }
    
        /**
         * 通过@ViewById得到id注入相应的View
         *
         * @param helper View帮助类
         * @param o      相关对象  Activity  view 等  从那个类传进来的
         */
        private static void injectField(ViewHelper helper, Object o) {
            //1.获取到Object中所有的带有@ViewById的字段
            Class<?> clazz = o.getClass();
            //获取所有的字段
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                //2.获取到相应的value值,也就是id值得到相应的View
                ViewById viewById = field.getAnnotation(ViewById.class);
                if (viewById != null) {
                    int viewId = viewById.value();
                    View view = helper.findViewById(viewId);
                    if (view != null) {
                        try {
                            //3.设置字段值,也就是给字段赋值
                            field.setAccessible(true);//为了使不被修饰符梭影响
                            field.set(o, view);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    
        /**
         * 设置点击时间
         *
         * @param helper View帮助类
         * @param o      相关对象  Activity  view 等  从那个类传进来的
         */
        private static void injectMethod(ViewHelper helper, Object o) {
            //1.获取到所有带有@OnClick的方法
            Class<?> clazz = o.getClass();
            Method[] methods = clazz.getDeclaredMethods();//获取所有方法
            for (Method method : methods) {
                OnClick onClick = method.getAnnotation(OnClick.class);
                if (onClick != null) {
                    //2.获取到相应的value值,也就是要设置点击时间的id数组
                    int[] values = onClick.value();
                    for (int viewId : values) {
                        //3.通过id获取到相应的Vie,然后设置点击事件
                        View view = helper.findViewById(viewId);
                        if (view != null) {
                            view.setOnClickListener(new DeclaredOnClickListener(method, o));
                        }
                    }
                }
            }
        }
    
        private static class DeclaredOnClickListener implements View.OnClickListener {
    
            //设置点击事件的方法
            private Method mMethod;
            //在那个类中
            private Object mObject;
    
    
            public DeclaredOnClickListener(Method method, Object o) {
                this.mMethod = method;
                this.mObject = o;
            }
    
            @Override
            public void onClick(View v) {
                try {
                    mMethod.setAccessible(true);//所有修饰符都可以搞事
                    mMethod.invoke(mObject, v);//可以避免点击闪退
                } catch (Exception e) {
                    e.printStackTrace();
                    try {
                        mMethod.invoke(mObject, (Object[]) null);//当方法体里面没有参数时候调用改方法,执行没有方法体的修饰的函数
                    } catch (IllegalAccessException e1) {
                        e1.printStackTrace();
                    } catch (InvocationTargetException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        }
    
    
    }
    
    
    package ioc.zzw.com.ioclibrary;
    
    import android.app.Activity;
    import android.support.annotation.IdRes;
    import android.view.View;
    
    /**
     * Created by zzw on 2017/5/4.
     * View帮助类
     */
    
    public class ViewHelper {
    
        private Activity mActivity;
        private View mView;
    
        public ViewHelper(Activity activity) {
            this.mActivity = activity;
        }
    
        public ViewHelper(View v) {
            this.mView = v;
        }
    
        public View findViewById(@IdRes int id) {
    
            return mActivity == null ? mView.findViewById(id) : mActivity.findViewById(id);
        }
    }
    
    
    package ioc.zzw.com.ioclibrary;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
    * Created by zzw on 2017/5/4.
    *
    */
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface ViewById {
       int value();
    }
    
    
      package ioc.zzw.com.ioclibrary;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * Created by zzw on 2017/5/4.
     */
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface OnClick {
        int []value();
    }
    
    
    package com.zzw.ioc;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import ioc.zzw.com.ioclibrary.OnClick;
    import ioc.zzw.com.ioclibrary.ViewById;
    import ioc.zzw.com.ioclibrary.ViewUtils;
    
    public class MainActivity extends AppCompatActivity {
    
        @ViewById(R.id.test_tv)
        private TextView mTestTv;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ViewUtils.inject(this);
            mTestTv.setText("ioc_");
        }
    
    
        @OnClick({R.id.test_tv, R.id.test_button})
        private void onClick(View view) {
            switch (view.getId()) {
                case R.id.test_tv:
                    Toast.makeText(this, "点击了TextView", Toast.LENGTH_SHORT).show();
                    break;
    
                case R.id.test_button:
                    Toast.makeText(this, "点击了Button", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    }
    
    

    我们可以根据需求做一些自定义需要的注解,这里用网络检测来做个例子,当点击了检测一下网络,如果没有网络的话就显示网络的提示,有的话就继续往下执行:
    我们加上CheckNet

    package ioc.zzw.com.ioclibrary;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * Created by zzw on 2017/5/4.
     * 点击之后是否要执行网络检测
     */
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface CheckNet {
        String value() default "亲,您的网络链接有问题哦!";
    }
    
    

    ViewUtils类中点击事件注入的时候加上判断:

    /**
         * 设置点击事件
         *
         * @param helper View帮助类
         * @param o      相关对象  Activity  view 等  从那个类传进来的
         */
        private static void injectMethod(ViewHelper helper, Object o) {
            //1.获取到所有带有@OnClick的方法
            Class<?> clazz = o.getClass();
            Method[] methods = clazz.getDeclaredMethods();//获取所有方法
            for (Method method : methods) {
                OnClick onClick = method.getAnnotation(OnClick.class);
                if (onClick != null) {
                    //网络检测
                    CheckNet checkNet = method.getAnnotation(CheckNet.class);
                    String hint = null;
                    if (checkNet != null) {
                        hint = checkNet.value();
                    }
    
                    //2.获取到相应的value值,也就是要设置点击时间的id数组
                    int[] values = onClick.value();
                    for (int viewId : values) {
                        //3.通过id获取到相应的Vie,然后设置点击事件
                        View view = helper.findViewById(viewId);
                        if (view != null) {
                            view.setOnClickListener(new DeclaredOnClickListener(method, o, hint));
                        }
                    }
                }
            }
        }
    
        private static class DeclaredOnClickListener implements View.OnClickListener {
    
            //设置点击事件的方法
            private Method mMethod;
            //在那个类中
            private Object mObject;
            //是否检查网络
            private String mNoNetHint;
    
            public DeclaredOnClickListener(Method method, Object o, String hint) {
                this.mMethod = method;
                this.mObject = o;
                this.mNoNetHint = hint;
            }
    
            @Override
            public void onClick(View v) {
                try {
                    mMethod.setAccessible(true);//所有修饰符都可以搞事
                    if (mNoNetHint != null && !isNetConnected(v.getContext())) {
                        Toast.makeText(v.getContext(), mNoNetHint, Toast.LENGTH_SHORT).show();
                        return;
                    }
                    mMethod.invoke(mObject, v);//可以避免点击闪退
                } catch (Exception e) {
                    e.printStackTrace();
                    try {
                        mMethod.invoke(mObject, (Object[]) null);//当方法体里面没有参数时候调用改方法,执行没有方法体的修饰的函数
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                }
            }
        }
    
    
        /**
         * 检测网络是否连接
         *
         * @return
         */
        private static boolean isNetConnected(Context context) {
            ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            if (cm != null) {
                NetworkInfo[] infos = cm.getAllNetworkInfo();
                if (infos != null) {
                    for (NetworkInfo ni : infos) {
                        if (ni.isConnected()) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
    

    这样,我们在需要判断的时候加上这个注解:

        @OnClick({R.id.test_tv, R.id.test_button})
        @CheckNet("您网络有问题哦!")
        private void onClick(View view) {
            switch (view.getId()) {
                case R.id.test_tv:
                    Toast.makeText(this, "点击了TextView", Toast.LENGTH_SHORT).show();
                    break;
    
                case R.id.test_button:
                    Toast.makeText(this, "点击了Button", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    

    这样的话就能够提示你需要的文字

    如果你直接这样:

        @OnClick({R.id.test_tv, R.id.test_button})
        @CheckNet
        private void onClick(View view) {
            switch (view.getId()) {
                case R.id.test_tv:
                    Toast.makeText(this, "点击了TextView", Toast.LENGTH_SHORT).show();
                    break;
    
                case R.id.test_button:
                    Toast.makeText(this, "点击了Button", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    

    这样的话就会提示默认的文字。

    最后加上一个小技巧:
    在我们利用反射来赋值或者执行方法的时候一般会try catch,这个时候我们把异常改为Exception,这样的话就能避免闪退,这样用户体验增加了,但是会有一些小问题,比如点击的时候出了异常,这时候点击就没反应,所以我们要细心查看logcat打印信息找到问题所在,从而解决问题。就是想下面这几段代码一样:

                    try {
                            //3.设置字段值,也就是给字段赋值
                            field.setAccessible(true);//为了使不被修饰符梭影响
                            field.set(o, view);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
    
             try {
                    mMethod.setAccessible(true);//所有修饰符都可以搞事
                    if (mNoNetHint != null && !isNetConnected(v.getContext())) {
                        Toast.makeText(v.getContext(), mNoNetHint, Toast.LENGTH_SHORT).show();
                        return;
                    }
                    mMethod.invoke(mObject, v);//可以避免点击闪退
                } catch (Exception e) {
                    e.printStackTrace();
                    try {
                        mMethod.invoke(mObject, (Object[]) null);//当方法体里面没有参数时候调用改方法,执行没有方法体的修饰的函数
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                }
    
    
    

    demo点此下载
    学习来源:红橙Darren

    相关文章

      网友评论

          本文标题:Android 自定义一个ioc框架,快速完成开发

          本文链接:https://www.haomeiwen.com/subject/kvlrtxtx.html