博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android进阶:框架打造之IOC框架
阅读量:6577 次
发布时间:2019-06-24

本文共 11439 字,大约阅读时间需要 38 分钟。

什么是IOC

IOC(Inversion of Control):控制反转。开发过程中类里面需要用到很多个成员变量

传统的写法:你要用这些成员变量的时候,那么你就new出来用 IOC的写法:你要用这些成员变量的时候,使用注解的方式自动注入进去 优点:代码量减少,加速开发 缺点:性能消耗加大,阅读性差,加速65535

框架的思路

框架例子

//实现Button自动findViewById的工作@ViewById(R.id.bt_ioc)private Button bt_ioc;复制代码

实现思路

  • 创建自定义注解 @ViewById
  • 通过某个字节码文件获取对应的自定义注解
  • 通过反射,获取注解和注解的值 (R.id.bt_ioc)
  • 通过对注解的值做相应的操作,并设置回对象自身

实现内容

  • 实现通过Id找到控件的功能
  • 实现通过Id找到Color、String资源
  • 实现绑定view的点击事件、长按事件
  • 实现绑定SetContentView
  • 实现绑定网络的检测功能

框架的结构

包含的注解介绍

  • OnClick:实现点击事件
  • OnLongClick:实现长按事件
  • ColorById:找到对应的Color值
  • ContentViewById:实现SetContentView
  • StringById:找到对应的String值
  • ViewById:实现findViewById
  • CheckNet:实现网络检查功能

框架的使用

下面的这个Activity实现了框架的所有内容

