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

2017-05-04  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读