个人博客
http://www.milovetingting.cn
IOC之运行时注入-实现Activity的布局注入+控件注入+事件绑定
前言
本文主要介绍基于IOC
的设计原则,实现以下功能:
布局注入
控件注入
事件注入
其实这些功能,在之前也有零散地介绍过,这里再做一个统一的整理。
这里暂时不考虑运行时反射的效率问题,只是展示一种实现方案。
IOC的定义
IOC,即Inversion of Control
,意为控制反转
,是面向对象编程中的一种设计原则,可以用来降低代码间的耦合。最常见的方式是依赖注入
(Dependence Injection
,简称DI
)。通过IOC,对象在创建时,由外界来控制,而不是内部直接控制。
布局注入
平时,我们在Activity中,可能会通过在onCreate
方法中调用setContentView
的方法,给Activity绑定布局。而基于IOC,则可以通过注解
来实现:
注解的定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LayoutInject {
/**
* 布局id
*
* @return
*/
@LayoutRes int value();
}
注解的使用
@LayoutInject(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtil.inject(this);
}
}
在MainActivity上加上前面定义的LayoutInject注解,然后在onCreate中调用注入的方法InjectUtil.inject(this)
Inject方法
/**
* 注入
*
* @param target
*/
public static void inject(Object target) {
if (target == null) {
return;
}
injectLayout(target);
}
/**
* 布局注入
*
* @param target 需要注入的组件
*/
private static void injectLayout(Object target) {
Class> clazz = target.getClass();
boolean annotationPresent = clazz.isAnnotationPresent(LayoutInject.class);
if (!annotationPresent) {
return;
}
LayoutInject annotation = clazz.getAnnotation(LayoutInject.class);
int layoutId = annotation.value();
try {
Method method = clazz.getMethod("setContentView", int.class);
method.invoke(target, layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
控件注入
注解的定义
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
/**
* 控件id
*
* @return
*/
@IdRes int value();
}
注解的使用
@ViewInject(R.id.btn1)
Button btn1;
@ViewInject(R.id.btn2)
Button btn2;
Inject方法
/**
* 注入
*
* @param target
*/
public static void inject(Object target) {
if (target == null) {
return;
}
injectView(target);
}
/**
* 控件注入
*
* @param target 需要注入的组件
*/
private static void injectView(Object target) {
Class> clazz = target.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
boolean annotationPresent = field.isAnnotationPresent(ViewInject.class);
if (!annotationPresent) {
continue;
}
ViewInject annotation = field.getAnnotation(ViewInject.class);
int viewId = annotation.value();
try {
Method method = clazz.getMethod("findViewById", int.class);
View view = (View) method.invoke(target, viewId);
field.setAccessible(true);
field.set(target, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
事件注入
注解的定义
事件类型的注解
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {
/**
* 设置listener的方法,如:setOnClickListener
*
* @return
*/
String listenerSetter();
/**
* 事件,如:new View.OnClickListener()
*
* @return
*/
Class> listenerType();
}
点击事件的注解
@Event(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
/**
* 控件id
*
* @return
*/
@IdRes int[] value();
}
长按事件的注解
@Event(listenerSetter = "setOnLongClickListener", listenerType = View.OnLongClickListener.class)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnLongClick {
/**
* 控件id
*
* @return
*/
@IdRes int[] value();
}
注解的使用
@OnClick({R.id.btn1, R.id.btn2})
public void click(View view) {
int id = view.getId();
switch (id) {
case R.id.btn1:
Toast.makeText(getApplicationContext(), "按钮1点击了", Toast.LENGTH_SHORT).show();
break;
case R.id.btn2:
Toast.makeText(getApplicationContext(), "按钮2点击了", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
@OnLongClick({R.id.btn1, R.id.btn2})
public boolean longClick(View view) {
int id = view.getId();
switch (id) {
case R.id.btn1:
Toast.makeText(getApplicationContext(), "按钮1长按了", Toast.LENGTH_SHORT).show();
break;
case R.id.btn2:
Toast.makeText(getApplicationContext(), "按钮2长按了", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
return true;
}
Inject方法
/**
* 注入
*
* @param target
*/
public static void inject(Object target) {
if (target == null) {
return;
}
injectEvent(target);
}
/**
* 事件注入
*
* @param target 需要注入的组件
*/
private static void injectEvent(Object target) {
Class> clazz = target.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
Class extends Annotation> annotationType = annotation.annotationType();
boolean annotationPresent = annotationType.isAnnotationPresent(Event.class);
if (!annotationPresent) {
continue;
}
Event event = annotationType.getAnnotation(Event.class);
String listenerSetter = event.listenerSetter();
Class> listenerType = event.listenerType();
try {
Method valueMethod = annotationType.getDeclaredMethod("value");
valueMethod.setAccessible(true);
int[] viewIds = (int[]) valueMethod.invoke(annotation);
for (int viewId : viewIds) {
Method findViewByIdMethod = clazz.getMethod("findViewById", int.class);
View view = (View) findViewByIdMethod.invoke(target, viewId);
if (view == null) {
continue;
}
ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(target, method);
Object proxyInstance = Proxy.newProxyInstance(InjectUtil.class.getClassLoader(), new Class[]{listenerType}, listenerInvocationHandler);
Method listenerSetterMethod = view.getClass().getMethod(listenerSetter, listenerType);
listenerSetterMethod.setAccessible(true);
listenerSetterMethod.invoke(view, proxyInstance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
static class ListenerInvocationHandler implements InvocationHandler {
private Object target;
private Method method;
public ListenerInvocationHandler(Object target, Method method) {
this.target = target;
this.method = method;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return this.method.invoke(target, args);
}
}