@ContentViewById(R.layout.activity_main)public class MainActivity extends AppCompatActivity {    @ViewById(R.id.bt_ioc)    private Button bt_ioc;    @StringById(R.string.app_name)    private String app_name;    @ColorById(R.color.colorAccent)    private int color;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        //IOC演示        InjectManager.inject(this);        bt_ioc.setText(app_name);        bt_ioc.setBackgroundColor(color);    }    //支持数组形式的绑定,绑定多个控件    @OnClick({R.id.open_ioc})    @OnLongClick({R.id.open_ioc})    @CheckNet()    public void open_ioc() {        Toast.makeText(this, "网络可用", Toast.LENGTH_SHORT).show();    }}复制代码

框架的实现

框架的实现分为两步:自定义注解的创建和通过反射进行注入

一、自定义注解

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface OnClick {    int[] value();}@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface OnLongClick {    int[] value();}@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface ColorById {    int value();}@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface ContentViewById {    int value();}@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface StringById {    int value();}@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface ViewById {    int value();}@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface CheckNet {}复制代码

Target注解的介绍

  • @Target(ElementType.XXX):代表的是注解放在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注解的介绍

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

二、注入步骤

从使用中可以看到,注入中最重要的步骤的是:InjectManager.inject(this),这里主要负责的事情有

  • 注入ContentView
  • 注入变量
  • 注入事件
public class InjectManager {    public static void inject(Activity activity) {        inject(new ViewManager(activity), activity);    }    public static void inject(Fragment fragment) {        inject(new ViewManager(fragment), fragment);    }    /**     * 注入     *     * @param viewManager     * @param object     */    private static void inject(ViewManager viewManager, Object object) {        InjectManagerService.injectContentView(viewManager, object);        InjectManagerService.injectField(viewManager, object);        InjectManagerService.injectEvent(viewManager, object);    }}复制代码

这里会使用到ViewManager辅助类,代码很简单,后面会用到

public class ViewManager {    private Activity mActivity;    private Fragment mFragment;    private View mView;    public ViewManager(Activity activity) {        this.mActivity = activity;    }    public ViewManager(View view) {        this.mView = view;    }    public ViewManager(Fragment fragment) {        this.mFragment = fragment;    }    /**     * 通过Id查询View     *     * @param resId     * @return     */    public View findViewById(int resId) {        View view = null;        if (mActivity != null) {            view = mActivity.findViewById(resId);        }        if (mFragment != null) {            view = mFragment.getActivity().findViewById(resId);        }        if (mView != null) {            view = mView.findViewById(resId);        }        return view;    }    /**     * 设置根布局,仅限Activity     *     * @param resId     */    public void setContentView(int resId) {        if (mActivity != null) {            mActivity.setContentView(resId);        }    }    /**     * 获取颜色     *     * @param resId     */    public int getColor(int resId) {        int color = -1;        if (mActivity != null) {            color = mActivity.getResources().getColor(resId);        }        if (mFragment != null) {            color = mFragment.getActivity().getResources().getColor(resId);        }        return color;    }    /**     * 获取字符串     *     * @param resId     */    public String getString(int resId) {        String str = "";        if (mActivity != null) {            str = mActivity.getString(resId);        }        if (mFragment != null) {            str = mFragment.getActivity().getString(resId);        }        return str;    }}复制代码

在InjectManagerService中,也是上面的三个主要步骤,主要还是下面通过反射实现其真正的效果

public class InjectManagerService {    /**     * 注入根布局     *     * @param viewManager     * @param object     */    public static void injectContentView(ViewManager viewManager, Object object) {        injectContentViewById(viewManager, object);    }    /**     * 注入变量     *     * @param viewManager     * @param object     */    public static void injectField(ViewManager viewManager, Object object) {        injectFieldById(viewManager, object);    }    /**     * 注入事件     *     * @param viewManager     * @param object     */    public static void injectEvent(ViewManager viewManager, Object object) {        injectOnClick(viewManager, object);        injectOnLongClick(viewManager, object);    }    /**     * 注入根布局     *     * @param viewManager     * @param object     */    private static void injectContentViewById(ViewManager viewManager, Object object) {        Class
clazz = object.getClass(); ContentViewById contentView = clazz.getAnnotation(ContentViewById.class); if (contentView != null) { int layoutId = contentView.value(); viewManager.setContentView(layoutId); } } /** * 注入findViewById事件 * * @param viewManager * @param object */ public static void injectFieldById(ViewManager viewManager, Object object) { //1. 获取Activity字节码,这里以Activity为例 Class
clazz = object.getClass(); //2. 获取字节码中所有的成员变量 Field[] fields = clazz.getDeclaredFields(); if (fields != null) { //3. 遍历所有变量 for (Field field : fields) { //4. 找到对应的注解 ViewById viewById = field.getAnnotation(ViewById.class); StringById stringById = field.getAnnotation(StringById.class); ColorById colorById = field.getAnnotation(ColorById.class); if (viewById != null) { //5. 获取注解中的值 int viewId = viewById.value(); //6. findViewById并设置访问权限 View view = viewManager.findViewById(viewId); field.setAccessible(true); try { //7. 动态注入到变量中 field.set(object, view); } catch (IllegalAccessException e) { e.printStackTrace(); } } if (stringById != null) { int viewId = stringById.value(); String string = viewManager.getString(viewId); field.setAccessible(true); try { field.set(object, string); } catch (IllegalAccessException e) { e.printStackTrace(); } } if (colorById != null) { int viewId = colorById.value(); int color = viewManager.getColor(viewId); field.setAccessible(true); try { field.set(object, color); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } /** * 注入点击事件 * * @param viewManager * @param object */ public static void injectOnClick(ViewManager viewManager, Object object) { Class
clazz = object.getClass(); Method[] methods = clazz.getDeclaredMethods(); if (methods != null) { for (Method method : methods) { OnClick onClick = method.getAnnotation(OnClick.class); if (onClick != null) { int[] viewIds = onClick.value(); for (int viewId : viewIds) { View view = viewManager.findViewById(viewId); //检查网络 boolean isCheckNet = method.getAnnotation(CheckNet.class) != null; if (view != null) { view.setOnClickListener(new DeclaredOnClickListener(method, object, isCheckNet)); } } } } } } /** * 注入长按事件 * * @param viewManager * @param object */ public static void injectOnLongClick(ViewManager viewManager, Object object) { Class
clazz = object.getClass(); Method[] methods = clazz.getDeclaredMethods(); if (methods != null) { for (Method method : methods) { OnLongClick onLongClick = method.getAnnotation(OnLongClick.class); if (onLongClick != null) { int[] viewIds = onLongClick.value(); for (int viewId : viewIds) { View view = viewManager.findViewById(viewId); //检查网络 boolean isCheckNet = method.getAnnotation(CheckNet.class) != null; if (view != null) { view.setOnLongClickListener(new DeclaredOnLongClickListener(method, object, isCheckNet)); } } } } } }}复制代码

这里用到两个点击事件,并且将检查网络作为参数传进去到事件中处理,由于长按事件和点击事件大同小异,这里只贴一处代码

public class DeclaredOnLongClickListener implements View.OnLongClickListener {    private Method mMethod;    private Object mObject;    private boolean mIsCheckNet;    public DeclaredOnLongClickListener(Method method, Object object, boolean isCheckNet) {        this.mMethod = method;        this.mObject = object;        this.mIsCheckNet = isCheckNet;    }    @Override    public boolean onLongClick(View v) {        if (mIsCheckNet) {            if (!NetUtils.isNetworkAvailable(v.getContext())) {                Toast.makeText(v.getContext(), "网络不可用", Toast.LENGTH_SHORT).show();                return true;            }        }        //执行点击事件        try {            mMethod.setAccessible(true);            mMethod.invoke(mObject, v);        } catch (Exception e) {            e.printStackTrace();            try {                mMethod.invoke(mObject, null);            } catch (Exception e1) {                e1.printStackTrace();            }        }        return true;    }}复制代码

结语

到这里IOC框架就结束了,其中比较重要的两点是注解的自定义和通过反射获取属性值并注入,其实代码挺简单的,反复看看还是挺容易理解的,大家可以结合源码进行阅读,其实在IOC路上还有权限的申请等功能可以实现,不过已经有第三方框架已经做好了.

转载于:https://juejin.im/post/5cd563656fb9a0323219996f

你可能感兴趣的文章
人民币中间价“四连涨”迫近6.6区间 创逾半年新高
查看>>
Java开发者该如何选择合适的NoSQL?
查看>>
广西龙胜一村寨旅游扶贫年终分红670万元
查看>>
统计局:居民睡觉休息平均时间为9小时19分钟
查看>>
月薪3万的开发利器
查看>>
2019陕州迎新春灯会吸引民众参观
查看>>
脱欧协议表决惨败 特雷莎·梅仅剩24小时保住首相位
查看>>
北京卫视推新节目《向往的星居》明星参与改造住房
查看>>
大数据技术,Spark核心技术之运行原理
查看>>
数据库分库分表中间件 Sharding-JDBC 源码分析 —— SQL 解析(三)之查询SQL解析...
查看>>
使用 ASDK 性能调优 - 提升 iOS 界面的渲染性能
查看>>
解锁区块链的创业密码
查看>>
3个月账期变1秒,蚂蚁区块链解题供应链金融困局
查看>>
使用 FCM 通知您的用户
查看>>
蹭了BCH热度,还来诋毁BCH,这些跳梁小丑到底在玩什么阴谋?
查看>>
模拟自然动画的精髓——TimeInterpolator与TypeEvaluator
查看>>
0911 - 全才才能生存的社会,不够成熟
查看>>
从零开始搭建React/redux/webpack脚手架
查看>>
【Dubbo实战】Dubbo+Zookeeper+Spring整合应用篇-Dubbo基于Zookeeper实现分布式服务(二)...
查看>>
使用 LeanCloud 服务做一站式 Chrome 插件开发 —— Favorite Image
查看>>