控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
简单点说就是:IOC是原来由程序代码中主动获取的资源,转变由第三方获取并使原来的代码被动接收的方式,以达到解耦的效果,称为控制反转。
实例:以模拟 ButterKnife 为例。注入布局文件和 view 的 findViewById 和 onClick事件。
// 注入框架的初始化放到了 BaseActivity中
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.inject(this);
}
}
// 在MainActivity中 注入布局
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
// 注入控件`在这里插入代码片`
@InjectView(R.id.btn1)
private Button btn1;
@InjectView(R.id.btn2)
private Button btn2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, btn1.toString());
Log.e(TAG, btn2.toString());
}
// 注入监听
@OnClick({R.id.btn1, R.id.btn2})
public void click(View view) {
Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
}
}
// 用来注入布局文件 需要传入一个 layoutId 运行时注解 作用在class
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
// 用来注入view 传入 viewId 运行时注解作用在属性上
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
int value();
}
// 作用在注解上的注解 用来扩展注解 下面用来判断是否是事件注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface BaseEvent {
// 传入 类似与 setOnClickListener / setonLongClick 等的监听事件
String setListenerType();
// new Onclick / onLongClick 传入需要监听的参数类
Class<?> newListenerType();
}
// 作用在方法上的运行时注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
// 这里是添加 setOnClickListener 如果是长按则添加 setOnLongClickListener
@BaseEvent(setListenerType = "setOnClickListener"
, newListenerType = View.OnClickListener.class)
public @interface OnClick {
// 传入 viewid 数组 使用时类似与 ButterKnife 的 @Click({R.id.btn,R.id.text})
int[] value();
}
/**
* ioc 注入工具类
*/
class InjectUtils {
static void inject(Object context) {
injectLayout(context);
injectView(context);
injectEvent(context);
}
/**
* 注入布layout
*
* @param context context
*/
private static void injectLayout(Object context) {
// 获取 calss
Class clazz = context.getClass();
// 拿到注解
ContentView contentView = (ContentView) clazz.getAnnotation(ContentView.class);
if (contentView != null) {
try {
// 拿到 布局 id
int layoutId = contentView.value();
// 通过反射调用 activity 的 setContentView 方法,传入id
Method method = clazz.getMethod("setContentView", int.class);
method.invoke(context, layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 注入view
*
* @param context context
*/
private static void injectView(Object context) {
// 拿到 activity class
Class clazz = context.getClass();
// 获取所有的变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 遍历看是否添加了注解
InjectView annotation = field.getAnnotation(InjectView.class);
if (annotation != null) {
try {
// 设置可以获取 private 修饰的属性
field.setAccessible(true);
// 获取viewId
int viewId = annotation.value();
// 通过反射执行 activity 的 findViewById
Method method = clazz.getMethod("findViewById", int.class);
View view = (View) method.invoke(context, viewId);
// 将 findViewById 执行结束获取到的 view 给属性赋值
field.set(context, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* 注册监听事件
*/
private static void injectEvent(Object context) {
Class clazz = context.getClass();
// 获取到所有的方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
// 得到方法上所有的注解 以免方法上有多个注解
Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
for (Annotation annotation : declaredAnnotations) {
Class<?> aClass = annotation.annotationType();
// 获取之前定义的 baseEvent 根据它来判断添加的是哪种类型的监听
BaseEvent baseEvent = aClass.getAnnotation(BaseEvent.class);
if (baseEvent == null) {
continue;
}
// 设置的监听类型 比如 onClick 或者 onLongClick
String listenerType = baseEvent.setListenerType();
// 设置监听执行的方法 new View.OnclickListener
Class<?> newListenerType = baseEvent.newListenerType();
try {
// 通过反射获取view
Method valueMethod = aClass.getDeclaredMethod("value");
int[] viewIds = (int[]) valueMethod.invoke(annotation);
for (int viewId : viewIds) {
Method findViewById = clazz.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(context, viewId);
if (view == null) {
continue;
}
// 通过动态代理 执行 setOnClickListener 或者 setOnLongClickListener
// 创建 InvocationHandler
ListenerInvocationHandler invocationHandler = new ListenerInvocationHandler(context, method);
// 创建代理类
Object proxy = Proxy.newProxyInstance(newListenerType.getClassLoader(), new Class[]{newListenerType}, invocationHandler);
Method eventMethod = view.getClass().getMethod(listenerType, newListenerType);
eventMethod.invoke(view, proxy);
}
} catch (Exception e) {
Log.e("exception", e.toString());
}
}
}
}
}
public class ListenerInvocationHandler implements InvocationHandler {
// 传入要代理执行的类 这里是实际上是 activity dialog等
private Object object;
// 要执行的方法
private Method method;
public ListenerInvocationHandler(Object object, Method method) {
this.object = object;
this.method = method;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return this.method.invoke(object, args);
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
// 同样需要 baseEvent 并标记事件类型
@BaseEvent(setListenerType = "setOnLongClickListener"
, newListenerType = View.OnLongClickListener.class)
public @interface OnLongClick {
int[] value() default -1;
}
// 由于事件分发机制 onLongClick 是需要返回值 这里没有做处理 只是返回了 true
@OnLongClick({R.id.btn1, R.id.btn2})
public boolean onLongClick(View view){
Toast.makeText(this, "longClick", Toast.LENGTH_SHORT).show();
return true;
}
好了 IOC 基本思想已经完毕。IOC 细想被应用到很多的框架中 如我们熟悉的ButterKnife ,java的 SpringMVC等等。这么做可以降低耦合度。减少以后的维护成本。但是创建对象的过程略显复杂,效率也比正常写法稍微低一些。