IOC控制反转注入框架
很早之前我们用过Xutils框架 里面有通过注解来使用findViewById 之前我们只是使用。
这样的框架我们要自己实现一遍
主要分为三个部分
1. 注入布局 (利用注解)
2. 注入控件
3. 注入事件 (利用动态代理注入事件)
定义注入布局时注解
package com.jiang.iocxutil.annotion;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by jiang on 2017/6/17.
* 注入布局
*
* ElementType.TYPE 表示类的注解
* RetentionPolicy.RUNTIME 运行时注解 一般我们编写的都是基于运行时的注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView
{
int value();
}
package com.jiang.iocxutil;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
/**
* Created by jiang on 2017/6/17.
*/
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.inject(this);
}
}
注入布局相关代码
/**
* 注入布局
* @param context
*/
public static void injectLayout(Context context) {
Class> clazz = context.getClass();
ContentView cOntentView= clazz.getAnnotation(ContentView.class);
if (contentView != null) {
int layoutId = contentView.value();
//setContentView
try {
Method method = clazz.getMethod("setContentView", int.class);
method.setAccessible(true);
method.invoke(context, layoutId);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
package com.jiang.iocxutil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.jiang.iocxutil.annotion.ContentView;
import com.jiang.iocxutil.annotion.OnClick;
import com.jiang.iocxutil.annotion.OnLongClick;
import com.jiang.iocxutil.annotion.ViewInject;
/**
* IOC控制反转框架
*
*/
@ContentView(value = R.layout.activity_main)
public class MainActivity extends BaseActivity {
private static final String TAG = "MainActivity";
// @Override
// protected void onCreate(Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
// }
@ViewInject(R.id.text_ioc)
TextView textView;
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "textView ==" + textView.hashCode());
// textView.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
//
// }
// });
// textView.setOnLongClickListener(new View.OnLongClickListener() {
// @Override
// public boolean onLongClick(View v) {
// return false;
// }
// });
}
// @OnClick(R.id.text_ioc)
// public void click(View view){
// toast("测试点击");
// }
@OnLongClick(R.id.text_ioc)
public boolean click(View view){
toast("测试点击");
return false;
}
public void toast(String string) {
Toast.makeText(this, string, Toast.LENGTH_SHORT).show();
}
}
这样就可以通过注入布局来代替展示SetContentView()方法
定义注解
package com.jiang.iocxutil.annotion;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by jiang on 2017/6/17.
* 注入控件注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
}
2.利用反射找到findViewById来注入控件
/**
* 依赖注入控件
* @param context
*/
public static void injectView(Context context) {
Class> aClass = context.getClass();
// 拿到成员变量 数组
Field[] fields = aClass.getDeclaredFields();
// 遍历所有的属性
for (Field field: fields) {
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
int valueID = viewInject.value();
try {
Method method = aClass.getMethod("findViewById", int.class);
View view = (View) method.invoke(context, valueID);
field.setAccessible(true);
field.set(context, view);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
@ViewInject(R.id.text_ioc)
TextView textView;
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "textView ==" + textView.hashCode());
}
为了事件的扩展性 我们要拿到事件的三个要素
1.事件的注册
2.事件的类型
3.事件的回调方法
package com.jiang.iocxutil.annotion;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by jiang on 2017/6/17.
* 注解的注解 事件的三要素
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
/**
* 监听事件的方法
* @return String
*/
String listenerSetter();
/**
* 事件的类型
* @return Class
*/
Class> listenerType();
/**
* 事件被触发后的回调方法
* @return
*/
String callBackMethod();
}
package com.jiang.iocxutil.annotion;
import android.app.Dialog;
import android.view.View;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by jiang on 2017/6/17.
* 以EventBase做注解
*
* 目前需要View.OnClickListener.class
* 为了扩展可能还需要Dialog.OnClickListener.class
*/
@EventBase(listenerSetter = "setOnClickListener"
, listenerType = View.OnClickListener.class
, callBackMethod = "onClick")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
int[] value();
}
然后通过反射和动态代理去执行回调
/**
* 注入事件
*
* public Method[] getMethods()返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法。
* public Method[] getDeclaredMethods()对象表示的类或接口声明的所有方法,
* 包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。
* @param context
*/
private static void injectEvent(Context context) {
Class> clazz = context.getClass();
Method[] methods = clazz.getMethods();
for (Method method: methods) {
/**
* 扩展性
*/
Annotation[] anns = method.getAnnotations();
// 循环拿到方法类型注解
for (Annotation ann: anns) {
// 拿到注解的类 先拿到OnlickC注解
Class extends Annotation> anntiOnType= ann.annotationType();
//然后再拿到注解的注解EvenBase
EventBase eventBase = anntionType.getAnnotation(EventBase.class);
if (eventBase == null) {
continue;
}
//拿到事件的三要素
// 设置事件 拿到 setOnclickListener
String listenerSetter = eventBase.listenerSetter();
// 事件类型 拿到 View.OnClickListener.class
Class> listenerType = eventBase.listenerType();
// 回调方法 拿到 callBackMethod onClick
String callBackMethod = eventBase.callBackMethod();
// 下一步 通过反射 给View 设置
// 继续反射拿到 注解里面的id
Map methodMap = new HashMap<>();
// 得到当前callBackMethod 对应 Onclick method -- clikText
methodMap.put(callBackMethod, method);
try {
//
Method declaredMethod = anntionType.getMethod("value");
// 注解上的方法 找到Id数组
int[] valuesId = (int[]) declaredMethod.invoke(ann);
for (int viewId: valuesId) {
Method findViewById = clazz.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(context, viewId);
//
if (view == null) {
continue;
}
//上面的事件三个要素全部拿到了 View的Class setOnClickListener 对应 view的setOnClickListener
Method setOnClickListener= view.getClass().getMethod(listenerSetter, listenerType);
ListenerInvocationHandler handler = new ListenerInvocationHandler(context, methodMap);
// 拿到动态代理
Object proxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{ listenerType }, handler);
// 将我们的方法执行
setOnClickListener.invoke(view, proxyInstance);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 如何给其设置一个监听 onClick又不在InjectUtils回调 而是在MainActivity里面回调
* 动态代理 今天的需求是要给按钮设置一个监听 我要执行的一个回调监听不能卸载InjectUtils里面
* 而是想回调 MainActivity
*/
动态代理类的设计
package com.jiang.iocxutil;
import android.content.Context;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
* Created by jiang on 2017/6/17.
*/
public class ListenerInvocationHandler implements InvocationHandler {
private Context context;
private Map methodMap;
public ListenerInvocationHandler(Context context, Map methodMap) {
this.cOntext= context;
this.methodMap = methodMap;
}
/**
* 处理代理对象的代理方法
* 我们要代理谁 MainActivity OnClickListener
* 持有一个真正的对象引用就是MainActivity
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
Method mtd = methodMap.get(name);
if (mtd == null) {
//不需要代理
return method.invoke(proxy, args);
} else {
//真正的代理方法
return mtd.invoke(context, args);
}
}
}
然后再MainActivity里面测试注册事件
@OnClick(R.id.text_ioc)
public void click(View view){
toast("测试点击");
}
此时我们的注册事件就已经完工。
我们看一下其扩展性
然后写长按点击事件 首先定义注解类似于点击事件
package com.jiang.iocxutil.annotion;
import android.view.View;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by jiang on 2017/6/17.
* 长按事件的注解
*/
@EventBase(listenerSetter = "setOnLongClickListener"
, listenerType = View.OnLongClickListener.class
, callBackMethod = "onLongClick")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnLongClick {
int[] value();
}
在Manactivity里面 添加
@OnLongClick(R.id.text_ioc)
public boolean longLlick(View view){
toast("测试点击");
return false;
}
长按点击也就可以成功了
注入工具类 包含注入布局 注入控件 和 注入事件
package com.jiang.iocxutil;
import android.content.Context;
import android.view.View;
import com.jiang.iocxutil.annotion.ContentView;
import com.jiang.iocxutil.annotion.EventBase;
import com.jiang.iocxutil.annotion.ViewInject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
/**
* Created by jiang on 2017/6/17.
* 依赖注入工具类、
* 分别为注入布局
* 注入控件
* 注入事件
*/
public class InjectUtils {
/**
* 初始化注入
* @param context
*/
public static void inject(Context context) {
injectLayout(context);
injectView(context);
injectEvent(context);
}
/**
* 依赖注入控件
* @param context
*/
public static void injectView(Context context) {
Class> aClass = context.getClass();
// 拿到成员变量 数组
Field[] fields = aClass.getDeclaredFields();
// 遍历所有的属性
for (Field field: fields) {
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
int valueID = viewInject.value();
try {
Method method = aClass.getMethod("findViewById", int.class);
View view = (View) method.invoke(context, valueID);
field.setAccessible(true);
field.set(context, view);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 注入布局
* @param context
*/
public static void injectLayout(Context context) {
Class> clazz = context.getClass();
ContentView cOntentView= clazz.getAnnotation(ContentView.class);
if (contentView != null) {
int layoutId = contentView.value();
//setContentView
try {
Method method = clazz.getMethod("setContentView", int.class);
method.setAccessible(true);
method.invoke(context, layoutId);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 注入事件
*
* public Method[] getMethods()返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法。
* public Method[] getDeclaredMethods()对象表示的类或接口声明的所有方法,
* 包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。
* @param context
*/
private static void injectEvent(Context context) {
Class> clazz = context.getClass();
Method[] methods = clazz.getMethods();
for (Method method: methods) {
/**
* 扩展性
*/
Annotation[] anns = method.getAnnotations();
// 循环拿到方法类型注解
for (Annotation ann: anns) {
// 拿到注解的类 先拿到OnlickC注解
Class extends Annotation> anntiOnType= ann.annotationType();
//然后再拿到注解的注解EvenBase
EventBase eventBase = anntionType.getAnnotation(EventBase.class);
if (eventBase == null) {
continue;
}
//拿到事件的三要素
// 设置事件 拿到 setOnclickListener
String listenerSetter = eventBase.listenerSetter();
// 事件类型 拿到 View.OnClickListener.class
Class> listenerType = eventBase.listenerType();
// 回调方法 拿到 callBackMethod onClick
String callBackMethod = eventBase.callBackMethod();
// 下一步 通过反射 给View 设置
// 继续反射拿到 注解里面的id
Map methodMap = new HashMap<>();
// 得到当前callBackMethod 对应 Onclick method -- clikText
methodMap.put(callBackMethod, method);
try {
//
Method declaredMethod = anntionType.getMethod("value");
// 注解上的方法 找到Id数组
int[] valuesId = (int[]) declaredMethod.invoke(ann);
for (int viewId: valuesId) {
Method findViewById = clazz.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(context, viewId);
//
if (view == null) {
continue;
}
//上面的事件三个要素全部拿到了 View的Class setOnClickListener 对应 view的setOnClickListener
Method setOnClickListener= view.getClass().getMethod(listenerSetter, listenerType);
ListenerInvocationHandler handler = new ListenerInvocationHandler(context, methodMap);
// 拿到动态代理
Object proxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{ listenerType }, handler);
// 将我们的方法执行
setOnClickListener.invoke(view, proxyInstance);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 如何给其设置一个监听 onClick又不在InjectUtils回调 而是在MainActivity里面回调
* 动态代理 今天的需求是要给按钮设置一个监听 我要执行的一个回调监听不能卸载InjectUtils里面
* 而是想回调 MainActivity
*/
}
匆匆编写 细节还需大家验证 有疑问
邮箱 zhangdanjiang_java@163.com
GitBub地址 https://github.com/JiangGeJavaAndroid/IocXutil