这篇文章讲解了编译时注入,但运行时注入框架也值得学习。
结下来的任务是分析xUtils3核心模块IOC注入式的框架设计,注解解决事件的三要素,静态代理和动态代理,运行时注入布局,控件,事件
运行时注入布局
在Activity中加载布局文件一般都是通过在onCreate方法中调用setContentView方法设置布局
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
如果在Activity的类上通过注解方式设置布局,如下代码。运行时注入布局方式实现,@ContentView替代setContentView
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity { }
然后只需要在onCreate方法中调用注入方法,就自动帮助我们设置了布局
ViewInjector.inject(this);
实现方式就是被一些人所诟病反射技术实现。
public static void inject(Object target) {
Class> targetClass = target.getClass();
// 注入布局文件
// 获取Activity上的ContentView注解
ContentView contentView = targetClass.getAnnotation(ContentView.class);
if (contentView != null) {
int layoutResid = contentView.value();
// 布局资源文件非法
if (layoutResid <= 0) {
throw new RuntimeException("注入的布局资源文件非法");
}
try {
Method setContentViewMethod = targetClass.getMethod("setContentView", int.class);
setContentViewMethod.invoke(target, layoutResid);
} catch (Exception e) {
throw new RuntimeException("注入的布局资源文件失败::" + e.getMessage());
}
}
}
通过获取Activity上的ContentView注解得到布局文件,使用反射调用setContentView方法。
运行时注入控件
通过在控件上添加@ViewInject,就可以代替findViewById
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.text)
private TextView text;
}
注入控件代码实现
public static void inject(Object target) {
// ...
injectObject(target, targetClass);
}
private static void injectObject(Object target, Class> targetClass) {
if (targetClass == null) return;
// 注入控件
Field[] fields = targetClass.getDeclaredFields();
if (fields.length > 0) {
for (Field field : fields) {
Class> fieldType = field.getType();
if (/*不注入基本类型字段*/ fieldType.isPrimitive() ||
/*不注入数组类型字段*/ fieldType.isArray() ||
/*不注入静态字段*/ Modifier.isStatic(field.getModifiers()) ||
/*不注入final字段*/ Modifier.isFinal(field.getModifiers())) {
continue;
}
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
int viewResid = viewInject.value();
if (viewResid <= 0) continue;
try {
Method findViewByIdMethod = targetClass.getMethod("findViewById", int.class);
Object view = findViewByIdMethod.invoke(target, viewResid);
if (view != null) {
field.setAccessible(true);
field.set(target, view);
} else {
throw new RuntimeException("Invalid @ViewInject for "
+ targetClass.getSimpleName() + "." + field.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
通过反射拿到注入类中所有的字段,排除不需要注入的字段有:基本类型、数据类型、静态修饰、final修饰。在获取字段上的@ViewInject注解,使用反射调用findViewById找到view并设置给该字段。
运行时注入事件
在实现运行时注入事件之前,先了解下动态代理。
在动态代理中,代理类并不是在java代码中实现,而是在运行期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理。
事件的三要素:订阅、事件源、事件
- 订阅:事件的setter方法名,默认为set+type,如setOnClickListener
- 事件源:事件的listener,默认为点击事件,如View.OnClickListener
- 事件:事件源中提供的方法,如onClick
/**
*
* 事件注解
* 被注解的方法必须:
* 1. private修饰
* 2. 返回值类型没有要求
* 3. 参数签名和type的接口要求的参数签名一致
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {
// 控件的id集合,当id小于1时不执行事件绑定
int[] value();
// 事件三要素:订阅、事件源、事件
// 订阅,事件的setter方法名,默认为set+type
String setter() default "";
// 事件源,事件的listener,默认为点击事件
Class> type() default View.OnClickListener.class;
// 事件,如果type的接口类型提供多个方法,需要使用此参数指定方法名
String method() default "";
}
只需要在方法上添加@Event注解就可以代替View.setOnClickListener(OnClickListener l)
@Event(R.id.text)
private void gotoOut(View view) {
Log.d("todo_xutils", "onCreate: 注入的方式点击事件");
}
注入事件代码实现
private static void injectObject(Object target, Class> targetClass) {
// 注入事件
Method[] methods = targetClass.getDeclaredMethods();
if (methods.length > 0) {
for (Method method : methods) {
if (/*不注入静态的方法*/ Modifier.isStatic(method.getModifiers()) ||
/*注入的方法必须是private的*/ !Modifier.isPrivate(method.getModifiers())) {
continue;
}
Event event = method.getAnnotation(Event.class);
if (event != null) {
int[] ids = event.value();
for (int i = 0; i < ids.length; i++) {
int id = ids[i];
if (id > 0) {
method.setAccessible(true);
EventListenerManager.addEventMethod(target, targetClass, id, event, method);
}
}
}
}
}
}
通过反射拿到注入类中所有的方法,注入的方法需要必须:private修饰、返回值类型没有要求、参数签名和type的接口要求的参数签名一致。
class EventListenerManager {
static void addEventMethod(
Object target, /*注入的类*/
Class> targetClass, /*注入的类Class*/
int id, /*注入的控件的id*/
Event event, /*Event注解*/
Method method /*注入的方法*/
) {
try {
Method findViewByIdMethod = targetClass.getMethod("findViewById", int.class);
Object view = findViewByIdMethod.invoke(target, id);
if (view == null) {
throw new RuntimeException("No Found @Event for "
+ targetClass.getSimpleName() + "." + method.getName());
}
// view.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// }
// });
// 注解中定义的接口,如Event注解默认的接口为View.OnClickListener
Class> listenerType = event.type();
// 默认为空,事件的setter方法名,如:setOnClickListener
String listenerSetter = event.setter();
if (TextUtils.isEmpty(listenerSetter)) {
listenerSetter = "set" + listenerType.getSimpleName();
}
Object proxyListener = Proxy.newProxyInstance(
listenerType.getClassLoader(),
new Class>[]{listenerType},
new EventInvocationHandler(target, method)
);
// view.setOnClickListener(@Nullable OnClickListener l)
Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
setEventListenerMethod.invoke(view, proxyListener);
} catch (Exception e) {
e.printStackTrace();
}
}
}
获取Method上的@Event注解后,获取事件三要素。通过反射调用订阅方法,方法的参数设置为代理类
private static class EventInvocationHandler implements InvocationHandler {
// 存放代码对象,如MainActivity
private WeakReference
如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)