博客主页
这篇文章讲解了编译时注入,但运行时注入框架也值得学习。
结下来的任务是分析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代码中实现,而是在运行期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理。
事件的三要素:订阅、事件源、事件
/** * * 事件注解 * 被注解的方法必须: * 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<Object> targetRef; private Method targetMethod; EventInvocationHandler(Object target, Method method) { targetRef = new WeakReference<>(target); targetMethod = method; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object target = targetRef.get(); if (target != null) { return targetMethod.invoke(target, args); } return null; } }