先大概了解下什么是IOC:
传统程序的编写过程中,在一个类中如果需要另外一个对象,就需要通过关键字 new 出另一个类的对象,然后才可以开始使用这个对象,使用完成之后,再将这个对象销毁,在这个过程中对象和对象之间耦合度很高。
有了 IoC 容器之后,所有的类都会在 IoC 容器中注册,并告诉 IoC 容器我是什么类,我需要什么对象,并且在运行到适当的时刻,将需要的对象创建并赋给我的引用,在适当的时刻再将该对象进行销毁。同时也会将创建我的对象,并赋给需要我的对象的类中。
在使用 IoC 容器框架编程时,对象的创建、销毁都是由 IoC 容器控制,而不是由引用它的对象控制。对于某个对象来说,以前是由它控制某个对象,现在是所有的对象都由 IoC 容器控制,所以叫控制反转。
先看下一个简单的onClickListener绑定:
public class ActivityIocTest extends AppCompatActivity implements View.OnClickListener {
@ResField
private Button btnTest1;
@ResField
private Button btnTest2;
@ResField
private Button btnTest3;
@ResField
private Button btnTest4;
@ResField
private Button btnTest5;
@ResField
private Button btnTest6;
@ResField(resId = R.id.btnTest7)
private Button btnTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View mainView = LayoutInflater.from(this).inflate(R.layout.activity_main,null);
setContentView(mainView);
ResUtil.setResItem(this.getClass(),this,this,mainView);
btnTest.setOnClickListener(this);
btnTest1.setOnClickListener(this);
btnTest2.setOnClickListener(this);
btnTest3.setOnClickListener(this);
btnTest4.setOnClickListener(this);
btnTest5.setOnClickListener(this);
btnTest6.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btnTest1:
btnTest1(v);
break;
case R.id.btnTest2:
btnTest2(v);
break;
case R.id.btnTest3:
btnTest3(v);
break;
case R.id.btnTest4:
break;
case R.id.btnTest5:
break;
case R.id.btnTest6:
break;
case R.id.btnTest7:
break;
}
}
public void btnTest1(View view){
}
public void btnTest2(View view){
}
public void btnTest3(View view){
}
}
像这种的写法,是要作很多重复的工作,现在就要注解来减少这种重复的工作。
先定义一个enum
public enum ListenerType {
onClick(1,"setOnClickListener", View.OnClickListener.class);
private int type;
private String methodName;
private Class<?> listenerClassType;
private ListenerType(int type,String methodName,Class<?> listenerType){
this.listenerClassType = listenerType;
this.type = type;
this.methodName = methodName;
}
public String getMethod(){
return methodName;
}
public Class<?> getListenerClassType(){
return listenerClassType;
}
}
现定义一个注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ListenerField {
ListenerType listenerType() default ListenerType.onClick;//哪种类型
}
具体实现:
public static void setViewListener(Class<?> cls, Object obj){
Field[] fields = cls.getDeclaredFields();//获取定义的类
if(fields != null) {
for (Field field : fields) {
field.setAccessible(true);
ListenerField annotation = field.getAnnotation(ListenerField.class);
if(annotation == null){
continue;
}
ListenerType listenerType = annotation.listenerType();
String methodName = listenerType.getMethod();
Class<?> classType = listenerType.getListenerClassType();
try {
View view = (View)field.get(obj);
if(view != null){
Method method = View.class.getMethod(methodName,classType);
method.invoke(view,obj);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
public class ActivityIocTest extends AppCompatActivity implements View.OnClickListener {
@ResField @ListenerField
private Button btnTest1;
@ResField @ListenerField
private Button btnTest2;
@ResField @ListenerField
private Button btnTest3;
@ResField @ListenerField
private Button btnTest4;
@ResField @ListenerField
private Button btnTest5;
@ResField @ListenerField
private Button btnTest6;
@ResField(resId = R.id.btnTest7)
private Button btnTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View mainView = LayoutInflater.from(this).inflate(R.layout.activity_main,null);
setContentView(mainView);
ResUtil.setResItem(this.getClass(),this,this,mainView);//先获取布局
ListenerUtil.setViewListener(this.getClass(),this);//再设置listener
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btnTest1:
btnTest1(v);
break;
case R.id.btnTest2:
btnTest2(v);
break;
case R.id.btnTest3:
btnTest3(v);
break;
case R.id.btnTest4:
break;
case R.id.btnTest5:
break;
case R.id.btnTest6:
break;
case R.id.btnTest7:
break;
}
}
public void btnTest1(View view){
Log.e("btn"," btnTest1");
}
public void btnTest2(View view){
Log.e("btn"," btnTest2");
}
public void btnTest3(View view){
Log.e("btn"," btnTest3");
}
}
这样就实现了用注解注册onClick事件,但这样又有问题要解决了:
1.如果注册了onLongClick,要把接口OnLongClickListener,实现下,如果还有View的其他事件要注册,又要把其他的接口实现下,能否把这步给省了?
先看下如下的代码片断:
ListenerType listenerType = annotation.listenerType();
String methodName = listenerType.getMethod();
Class<?> classType = listenerType.getListenerClassType();
try {
View view = (View)field.get(obj);
if(view != null){
Method method = View.class.getMethod(methodName,classType);
View.OnClickListener listener = new View.OnClickListener(){
@Override
public void onClick(View v) {
}
};
method.invoke(view,listener );
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
像这样的实现,是可以监听到click事件了,但onClick(View v)被执行到的时候,怎样可以被监到后,怎么执行我们需要执行的方法?
这里可以用动态代理的方法,至于什么动态代理,这里就先略过了,感觉兴趣的可以自行百度。
动态代理涉及到以下两个核心的接口和类:InvocationHandler和Proxy
1.InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
看下官方文档对InvocationHandler接口的描述:
/**
* {@code InvocationHandler} is the interface implemented by
* the <i>invocation handler</i> of a proxy instance.
*
* <p>Each proxy instance has an associated invocation handler.
* When a method is invoked on a proxy instance, the method
* invocation is encoded and dispatched to the {@code invoke}
* method of its invocation handler.
*
* @author Peter Jones
* @see Proxy
* @since 1.3
*/
每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,看如下invoke方法:
/**
* proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
* method:我们所要调用某个对象真实的方法的Method对象
* args:指代代理对象方法传递的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
2.Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
这个方法的作用就是创建一个代理类对象,它接收三个参数,我们来看下几个参数的含义:
1.loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
2.interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
3.h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
介绍完两个核心的类和接口,现在要说下具体的实现方法
先定义一个ListenerInvocationHandler类:
public class ListenerInvocationHandler implements InvocationHandler {
private Object activity;
private Method listeneInvokeMethod;//自己定义的要被执行的类
public ListenerInvocationHandler(Object activity, Method listeneInvokeMethod) {
this.activity = activity;
this.listeneInvokeMethod = listeneInvokeMethod;
}
/**
*按钮点下去就执行这个方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在这里去调用被注解了的click()
return listeneInvokeMethod.invoke(activity,args);
}
}
这里对 listeneInvokeMethod进行下解释:
listenerInvokeMethod就是事件被执行要被处理的方法,如:
public void btnTest1(View view){
Log.e("btn"," btnTest1");
}
对于:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在这里去调用被注解了的click()
return listeneInvokeMethod.invoke(activity,args);
}
例如当一个Button注册了onClick事件后,按下按钮后,会执行到onClick(View v)事件,
这时候就会执行到invoke的时候,method就相当于onClick,args就是v,这时候invoke的代码可以转为如下的伪代码:
@Override
public void onClick(View v) {
listeneInvokeMethod.invoke(activity,v);
}
如果listeneInvokeMethod是public void btnTest1(View view)
就相当于:
@Override
public void onClick(View v) {
btnTest1(v);
}
现在就要用Proxy来实现对应的Listener事件接口
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
比如我们要对View.OnClickListener生成对应的代理:
这里我们需要一个View.OnClickListener事件对应的ClassLoader,
就是View.OnClickListener.class.getClassLoader(),
interfaces是接口数组,这里就可以定义成: new Class[]{View.OnClickListener.class}
h这里就是ListenerInvocationHandler.
动态代理的代码基本实现完了,之前的注解是用Field上的,现在用动态代理,我们需要知道在事件被执行的时候,要调用哪个方法。现在重新生成一个用在方法Method上的注解,并且这个解注还需要知道这个方法上所绑定的控件id.
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListenerMethod {
ListenerType listenerType() default ListenerType.onClick;//哪种类型
int[] idList();//View id的列表
}
具体实现:
public static void setMethodListener(Object context){
Class<?> cls = context.getClass();
Method[] methodss = cls.getDeclaredMethods();//获取定义的类
if(methodss != null) {
for (Method method : methodss) {
method.setAccessible(true);
ListenerMethod annotation = method.getAnnotation(ListenerMethod.class);
if(annotation == null){
continue;
}
ListenerType listenerType = annotation.listenerType();
String methodName = listenerType.getMethod();
Class<?> classType = listenerType.getListenerClassType();
int[] idList = annotation.idList();
ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(context, method);
if(idList != null){
for(int id:idList){
Method findViewById= null;
try {
//获取findViewById的方法
findViewById = context.getClass().getMethod("findViewById",int.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
View view= null;
try {
//执行findViewById
view = (View) findViewById.invoke(context,id);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
if(view==null)
{
continue;
}
Object proxy= Proxy.newProxyInstance(classType.getClassLoader(), new Class[]{classType}, listenerInvocationHandler);
try {
Method listenerMethod = View.class.getMethod(methodName,classType);//获取例如setOnClickListener
listenerMethod.invoke(view,proxy);//执行例如setOnClickListener
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
}
这样就完成了对能过method对控件的事件的绑定,如要增加其他事件可以在ListenerType
里增加对应的方法。
这种的实现方式,还是会有以下的问题:
现在我们是在源码的基本上改的,如果是以sdk的形式提供出去,是没办法在ListenerType增加对应的方法的,所以当我们以sdk的形式提供功能后,要怎么才能动态的增加相应的方法?
要解决这个问题,可以在注解上使用注解。
定义一个注解ListenerEven,该注解在另外一个注解上使用
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ListenerEvent{
String listenerName();
Class<?> listenerType();//事件监听的类型
String getIdListMethodName() default "idList";//被注解修饰的注解获取id列表的方法名称,默认就叫idList
}
生成一个注解OnClick
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ListenerEvent(listenerName = "setOnClickListener",listenerType = View.OnClickListener.class)
public @interface OnClick {
int[] idList();//View id的列表
}
生成一个注解OnLongClick
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ListenerEvent(listenerName = "setOnLongClickListener",listenerType = View.OnLongClickListener.class,getIdListMethodName = "longClickIdList")
public @interface OnLongClick {
int[] longClickIdList();//View id的列表
}
当我们获取方法上的注解的时候,获取ListenerEven的注解就可以动态获取对应的方法了。
由于这里的注解不是固定的,所以通过获取注解上的ListenerEven注解才能确定相对应的注解,要获取对应的id列表,就要用反射把idList方法执行后获取对应的id列表。由于获取View id列表的函数有可能会变,所以在ListenerEvent定义一个getIdListMethodName用于定义获取View id列表的函数,默认为idList.
具体的实现如下:
/**
* 事件注入
* @param context
*/
public static void injectClick(Object context) {
Class<?> cls = context.getClass();
Method[] methods = cls.getDeclaredMethods();//获取定义的类
if(methods != null) {
for (Method method : methods) {
//得到方法上的所有注解
Annotation[] annotations = method.getAnnotations();
operatorAnnotations(context,method,annotations);
}
}
}
private static void operatorAnnotations(Object content,Method method,Annotation[] annotations){
for (Annotation annotation : annotations) {
Class<?> annotionClass = annotation.annotationType();
ListenerEvent listenerEven = annotionClass.getAnnotation(ListenerEvent.class);//获取注解上的ListenerEven注解
if(listenerEven == null){
continue;
}
// 用于确定是哪种事件(onClick还是onLongClick)
//订阅
String listenerName = listenerEven.listenerName();
//事件(事件监听的类型)
Class<?> listenerType = listenerEven.listenerType();
// int[] idList = OnClick.idList();//由于注解的类型是不定的,所以不能这么写死
Method idListMethod= null;
//获取注解里的idList方法
String idListMethodName = listenerEven.getIdListMethodName();
try {
idListMethod = annotionClass.getDeclaredMethod(idListMethodName);
int[] viewId= (int[]) idListMethod.invoke(annotation);
for(int id:viewId){
invokeViewListener(content,id,method,listenerName,listenerType);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
private static void invokeViewListener(Object context,int id,Method method,String listenerName,Class<?> listenerType){
Class<?> cls = context.getClass();
try {
Method findViewById=cls.getMethod("findViewById",int.class);
View view= (View) findViewById.invoke(context,id);
if(view==null){
return;
}
ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(context, method);
Object proxy=Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, listenerInvocationHandler);
//执行方法 setOnClickListener,new View.OnClickListener()
Method onClickMethod = view.getClass().getMethod(listenerName, listenerType);
onClickMethod.invoke(view, proxy);//view.setOnClickListener();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public class ActivityIocTest extends AppCompatActivity implements View.OnClickListener{
@ResField @ListenerField
private Button btnTest1;
@ResField @ListenerField
private Button btnTest2;
@ResField @ListenerField
private Button btnTest3;
@ResField @ListenerField
private Button btnTest4;
@ResField @ListenerField
private Button btnTest5;
@ResField @ListenerField
private Button btnTest6;
@ResField(resId = R.id.btnTest7)
private Button btnTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View mainView = LayoutInflater.from(this).inflate(R.layout.activity_main,null);
setContentView(mainView);
ResUtil.setResItem(this.getClass(),this,this,mainView);//先获取布局
// ListenerUtil.setViewListener(this);
ListenerUtil.setMethodListener(this);//再设置listener
ListenerUtil.injectClick(this);
//btnTest1.setOnLongClickListener();
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btnTest1:
btnTest1(v);
break;
case R.id.btnTest2:
btnTest2(v);
break;
case R.id.btnTest3:
btnTest3(v);
break;
case R.id.btnTest4:
break;
case R.id.btnTest5:
break;
case R.id.btnTest6:
break;
case R.id.btnTest7:
break;
}
}
@OnClick(idList = {R.id.btnTest1,R.id.btnTest4})
public void btnTest1(View view){
Log.e("btn"," btnTest1");
}
@OnClick(idList = {R.id.btnTest2})
public void btnTest2(View view){
Log.e("btn"," btnTest2");
}
@OnClick(idList = {R.id.btnTest3,R.id.btnTest6})
public void btnTest3(View view){
Log.e("btn"," btnTest3");
}
@OnLongClick(longClickIdList= {R.id.btnTest4,R.id.btnTest5})
public boolean onBtnLongClick(View v) {
Log.e("btn"," v long click id is "+v.getId());
return true;
}
@ListenerMethod(listenerType= ListenerType.onLongClick,idList = {R.id.btnTest6,R.id.btnTest7})
public boolean onBtnLongClick1(View v) {
Log.e("btn"," v long click id is "+v.getId());
return false;
}
@ListenerMethod(idList = {R.id.btnTest4,R.id.btnTest5})
public void btnTest4(View view){
Log.e("btn"," btnTest4");
}
}
以上就完成了通过依赖注入的方式实现View事件的绑定。
gitHub地址:git@github.com:fzhFidt/ResUtil.git