Android进阶——框架打造之IOC框架

什么是IOC

IOC(Inversion of Control):控制反转。开发过程中类里面需要用到很多个成员变量

  1. 传统的写法:你要用这些成员变量的时候,那么你就new出来用
  2. IOC的写法:你要用这些成员变量的时候,使用注解的方式自动注入进去

优点:代码量减少,加速开发
缺点:性能消耗加大,阅读性差,加速65535

框架的思路

框架例子

1
2
3
//实现Button自动findViewById的工作
@ViewById(R.id.bt_ioc)
private Button bt_ioc;

实现思路

  • 创建自定义注解 @ViewById
  • 通过某个字节码文件获取对应的自定义注解
  • 通过反射,获取注解和注解的值 (R.id.bt_ioc)
  • 通过对注解的值做相应的操作,并设置回对象自身

实现内容

  • 实现通过Id找到控件的功能
  • 实现通过Id找到Color、String资源
  • 实现绑定view的点击事件、长按事件
  • 实现绑定SetContentView
  • 实现绑定网络的检测功能

框架的结构

这里写图片描述

包含的注解介绍

  • OnClick:实现点击事件
  • OnLongClick:实现长按事件
  • ColorById:找到对应的Color值
  • ContentViewById:实现SetContentView
  • StringById:找到对应的String值
  • ViewById:实现findViewById
  • CheckNet:实现网络检查功能

框架的使用

下面的这个Activity实现了框架的所有内容

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
@ContentViewById(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.bt_ioc)
private Button bt_ioc;
@StringById(R.string.app_name)
private String app_name;
@ColorById(R.color.colorAccent)
private int color;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//IOC演示
InjectManager.inject(this);
bt_ioc.setText(app_name);
bt_ioc.setBackgroundColor(color);
}
//支持数组形式的绑定,绑定多个控件
@OnClick({R.id.open_ioc})
@OnLongClick({R.id.open_ioc})
@CheckNet()
public void open_ioc() {
Toast.makeText(this, "网络可用", Toast.LENGTH_SHORT).show();
}
}

框架的实现

框架的实现分为两步:自定义注解的创建和通过反射进行注入

