美文网首页
Android IOC注入框架实现

Android IOC注入框架实现

作者: carlwu_186 | 来源:发表于2022-06-15 13:58 被阅读0次

Android中用到ioc,可以实现视图、组件绑定 ,事件绑定等。在我的另一篇文章butterKnife中提到了apt实现编译期生成注入代码,我们这里仿XUtils的注入模块原理,手动实现自己的注入框架,这种似乎看起来会更简单。

简单出发,我们实现的功能有:视图绑定(setContentView)、组件绑定(findViewById)、点击事件绑定(setOnClickListener)、长按事件绑定(setOnLongClickListener),其他你们想得到的可以触类旁通。

我们先看看怎么用这套框架:

1.定义BaseActivity
可能你会觉得代码侵入性太大了,那么你可以选择butterknife那一套,无奈xutils就是这么干的。

public class BaseActivity  extends Activity{


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtils.inject(this);
    }
}

ok ,就一行代码而已。

2.开始绑定
没错,就是两个步骤,很简单吧。

@ContentView(R.layout.activity_main)//视图绑定
public class MainActivity extends BaseActivity {

    @ViewInject(R.id.app_text)//组件绑定
    private Button textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);//这行不能少哦,注入还得依靠父类BaseActivity的onCreate去完成呢
    }

    @OnClick({R.id.app_text, R.id.app_text1})
    public void click(View view) {
        Log.d("wyj", "click: ");
    }

    @OnLongClick({R.id.app_text, R.id.app_text1})
    public boolean longClick(View view) {
        Log.d("wyj", "longClick: ");  
        return false;
    }
}

OK,回到Mainactivity中,我们注意到注入的onClick方法是void类型 ,注入的onLongClick方法是boolean类型,这是因为sdk api中这两个方法是这样返回的,我们其实归根到底还是对view设置了Listener,并且longClick返回的boolean值就是后面会返回给系统的。

接下来我们就着重讲讲InjectUtils是怎么实现注入的。

public class InjectUtils {

    public static void inject(Object context) {
        injectLayout(context);
        injectView(context);
        injectClick(context);
    }
}

可以看到,注入总共分为三块来做:视图注入、控件注入、点击事件注入,当然还可以扩展功能,这里点到为止。

