什么是IOC?
看了很多文章说IOC是依赖注入
,或者说是控制反转
,其实这里有多个联系比较紧密的概念,纯理论概念的内容,感兴趣可以大概了解下:
DIP 依赖倒置原则(Dependency Inverse Rrinciple)
强调系统的“高层组件”不应当依赖于“底层组件”,并且不论是“高层组件”还是“底层组件”
都应当依赖于抽象。抽象不应当依赖于实现,实现应当依赖于抽象(软件设计原则)
IOC 控制反转( Inverse of Control)
一种反转流、依赖和接口的方式。就是将控制权“往高处/上层”转移,
控制反转是实现依赖倒置的一种方法(DIP的具体实现方 式)
DI 依赖注入(-Dependency Injection)
组件通过构造函数或者setter方法,将其依赖暴露给.上层,上层要设法取得组件的依赖, 并将其传递给组件
依赖注入是实现控制反转的一种手段(IloC的具 体实现方式)
IOC容器
依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)
注:
本文主要是借IOC实现事件注入,简单串一下几个只是点,属于比较基础的点,所以写的不是很详细,都写在备注上了:
①实现布局的注入
布局的注入比较简单:
主要实现的是用注解的方式,取代Activity的 setContentView()
方法:
创建一个注解,作用在类上(Demo中是Activity),用于取注解的值,也就是对应(Activity)要设置的布局的值
MainActivity只有两个控件,一个TextView,一个Button,代码就不贴了,有需看文末完整代码,
/**
* ContentView注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
//返回int类型的布局值
int value();
}
MainActivity和BaseActivity中代码比较简单:
MainActivity
//使用ContentView注解
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
}
BaseActivity
/**
* desc : BaseActivity
*/
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//帮助子类实现布局,控件,点击事件注入
InjectManager.inject(this);
}
}
所以代码逻辑主要在InjectManager
中实现,布局注入比较简单,直接看代码和备注吧,不做赘述,可以在Activity中的onResume()
等方法中检验代码是否有效~:
/**
* desc : InjectManager
* 注解管理类
*/
public class InjectManager {
/**
* @param activity
*/
public static void inject(Activity activity){
injectLayout(activity);
}
/**
* @param activity
* 布局注入
*/
public static void injectLayout(Activity activity){
//获取类
Class extends Activity> clazz = activity.getClass();
//获取作用在类上的注解,这里取的是ContentView类型的注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView!=null) {
//获取ContentView注解的值,如Demo中Mainactivity取到的是 R.layout.activity_main
int layoutId = contentView.value();
try {
//获取setContentView方法
Method method = clazz.getMethod("setContentView", int.class);
//执行setContentView方法
method.invoke(activity,layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
②实现控件的注入
控件的注入就是为了通过注解取代findViewById
方法,类似ButterKnife,只不过实现方式不同,ButterKnife采用的是APT
,这里主要就是使用的反射,实现上首先定义个注解,并在MainActivity中使用,代码如下:
InjectView注解
/**
* desc : InjectView
* InjectView注解,取代findViewById
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
int value();
}
MainActivity
中使用
@InjectView(R.id.btn)
Button btn;
具体实现实在InjectManager
中增加了一个方法 用于处理控件的注入,这里上面的布局注入相差不大,主要注意下需要将findViewById
方法的返回值赋值给View,具体可以看下注释:
/**
* @param activity
* 控件的注入
*/
private static void injectView(Activity activity) {
//获取类
Class extends Activity> clazz = activity.getClass();
//获取所有属性,包括private等
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//获取属性上的InjectView类型的注解
InjectView injectView = field.getAnnotation(InjectView.class);
if (injectView != null) {
//获取注解的值,就是View的id值
int viewId = injectView.value();
try {
//获取findViewById方法
Method method = clazz.getMethod("findViewById", int.class);
//执行findViewById方法
Object view = method.invoke(activity, viewId);
//打开私有访问权限,即private属性可以修改
field.setAccessible(true);
//将findViewById方法的返回值赋值给控件
field.set(activity,view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
③点击事件的注入
首先我们都知道Android的点击事件和长按事件是这样的:
//点击事件
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
//长按事件
btn.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
可以看到他们的共同点
- 监听方法名 setOnClickListener / setOnLongClickListener
- 监听对象 View.OnClickListener() / View.OnLongClickListener()
- 返回值 onClick(View v) / onLongClick(View v)
那么我们定义一个EventBase
注解需要将这是哪个特点体现出来,另外还要定义一个注解OnClick
,EventBase
作用在OnClick
注解之上,而OnClick
作用在回调的方法之上,就是响应View的点击事件或者长按事件的回调,也就是最终要在MainActivity中触发的方法~
下面以注入点击事件
为例
首先我们定义两个注解:
EventBase
注解主要是拿到三个必要信息,
/**
* desc : EventBase
* 事件注入的注解
*/
@Target(ElementType.ANNOTATION_TYPE)//作用在其它注解之上
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
//1.监听方法名 setOnClickListener / setOnLongClickListener
String listenerSetter();
//2.监听对象 View.OnClickListener() / View.OnLongClickListener()
Class> listenerType();
//3. 返回值 onClick(View v) / onLongClick(View v)
String callBackListener();
}
/**
* desc : OnClick
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter="setOnClickListener",listenerType= View.OnClickListener.class,callBackListener="onClick")
public @interface OnClick {
int[] value();
}
然后在InjectManager
中添加一个injectEvent
的方法用于处理方法事件的注入,这里用到了动态代理,
目的是确定回调的方法,因为回调的方法有可能是onClick
,也可能是onLongClick
代码如下:
/**
* @param activity
* 事件注入
*/
private static void injectEvent(Activity activity) {
//获取类
Class extends Activity> clazz = activity.getClass();
//获取方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//获取方法上的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
//获取注解的类型
Class extends Annotation> annotationType = annotation.annotationType();
if (annotationType != null) {
//获取EventBase注解
EventBase eventBaseAnnotation = annotationType.getAnnotation(EventBase.class);
//获取三个信息
if (eventBaseAnnotation!=null) {
//listenerSetter = setOnClickListener
String listenerSetter = eventBaseAnnotation.listenerSetter();
//listenerType = View.OnClickListener
Class> listenerType = eventBaseAnnotation.listenerType();
//
String callBackListener = eventBaseAnnotation.callBackListener();
try {
//获取OnClick注解的value方法
Method valueMethod = annotationType.getDeclaredMethod("value");
//获取OnClick注解的的值
int[] viewIds = (int[])valueMethod.invoke(annotation);
//代理的方式,根据注解类型,匹配对应的回调方法
//即View.OnClickListener 回调 onClick(View view)
//View.OnLongClickListener() 回调 onLongClick(View v)
ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(activity);
//添加需要拦截的方法,和替换执行的方法
//callBackListener onClick
//method clickEvent
Log.e("callBackListener:",callBackListener+"");
Log.e("method:",method.getName()+"");
listenerInvocationHandler.addMethod(callBackListener,method);
Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, listenerInvocationHandler);
for (int viewId : viewIds) {
//获取findViewById方法
Method findViewMethod = clazz.getMethod("findViewById", int.class);
//执行findViewById方法
View view = (View) findViewMethod.invoke(activity, viewId);
Method setter = view.getClass().getMethod(listenerSetter, listenerType);
setter.invoke(view,listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
贴一下代理的实现:
/**
* desc : ListenerInvocationHandler
* 代理的实现
* AOP切面思想
*/
public class ListenerInvocationHandler implements InvocationHandler {
//拦截的对象,可能是Activity可能是Fragment
//这里是MainActivity的OnClick方法
private Object target;
//存储方法名和方法
//这里key:OnClick value : 预期回调的目标方法 clickEvent
private HashMap methodHashMap=new HashMap<>();
public ListenerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (target!=null) {
String name = method.getName();
method=methodHashMap.get(name);//集合中有需要拦截的方法名直接拦截替换
if (method!=null) {
return method.invoke(target,args);
}
}
return null;
}
/**
* @param methodName 要拦截的方法名methodName:OnClick
* @param method 需要替换执行的方法method:clickEvent
* 添加要拦截的方法
*/
public void addMethod(String methodName,Method method){
methodHashMap.put(methodName,method);
}
}
另外OnLongClick
的注解如下,和上面OnClick
不同的是如果替换onLongClick方法,需要有个boolean返回值
/**
* desc : OnLongClick
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter="setOnLongClickListener",listenerType= View.OnLongClickListener.class,callBackListener="onLongClick")
public @interface OnLongClick {
int[] value();
}
完整代码可见:github