打造Android依赖注入框架,改变你的代码方式


[toc]


先来介绍下本次列车

本次列车来自地球,时速200km/s,即将开往火星,请要上车的朋友速速上车..
先介绍一个非常重要的东西:注解
注解(Annotation),我们在写代码的过程中,相信已经见不少了

1
2
3
4
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

简单一点来说就是带有@标记加上关键字的,我们通称为注解.
引用一下装逼语句

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

能够添加到 Java 源代码的语法元数据。类、方法、变量、参数、包都可以被注解,可用来将信息元数据与程序元素进行关联。Annotation 中文常译为“注解”。

注解的作用

  • 标记,用于告诉编辑器信息

  • 编译时动态处理,用于动态生成代码

  • 运行时动态处理,用于得到注解信息

定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.eicky;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Person {
String name() default "";
String sex() default "";
}

注解是通过 @interface 定义, 注解名则为你定义的名字.
实现部分所有方法没有方法体,没有参数没有修饰符,实际只允许 publicabstract 修饰符,默认为 public,不允许抛异常.
方法返回值只能是基本类型,String, Class, annotation, enumeration 或者是他们的一维数组.

@Target:定义注解的作用目标

  • @Target(ElementType.TYPE) //接口、类、枚举、注解
  • @Target(ElementType.FIELD) //字段、枚举的常量
  • @Target(ElementType.METHOD) //方法
  • @Target(ElementType.PARAMETER) //方法参数
  • @Target(ElementType.CONSTRUCTOR) //构造函数
  • @Target(ElementType.LOCAL_VARIABLE)//局部变量
  • @Target(ElementType.ANNOTATION_TYPE)//注解
  • @Target(ElementType.PACKAGE) ///包

@Retention: 定义注解的保留策略

  • @Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
  • @Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
  • @Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

调用注解

1
2
3
4
5
6
7
8
package com.eicky;

public class Eicky {
@Person(name = "eicky", sex = "男")
public String getInfo() {
return "Hello, Eicky";
}
}

解析注解

可以手动调用下面三个常用API进行解析

1
2
3
target.getAnnotation(AnnotationName.class);
target.getAnnotations();
target.isAnnotationPresent(AnnotationName.class);

好了,介绍了我们最重要的东西,应该步入正题了
我们Android中其实已经用过很多的依赖注入框架了,比如xutils、Retrofit 、Butter Knife、ORMLite等等,这些都是Android中的依赖注入框架,下面我们自己动手打造一个简单的依赖注入(毕竟一两篇博客是不可能做一个完美的依赖注入框架的)

不扯犊子了,发车了..

注入ContentView

  • 定义注解

    1
    2
    3
    4
    5
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ContentView {
    int value();
    }
  • 使用注解

    1
    2
    3
    4
    @ContentView(R.layout.activity_main)
    public class MainActivity extends AppCompatActivity {
    ....
    }

注入View

  • 定义注解

    1
    2
    3
    4
    5
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ViewInject {
    int value();
    }
  • 使用注解

    1
    2
    @ViewInject(R.id.text)
    TextView mTextView;

解析定义的注解,并且注入到Activity中

定义InjectUtils

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
public class InjectUtils {
private static final String CONTENT_VIEW_METHOD = "setContentView";
private static final String FIND_VIEW_METHOD = "findViewById";

public static void inject(Activity activity){
injectContentView(activity);
injectViews(activity);
}
//注入ContentView
private static void injectContentView(Activity activity){
Class<? extends Activity> clazz = activity.getClass();
//获取Class上的ContentView注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView != null){
try {
Method method = clazz.getMethod(CONTENT_VIEW_METHOD, int.class);
//反射调用Activity的setContentView方法
method.invoke(activity, contentView.value());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
//注入View
private static void injectViews(Activity activity){
Class<? extends Activity> clazz = activity.getClass();
//获取Activity中所有的字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields){
//获取字段上带有ViewInject标记的字段
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null){
try {
Method method = clazz.getMethod(FIND_VIEW_METHOD, int.class);
//反射调用Activity的findViewById方法获取View对象
Object target = method.invoke(activity, viewInject.value());
field.setAccessible(true);
//对字段设值
field.set(activity, target);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}

这样就完成了我们简单的ContentView视图和View的注入


下一篇将带大家进行Android中事件的注入,代码已上传github, 传送门

谢谢你请我吃糖,Mua
0%