个人博客

http://www.milovetingting.cn

IOC之运行时注入-实现Activity的布局注入+控件注入+事件绑定

前言

本文主要介绍基于IOC的设计原则,实现以下功能:

  • 布局注入

  • 控件注入

  • 事件注入

其实这些功能,在之前也有零散地介绍过,这里再做一个统一的整理。

这里暂时不考虑运行时反射的效率问题,只是展示一种实现方案。

IOC的定义

IOC,即Inversion of Control,意为控制反转,是面向对象编程中的一种设计原则,可以用来降低代码间的耦合。最常见的方式是依赖注入(Dependence Injection,简称DI)。通过IOC,对象在创建时,由外界来控制,而不是内部直接控制。

布局注入

平时,我们在Activity中,可能会通过在onCreate方法中调用setContentView的方法,给Activity绑定布局。而基于IOC,则可以通过注解来实现:

注解的定义

1
2
3
4
5
6
7
8
9
10
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LayoutInject {
/**
* 布局id
*
* @return
*/
@LayoutRes int value();
}

注解的使用

1
2
3
4
5
6
7
8
9
@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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 注入
*
* @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();
}
}

控件注入

注解的定义

1
2
3
4
5
6
7
8
9
10
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
/**
* 控件id
*
* @return
*/
@IdRes int value();
}

注解的使用

1
2
3
4
5
@ViewInject(R.id.btn1)
Button btn1;

@ViewInject(R.id.btn2)
Button btn2;

Inject方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 注入
*
* @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();
}
}
}

事件注入

注解的定义

事件类型的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {

/**
* 设置listener的方法,如:setOnClickListener
*
* @return
*/
String listenerSetter();

/**
* 事件,如:new View.OnClickListener()
*
* @return
*/
Class<?> listenerType();
}

点击事件的注解

1
2
3
4
5
6
7
8
9
10
11
12
@Event(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {

/**
* 控件id
*
* @return
*/
@IdRes int[] value();
}

长按事件的注解

1
2
3
4
5
6
7
8
9
10
11
12
@Event(listenerSetter = "setOnLongClickListener", listenerType = View.OnLongClickListener.class)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnLongClick {

/**
* 控件id
*
* @return
*/
@IdRes int[] value();
}

注解的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
 /**
* 注入
*
* @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);
}
}