博客主页

这篇文章讲解了编译时注入,但运行时注入框架也值得学习。

结下来的任务是分析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. 订阅:事件的setter方法名,默认为set+type,如setOnClickListener
  2. 事件源:事件的listener,默认为点击事件,如View.OnClickListener
  3. 事件:事件源中提供的方法,如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<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;
    }
}

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)


小兵兵同学
56 声望23 粉丝

Android技术分享平台,每个工作日都有优质技术文章分享。从技术角度,分享生活工作的点滴。