Android注解框架与自己实现类似ButterKnife效果解析

本文介绍了一个简单的AndroidIOC框架,该框架利用注解实现视图的自动绑定和事件的自动绑定。通过@OnClick和ViewById注解,可以自动将点击事件和视图绑定,同时支持@CheckNet注解进行网络状态检测。框架通过反射获取对象的属性和方法,动态注入视图和事件监听器,降低了代码耦合,提高了可维护性。
摘要由CSDN通过智能技术生成

        IOC是Inversion of Control(控制反转)的缩写,是一种设计模式,其核心思想是将对象的控制权转移给容器或框架,而不是在代码中直接实例化和管理对象。在这种模式中,对象的创建和组装由容器完成,而应用程序只需要定义对象需要的属性和依赖关系,容器会自动完成对象的创建和依赖注入。

        在本文中,我们将介绍一个简单的IOC框架,它基于注解,可以实现视图的自动绑定和事件的自动绑定。该框架的核心代码如下:

一、@onclick注入

public class ViewUtils {

    public static final int NETWORK_NULL = 0;
    public static final int NETWORK_WIFI = 1;
    public static final int NETWORK_MOBILE_DATA = 2;
    public static final int NETWORK_UNKOWN = 3;

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

    /**
     * 注入View
     */
    public static void inject(View view) {
        inject(new ViewFinder(view), view);
    }

    /**
     * 注入任意对象
     */
    public static void inject(View view, Object object) {
        inject(new ViewFinder(view), object);
    }

    /**
     * 兼容上面三个方法
     *
     * @param finder
     * @param object 反射执行的类
     */
    private static void inject(ViewFinder finder, Object object) {
        injectFiled(finder, object);
        injectEvent(finder, object);
    }

    /**
     * 事件的注入
     *
     * @param finder
     * @param object
     */
    private static void injectEvent(ViewFinder finder, Object object) {
        //1.获取类里面所有方法
        Class<?> clazz = object.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        //2.获取OnClick的value值
        for (Method method : methods) {
            OnClick onClick = method.getAnnotation(OnClick.class);
            if (onClick != null) {
                int[] viewIds = onClick.value();
                for (int viewId : viewIds) {
                    //3.findViewById找到View
                    View view = finder.findViewById(viewId);
                    //拓展功能,比如检查网络
                    boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;
                    if (view != null) {
                        //4.view.setOnClickListener
                        view.setOnClickListener(new DeclaredOnClickListener(method, object, isCheckNet));
                    }
                }
            }
        }
    }
}

        以上这段代码是一个事件注入的实现,主要用于将被注解的方法绑定到指定的 View 上,实现点击事件的响应。具体的实现过程如下:

  1. 获取传入对象的 Class 对象和其声明的所有方法。

  2. 遍历方法数组,获取被 @OnClick 注解的方法,并获取其注解中的 value 值,即指定的 View ID 数组。

  3. 遍历 View ID 数组,通过 ViewFinder 找到对应的 View。

  4. 将 View 和被注解的方法绑定,生成一个 OnClickListener 并设置给 View。

        其中,为了实现拓展功能,还对注解中的 CheckNet 进行了判断,如果该注解存在,则生成的 OnClickListener 中会对网络进行检查。

二、findViewById注入

