Android进阶——框架之IOC框架

什么是IOC

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

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

框架的思路

框架例子

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

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

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

框架的结构
20171020001616734.png

包含的注解介绍

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

框架的实现

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

一、自定义注解

@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路上还有权限的申请等功能可以实现,不过已经有第三方框架已经做好了

你可能感兴趣的:(Android进阶——框架之IOC框架)