IOC注入框架设计
什么是IOC注入框架
IOC-控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心。 控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。这段百度对IOC框架的解释,对于Java开发者来讲最著名的IOC框架莫过于Spring,而在我们的Android开发中,IOC的使用更为常见,比如大家经常使用的XUtil、butterKnife、EventBus、dagger、dagger2、otto等等,这些第三方库几乎都使用了IOC思想,比如使用ButterKnife对于view的注入减少了大量篇幅的findViewById操作,而注解注入的方式也显得更加优雅。
IOC思想
IOC是原来由程序代码中主动获取的资源,转变由第三方获取并使原来的代码被动接收的方式,以达到解耦的效果,称为控制反转
1577951887382.png如何实现IOC
看看示例代码
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@ViewInject(R.id.app_text)
private Button textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// textView.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// Toast.makeText(MainActivity.this,"点击了",Toast.LENGTH_SHORT).show();
// }
// });
}
@OnClick({R.id.app_text,R.id.app_text1})
public boolean click(View view){
Toast.makeText(this,"---->"+textView,Toast.LENGTH_SHORT).show();
return false;
}
@OnLongClick({R.id.app_text,R.id.app_text1})
public boolean longClick(View view){
Toast.makeText(this, "长按了", Toast.LENGTH_SHORT).show();
return true;
}
}
package com.highgreat.sven.ioc;
import android.app.Activity;
import android.os.Bundle;
public class BaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.inject(this);
}
}
public class InjectUtils {
public static void inject(Object object){
injectLayout(object);
injectView(object);
injectClick(object);
}
...
}
布局注入
@ContentView(R.layout.activity_main)
需要创建ContentView注解
package com.highgreat.sven.ioc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)//注解在运行时执行
@Target(ElementType.TYPE)//作用在类上面
public @interface ContentView {
int value();
}
Java实现注解的执行逻辑
/**
* 布局注入
* @param object
*/
private static void injectLayout(Object object) {
int layoutId = 0;
Class<?> aClass = object.getClass();
ContentView annotation = aClass.getAnnotation(ContentView.class);
if(annotation != null){
layoutId = annotation.value();
try {
//反射Activity的setContentView方法
Method method = aClass.getMethod("setContentView", int.class);
method.invoke(object,layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
这段代码首先通过object.getClass()拿到这个Class对象,再通过aClass.getAnnotation(ContentView.class);拿到类上的注解,取得layout的id,然后反射Activity的setcontentView(R.id.layout)方法,这样就实现了布局的注入
控件注入
定义注解文件
package com.highgreat.sven.ioc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)//字段
public @interface ViewInject {
int value();
}
java执行逻辑
/**
* 控件注入
* @param object
*/
private static void injectView(Object object) {
Class<?> aClass = object.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field : declaredFields) {
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if(viewInject != null){
int value = viewInject.value();
try {
Method method = aClass.getMethod("findViewById", int.class);
View view = (View) method.invoke(object,value);
field.setAccessible(true);//设置为可访问的权限。即将private修饰的字段变成可公共访问的
field.set(object,view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这段代码首先通过aClass.getDeclaredFields()拿到类的所有字段属性,再通过ViewInject viewInject = field.getAnnotation(ViewInject.class);拿到字段属性的注解判断是否是需要注入的字段,再通过Method method = aClass.getMethod("findViewById", int.class);View view = (View) method.invoke(object,value);反射Activity类的findViewById方法获取到对应的view,setContentView方法没有返回值,而findViewById则相反,所以我们需要为属性(这里就是一些View)赋值,调用的是field.set(object,view)。
Android事件监听规律
事件的注入相比之前的布局和控件注入,难度和复杂度大大提高了。通过对Android中的事件监听代码的观察,我们得出如下三部曲:
- setListener
- new Listener
- doCallback
就像View的点击事件和长按时间监听那样,首先setListener:View.setOnClickListener(),然后new 一个Listener传入,View.setOnClickListener(new OnClickListener(View v){}),最后执行回调方法:
onClick(View v){...}
定义事件监听规律的注解
package com.highgreat.sven.ioc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)//作用在其他注解上
public @interface EventBase {
// setOnClickListener 订阅
String listenerSetter();
/**
* 事件监听的类型
*/
Class<?> listenerType();
/**
* 事件处理
* @return
*/
String callbackMethod();
}
这个注解是放在其他注解之上的,那么这个注解怎么使用呢,就以View的长按事件监听为例:
package com.highgreat.sven.ioc;
import android.view.View;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerType = View.OnLongClickListener.class
,listenerSetter = "setOnLongClickListener",callbackMethod = "onLongClick")
public @interface OnLongClick {
int[] value() default -1;
}
在这个注解上面调用了刚才定义的EventBase注解,根据传入的值大家似乎就什么都看明白了吧,没错这里传入了View.OnLongClickListener事件监听三部曲,因为在一个类中可能不止一个控件会设置长按事件监听,所以这里的返回值是数组。
事件注入逻辑
/**
* 事件注入
* @param object
*/
private static void injectClick(Object object) {
Class<?> aClass = object.getClass();
//得到类的所有方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method method : declaredMethods) {
//得到方法上的所有注解(避免写死)
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
//创建一个事件类的超类,所有事件都是一个EventBase
EventBase eventBase = annotationType.getAnnotation(EventBase.class);
//如果没有eventBase,则表示当前方法不是一个处理事件的方法
if(eventBase==null) {
continue;
}
// 用于确定是哪种事件(onClick还是onLongClick)以及由谁来处理
//事件监听的类型
Class<?> listenerType = eventBase.listenerType();//View.OnClickListener.class
//订阅
String listenerSetter = eventBase.listenerSetter();//setOnClickListener
//事件处理 事件被触发之后,执行的回调方法的名称
String callBackMethod=eventBase.callbackMethod();
try {
//反射得到id,再根据id得到对应的View
Method valueMethod = annotationType.getDeclaredMethod("value");
int[] viewId = (int[])valueMethod.invoke(annotation);
for (int id : viewId) {
//根据id反射获取对应的view
Method findViewById=aClass.getMethod("findViewById",int.class);
View view = (View)findViewById.invoke(object, id);
if(view==null) {
continue;
}
//得到ID对应的VIEW以后
//开始在这个VIEW上执行监听 (使用动态代理)
//需要执行activity上的onClick方法
ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(object, method);
Object instance = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[]{listenerType}, listenerInvocationHandler);
//setOnClickListener(new View.OnClickListener());
Method onclickMethod = view.getClass().getMethod(listenerSetter,listenerType);
onclickMethod.invoke(view,instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
使用动态代理的方式将method的callback方法Onlick交回给activity去处理
package com.highgreat.sven.ioc;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ListenerInvocationHandler implements InvocationHandler {
private Object activity;
private Method activityMethod; //Onclick
public ListenerInvocationHandler(Object activity, Method activityMethod) {
this.activity = activity;
this.activityMethod = activityMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在这里调用Activity下面注解了的click方法
return activityMethod.invoke(activity,args);
}
}