我们先讲视图注入:

    private static void injectLayout(Object context) {
        int layoutId = 0;
        Class<?> clazz = context.getClass();
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        if (contentView != null) {

            layoutId = contentView.value();

            try {
                Method method = context.getClass().getMethod("setContentView", int.class);
                method.invoke(context, layoutId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

大意就是首先获取注入类的注解ContentView,这个注解是我们自己定义的:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
    int value();
}

我们通过注解类拿到传入的layout对应ID,再反射拿到注入类的setContentView方法,就可以进行注入了。如果你熟悉反射操作,相信这些都是很简单的了。

OK,下面讲组件注入:

    private static void injectView(Object context) {
        Class<?> aClass = context.getClass();
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            ViewInject viewInject = field.getAnnotation(ViewInject.class);
            if (viewInject != null) {
                int valueId = viewInject.value();
                try {
                    Method method = aClass.getMethod("findViewById", int.class);
                    View view = (View) method.invoke(context, valueId);
                    field.setAccessible(true);
                    field.set(context, view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
    int value();
}

这块和视图注入一样,没任何难度。

下面处理点击事件注入:

点击事件的注入我们写了两种类型的:短按点击、长按点击。
对应在Android常规写法就是setOnClickListener和setOnLongClickListener,框架无非就是帮助我们做了这些繁琐的事情。
先亮出点击事件的注解类:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener"
        , listenerType = View.OnClickListener.class
        , callbackMethod = "onClick")
public @interface OnClick {
    int[] value() default -1;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnLongClickListener"
        , listenerType = View.OnLongClickListener.class
        , callbackMethod = "onLongClick")
public @interface OnLongClick {
    int[] value() default -1;
}
@Retention(RetentionPolicy.RUNTIME)
//该注解在另外一个注解上使用
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {
//1  setOnClickListener  订阅
    String  listenerSetter();

//    事件源
    /**
     * 事件监听的类型
     * @return
     */
    Class<?> listenerType();


    /**
     * 事件被触发之后,执行的回调方法的名称
     * @return
     */
    String callbackMethod();
}

下面是处理的方法:

    private static void injectClick(Object context) {
        Class<?> clazz = context.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
//            OnClick onClick = method.getAnnotation(OnClick.class);
            Annotation[] annotations = method.getAnnotations();

            for (Annotation annotation : annotations) {

//                annotation  ===OnClick  OnClick.class
                Class<?> annotionType = annotation.annotationType();
                EventBase eventBase = annotionType.getAnnotation(EventBase.class);
                if (eventBase == null) {

                    continue;
                }
//                获取事件三要素
                //获取事件三要素 通过反射完成事件注入  设置事件监听的方法
                String listenerSetter = eventBase.listenerSetter();
                //事件监听的类型
                Class<?> listenerType = eventBase.listenerType();
                //onClick   事件被触发之后,执行的回调方法的名称
                String callBackMethod = eventBase.callbackMethod();

                Method valueMethod = null;
                try {
                    valueMethod = annotionType.getDeclaredMethod("value");
                    int[] viewId = (int[]) valueMethod.invoke(annotation);
                    for (int id : viewId) {
                        Method findViewById = clazz.getMethod("findViewById", int.class);
                        View view = (View) findViewById.invoke(context, id);
                        if (view == null) {
                            continue;
                        }

                        Method onClickMethod = view.getClass().getMethod(listenerSetter, listenerType);//setOnClickListener

                        ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(context, method);
//proxy  ONClickListeng
                        Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, listenerInvocationHandler);
                        onClickMethod.invoke(view, proxy);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

可以看到,首先我们拿到注入类的所有方法,然后针对单个方法拿到其上面的注解集合,我们的OnClick、OnLongClick 上面都是加了 EventBase 注解的。所以我们就需要尝试在注解之上再去拿注解EventBase

Class<?> annotionType = annotation.annotationType();
EventBase eventBase = annotionType.getAnnotation(EventBase.class);

再接下来要做的事情就是利用反射获取到View对象,并且通过动态代理的方式对view对象进行setOnClickListener或者setOnLongClickListener。
动态代理其实就利用method.invoke(view,Object) 来实现,我们这里的参数Object就是代理对象,你可以把Object proxy理解为就是listenerType的实例化对象。
进去ListenerInvocationHandler 瞅瞅:


相关文章

  • Android IOC注入框架实现

    Android中用到ioc,可以实现视图、组件绑定 ,事件绑定等。在我的另一篇文章butterKnife中提到了a...

  • Android IOC注入框架

    什么是IOC注入框架 ButterKnife大家都应该使用过,对于view的注入减少了大量篇幅的findViewB...

  • 初见spring

    框架 spring IOC AOP 配置文件 IOC/DI 依赖注入(Dependecy Injection) ...

  • MVPArms到Dagger2

    Dagger2 介绍 一般的IOC框架都是通过反射来实现的,单Dagger2作为android端的IOC框架,为了...

  • Android IOC框架实现

    写在前面 这篇文章是我以前写在CSDN上的,由于感觉CSDN上写作体验感觉不好,现在准备把以前的一些文章转到简书 ...

  • 解读 IoC 框架 InversifyJS

    InversityJS 是一个 IoC 框架。IoC(Inversion of Control) 包括依赖注入(D...

  • IOC注入框架设计

    什么是IOC注入框架 IOC-控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面...

  • Spring笔记

    IOC/DI 1.Xml实现 bean 注入方式 (set注入、构造注入)bean 作用域 (singleton ...

  • Spring5

    IoC 控制反转Ioc(Inversion of Control),是一种设计思想,DI(依赖注入)是实现Ioc的...

  • Spring

    Spring 的模块 1 什么是IOC,什么是依赖注入,Spring IOC 如何实现ans: IOC — Inv...

网友评论

      本文标题:Android IOC注入框架实现

      本文链接:https://www.haomeiwen.com/subject/aoyheftx.html