一、自定义注解

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
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
int[] value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnLongClick {
int[] value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ColorById {
int value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentViewById {
int value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface StringById {
int value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewById {
int value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckNet {
}

Target注解的介绍

  • @Target(ElementType.XXX):代表的是注解放在XXX位置
  • @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(Policy.RUNTIME):代表运行时检测,class文件中存在
  • @Retention(Policy.CLASS):代表编译时检测,存在于class文件中,运行时无法获取
  • @Retention(Policy.SOURCE):代表在源文件中有效(在.java文件中有效)

二、注入步骤

从使用中可以看到,注入中最重要的步骤的是:InjectManager.inject(this),这里主要负责的事情有

  • 注入ContentView
  • 注入变量
  • 注入事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class InjectManager {
public static void inject(Activity activity) {
inject(new ViewManager(activity), activity);
}
public static void inject(Fragment fragment) {
inject(new ViewManager(fragment), fragment);
}
/**
* 注入
*
* @param viewManager
* @param object
*/
private static void inject(ViewManager viewManager, Object object) {
InjectManagerService.injectContentView(viewManager, object);
InjectManagerService.injectField(viewManager, object);
InjectManagerService.injectEvent(viewManager, object);
}
}

这里会使用到ViewManager辅助类,代码很简单,后面会用到

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
72
73
74
75
76
77
78
79
80
81
public class ViewManager {
private Activity mActivity;
private Fragment mFragment;
private View mView;
public ViewManager(Activity activity) {
this.mActivity = activity;
}
public ViewManager(View view) {
this.mView = view;
}
public ViewManager(Fragment fragment) {
this.mFragment = fragment;
}
/**
* 通过Id查询View
*
* @param resId
* @return
*/
public View findViewById(int resId) {
View view = null;
if (mActivity != null) {
view = mActivity.findViewById(resId);
}
if (mFragment != null) {
view = mFragment.getActivity().findViewById(resId);
}
if (mView != null) {
view = mView.findViewById(resId);
}
return view;
}
/**
* 设置根布局,仅限Activity
*
* @param resId
*/
public void setContentView(int resId) {
if (mActivity != null) {
mActivity.setContentView(resId);
}
}
/**
* 获取颜色
*
* @param resId
*/
public int getColor(int resId) {
int color = -1;
if (mActivity != null) {
color = mActivity.getResources().getColor(resId);
}
if (mFragment != null) {
color = mFragment.getActivity().getResources().getColor(resId);
}
return color;
}
/**
* 获取字符串
*
* @param resId
*/
public String getString(int resId) {
String str = "";
if (mActivity != null) {
str = mActivity.getString(resId);
}
if (mFragment != null) {
str = mFragment.getActivity().getString(resId);
}
return str;
}
}

在InjectManagerService中,也是上面的三个主要步骤,主要还是下面通过反射实现其真正的效果

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
public class InjectManagerService {
/**
* 注入根布局
*
* @param viewManager
* @param object
*/
public static void injectContentView(ViewManager viewManager, Object object) {
injectContentViewById(viewManager, object);
}
/**
* 注入变量
*
* @param viewManager
* @param object
*/
public static void injectField(ViewManager viewManager, Object object) {
injectFieldById(viewManager, object);
}
/**
* 注入事件
*
* @param viewManager
* @param object
*/
public static void injectEvent(ViewManager viewManager, Object object) {
injectOnClick(viewManager, object);
injectOnLongClick(viewManager, object);
}
/**
* 注入根布局
*
* @param viewManager
* @param object
*/
private static void injectContentViewById(ViewManager viewManager, Object object) {
Class<?> clazz = object.getClass();
ContentViewById contentView = clazz.getAnnotation(ContentViewById.class);
if (contentView != null) {
int layoutId = contentView.value();
viewManager.setContentView(layoutId);
}
}
/**
* 注入findViewById事件
*
* @param viewManager
* @param object
*/
public static void injectFieldById(ViewManager viewManager, Object object) {
//1. 获取Activity字节码,这里以Activity为例
Class<?> clazz = object.getClass();
//2. 获取字节码中所有的成员变量
Field[] fields = clazz.getDeclaredFields();
if (fields != null) {
//3. 遍历所有变量
for (Field field : fields) {
//4. 找到对应的注解
ViewById viewById = field.getAnnotation(ViewById.class);
StringById stringById = field.getAnnotation(StringById.class);
ColorById colorById = field.getAnnotation(ColorById.class);
if (viewById != null) {
//5. 获取注解中的值
int viewId = viewById.value();
//6. findViewById并设置访问权限
View view = viewManager.findViewById(viewId);
field.setAccessible(true);
try {
//7. 动态注入到变量中
field.set(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (stringById != null) {
int viewId = stringById.value();
String string = viewManager.getString(viewId);
field.setAccessible(true);
try {
field.set(object, string);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (colorById != null) {
int viewId = colorById.value();
int color = viewManager.getColor(viewId);
field.setAccessible(true);
try {
field.set(object, color);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 注入点击事件
*
* @param viewManager
* @param object
*/
public static void injectOnClick(ViewManager viewManager, Object object) {
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
if (methods != null) {
for (Method method : methods) {
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
int[] viewIds = onClick.value();
for (int viewId : viewIds) {
View view = viewManager.findViewById(viewId);
//检查网络
boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;
if (view != null) {
view.setOnClickListener(new DeclaredOnClickListener(method, object, isCheckNet));
}
}
}
}
}
}
/**
* 注入长按事件
*
* @param viewManager
* @param object
*/
public static void injectOnLongClick(ViewManager viewManager, Object object) {
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
if (methods != null) {
for (Method method : methods) {
OnLongClick onLongClick = method.getAnnotation(OnLongClick.class);
if (onLongClick != null) {
int[] viewIds = onLongClick.value();
for (int viewId : viewIds) {
View view = viewManager.findViewById(viewId);
//检查网络
boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;
if (view != null) {
view.setOnLongClickListener(new DeclaredOnLongClickListener(method, object, isCheckNet));
}
}
}
}
}
}
}

这里用到两个点击事件,并且将检查网络作为参数传进去到事件中处理,由于长按事件和点击事件大同小异,这里只贴一处代码

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
public class DeclaredOnLongClickListener implements View.OnLongClickListener {
private Method mMethod;
private Object mObject;
private boolean mIsCheckNet;
public DeclaredOnLongClickListener(Method method, Object object, boolean isCheckNet) {
this.mMethod = method;
this.mObject = object;
this.mIsCheckNet = isCheckNet;
}
@Override
public boolean onLongClick(View v) {
if (mIsCheckNet) {
if (!NetUtils.isNetworkAvailable(v.getContext())) {
Toast.makeText(v.getContext(), "网络不可用", Toast.LENGTH_SHORT).show();
return true;
}
}
//执行点击事件
try {
mMethod.setAccessible(true);
mMethod.invoke(mObject, v);
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject, null);
} catch (Exception e1) {
e1.printStackTrace();
}
}
return true;
}
}

结语

到这里IOC框架就结束了,其中比较重要的两点是注解的自定义和通过反射获取属性值并注入,其实代码挺简单的,反复看看还是挺容易理解的,大家可以结合源码进行阅读,其实在IOC路上还有权限的申请等功能可以实现,不过已经有第三方框架已经做好了

源码下载

坚持原创技术分享,您的支持将鼓励我继续创作!