/**
     * findViewById注入
     *
     * @param finder
     * @param object
     */
    private static void injectFiled(ViewFinder finder, Object object) {
        //1.获取类里面所有属性
        Class<?> clazz = object.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        //2.获取ViewById里面的value值
        for (Field declaredField : declaredFields) {
            ViewById viewById = declaredField.getAnnotation(ViewById.class);
            if (viewById != null) {
                //获取注解里面的id值
                int viewId = viewById.value();
                //3.findViewById找到View
                View view = finder.findViewById(viewId);
                if (view != null) {
                    //能够注入所有修饰符
                    declaredField.setAccessible(true);
                    //4.动态的注入找到View
                    try {
                        //设置属性,将declaredField的属性值设置为通过findViewById找到的view。
                        declaredField.set(object, view);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

        以上这段代码实现的是通过注解方式实现findViewById的功能,将xml中定义的控件和Java代码中定义的属性绑定在一起,以便在Java代码中操作这些控件。其实现步骤如下:

  1. 通过反射获取目标类中所有的属性(Field);
  2. 遍历每个属性,如果该属性使用了ViewById注解,则获取注解中的id值;
  3. 调用ViewFinder中的findViewById方法根据id值找到View;
  4. 将查找到的View设置给该属性。

        需要注意的是,由于该属性有可能被定义为private等访问修饰符,因此在将查找到的View设置给属性时,需要先调用setAccessible方法设置访问权限。

三、@CheckNet注入

private static class DeclaredOnClickListener implements View.OnClickListener {

        private Object mObject;
        private Method mMethod;
        private boolean mIsCheckNet;

        public DeclaredOnClickListener(Method method, Object object, boolean isCheckNet) {
            this.mMethod = method;
            this.mObject = object;
            this.mIsCheckNet = isCheckNet;
        }

        @Override
        public void onClick(View v) {
            //是否需要检测网络
            if (mIsCheckNet) {
                int network_state = networkAvailable(v.getContext());
                if (network_state == NETWORK_WIFI) {
                    Toast.makeText(v.getContext(), "当前已连接WIFI", Toast.LENGTH_SHORT).show();
                    return;
                } else if (network_state == NETWORK_MOBILE_DATA) {
                    //打印Toast
                    Toast.makeText(v.getContext(), "请注意流量消耗", Toast.LENGTH_SHORT).show();
                    return;
                } else if(network_state == NETWORK_NULL){
                    //打印Toast
                    Toast.makeText(v.getContext(), "网络不太给力", Toast.LENGTH_SHORT).show();
                    return;
                }
            }
            try {
                mMethod.setAccessible(true);
                //5.反射执行方法
                mMethod.invoke(mObject, v);
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    mMethod.invoke(mObject);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

        以上这段代码定义了一个私有静态内部类 DeclaredOnClickListener 实现了 View.OnClickListener 接口,并在 injectEvent 方法中使用。这个类的主要作用是在响应点击事件时调用对应的方法,同时支持网络检测的功能。

        这个类的构造方法接收三个参数:方法、对象、是否需要检测网络。当 View 被点击时,onClick 方法会被调用,首先会根据 mIsCheckNet 参数判断是否需要进行网络检测。如果需要,会调用 networkAvailable 方法检测网络状态,并根据结果打印相应的 Toast 提示。如果不需要网络检测或者网络状态良好,就会通过反射调用传入的方法 mMethod,执行响应的点击事件逻辑。

        这个类的实现相对较为简单,但使用起来非常灵活,可以通过注解的方式自由添加和删除方法的点击事件,并在必要时添加网络检测的功能。

四、网络状态判断方法

 private static int networkAvailable(Context context) {
        try {
            //得到连接管理器对象
            ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                Network network = connectivityManager.getActiveNetwork();
                NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
                if (capabilities != null) {
                    if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
                        // WiFi is active
                        return NETWORK_WIFI;
                    } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
                        // Mobile data is active
                        return NETWORK_MOBILE_DATA;
                    }
                } else {
                    // No active network
                    return NETWORK_NULL;
                }
            } else {
                NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
                if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
                    int type = activeNetworkInfo.getType();
                    if (type == ConnectivityManager.TYPE_WIFI) {
                        // 当前网络是 WiFi
                        return NETWORK_WIFI;
                    } else if (type == ConnectivityManager.TYPE_MOBILE) {
                        // 当前网络是移动数据网络
                        return NETWORK_MOBILE_DATA;
                    }
                } else {
                    // 没有活动的网络连接
                    return NETWORK_NULL;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return NETWORK_UNKOWN;
    }

        这段代码实现了一个判断当前网络状态的方法,返回值为整型常量,表示当前网络状态,常量值如下:

  • NETWORK_WIFI:当前网络连接为 WiFi。
  • NETWORK_MOBILE_DATA:当前网络连接为移动数据网络。
  • NETWORK_NULL:当前没有可用的网络连接。
  • NETWORK_UNKOWN:当前网络状态未知。

        在方法中首先获取连接管理器对象 ConnectivityManager,然后判断 Android 版本是否大于等于 23(Build.VERSION_CODES.M),如果是则使用新 API 获取网络连接状态,否则使用旧的方法。如果当前有活动的网络连接,则判断当前网络类型为 WiFi 还是移动数据网络,最后根据判断结果返回对应的常量值。如果没有可用的网络连接,则返回 NETWORK_NULL。如果出现异常,则返回 NETWORK_UNKOWN。

五、使用方法

        以上就是一个简单的IOC框架的实现代码,下面我们来详细介绍一下它的实现原理以及使用方法。

        IOC(Inversion of Control)控制反转,是一种设计模式,它是指在应用程序开发中,将对象的创建、依赖关系的管理交给框架或容器来实现,而不是由应用程序自己来完成。IOC的优点是降低了代码之间的耦合度,提高了代码的可维护性和可扩展性。

        ViewUtils类中的inject方法就是一个典型的IOC实现,它的作用是自动注入控件和事件。在使用该方法之前,我们需要在控件上添加ViewById和OnClick注解,注解中包含了控件的ID和点击事件的方法名。ViewUtils在运行时利用反射机制获取到这些注解,然后自动注入控件和事件。

        ViewUtils的注入方法非常灵活,它可以注入Activity、View以及任意Java对象中的控件和事件。在注入控件时,ViewUtils会通过反射机制获取到该对象的所有属性,并获取到对应的控件ID,然后使用findViewById方法找到对应的控件,并通过反射机制动态的将控件赋值给对应的属性。

        在注入事件时,ViewUtils会通过反射机制获取到该对象的所有方法,并获取到被OnClick注解修饰的方法名,然后通过反射机制动态的为对应的控件设置OnClickListener事件。

        ViewUtils还支持扩展功能,比如在事件执行前检测网络状态。在ViewUtils中,我们可以为需要检测网络状态的方法添加CheckNet注解,当点击对应的控件时,ViewUtils会首先检测网络状态,然后再执行对应的方法。通过这种方式,我们可以非常方便地为应用程序添加网络状态检测功能。

        使用ViewUtils非常简单,我们只需要在需要注入控件或事件的对象中调用对应的inject方法即可。例如,在Activity的onCreate方法中调用ViewUtils.inject(this)方法,即可实现自动注入控件和事件。使用代码如下:

//findViewById注入
@ViewById(R.id.img_collect)
private ImageView imgCollect;


//点击事件注入
@OnClick(R.id.img_card)
//检查网络状态
@CheckNet
private void go2Card() {
   Intent intent = new Intent(this, CardActivity.class);
   intent.putExtra("num", num);
   startActivityForResult(intent, MyTag.CARD);
}

 完整代码见:ioc: Android实现注解框架

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值