Android_IOC容器实现View点击注入

什么是IOC?

看了很多文章说IOC是依赖注入,或者说是控制反转,其实这里有多个联系比较紧密的概念,纯理论概念的内容,感兴趣可以大概了解下:

DIP 依赖倒置原则(Dependency Inverse Rrinciple)

强调系统的“高层组件”不应当依赖于“底层组件”,并且不论是“高层组件”还是“底层组件”
都应当依赖于抽象。抽象不应当依赖于实现,实现应当依赖于抽象(软件设计原则)

IOC 控制反转( Inverse of Control)

一种反转流、依赖和接口的方式。就是将控制权“往高处/上层”转移,
控制反转是实现依赖倒置的一种方法(DIP的具体实现方 式)

DI 依赖注入(-Dependency Injection)

组件通过构造函数或者setter方法,将其依赖暴露给.上层,上层要设法取得组件的依赖, 并将其传递给组件
依赖注入是实现控制反转的一种手段(IloC的具 体实现方式)

IOC容器

依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)

注: 本文主要是借IOC实现事件注入,简单串一下几个只是点,属于比较基础的点,所以写的不是很详细,都写在备注上了:

①实现布局的注入

布局的注入比较简单:
主要实现的是用注解的方式,取代Activity的 setContentView()方法:
创建一个注解,作用在类上(Demo中是Activity),用于取注解的值,也就是对应(Activity)要设置的布局的值
MainActivity只有两个控件,一个TextView,一个Button,代码就不贴了,有需看文末完整代码,

/**
 * ContentView注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    //返回int类型的布局值
    int value();
}

MainActivity和BaseActivity中代码比较简单:
MainActivity

//使用ContentView注解
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
}

BaseActivity

/**
 * desc   : BaseActivity
 */
public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //帮助子类实现布局,控件,点击事件注入
        InjectManager.inject(this);
    }
}

所以代码逻辑主要在InjectManager中实现,布局注入比较简单,直接看代码和备注吧,不做赘述,可以在Activity中的onResume()等方法中检验代码是否有效~:

/**
 * desc   : InjectManager
 * 注解管理类
 */
public class InjectManager {

    /**
     * @param activity
     */
    public static void inject(Activity activity){
        injectLayout(activity);
    }

    /**
     * @param activity
     * 布局注入
     */
    public static void injectLayout(Activity activity){
        //获取类
        Class clazz = activity.getClass();
        //获取作用在类上的注解,这里取的是ContentView类型的注解
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        if (contentView!=null) {
            //获取ContentView注解的值,如Demo中Mainactivity取到的是 R.layout.activity_main
            int layoutId = contentView.value();
            try {
                //获取setContentView方法
                Method method = clazz.getMethod("setContentView", int.class);
                //执行setContentView方法
                method.invoke(activity,layoutId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

②实现控件的注入

控件的注入就是为了通过注解取代findViewById方法,类似ButterKnife,只不过实现方式不同,ButterKnife采用的是APT,这里主要就是使用的反射,实现上首先定义个注解,并在MainActivity中使用,代码如下:
InjectView注解

/**
 * desc   : InjectView
 * InjectView注解,取代findViewById
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
    int value();
}

MainActivity中使用

  @InjectView(R.id.btn)
    Button btn;

具体实现实在InjectManager中增加了一个方法 用于处理控件的注入,这里上面的布局注入相差不大,主要注意下需要将findViewById方法的返回值赋值给View,具体可以看下注释:

 /**
     * @param activity
     * 控件的注入
     */
    private static void injectView(Activity activity) {
        //获取类
        Class clazz = activity.getClass();
        //获取所有属性,包括private等
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //获取属性上的InjectView类型的注解
            InjectView injectView = field.getAnnotation(InjectView.class);
            if (injectView != null) {
                //获取注解的值,就是View的id值
                int viewId = injectView.value();
                try {
                    //获取findViewById方法
                    Method method = clazz.getMethod("findViewById", int.class);
                    //执行findViewById方法
                    Object view = method.invoke(activity, viewId);
                    //打开私有访问权限,即private属性可以修改
                    field.setAccessible(true);
                    //将findViewById方法的返回值赋值给控件
                    field.set(activity,view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

③点击事件的注入

首先我们都知道Android的点击事件和长按事件是这样的:

//点击事件
  btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });
//长按事件
   btn.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return false;
            }
        });

可以看到他们的共同点

  • 监听方法名 setOnClickListener / setOnLongClickListener
  • 监听对象 View.OnClickListener() / View.OnLongClickListener()
  • 返回值 onClick(View v) / onLongClick(View v)
    那么我们定义一个EventBase注解需要将这是哪个特点体现出来,另外还要定义一个注解OnClick,EventBase作用在OnClick注解之上,而OnClick作用在回调的方法之上,就是响应View的点击事件或者长按事件的回调,也就是最终要在MainActivity中触发的方法~
    下面以注入点击事件为例
    首先我们定义两个注解:

EventBase注解主要是拿到三个必要信息,

/**
 * desc   : EventBase
 * 事件注入的注解
 */

@Target(ElementType.ANNOTATION_TYPE)//作用在其它注解之上
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
    //1.监听方法名 setOnClickListener / setOnLongClickListener
    String listenerSetter();

    //2.监听对象 View.OnClickListener() / View.OnLongClickListener()
    Class listenerType();

    //3. 返回值  onClick(View v) /  onLongClick(View v)
    String callBackListener();
}
/**
 * desc   : OnClick
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter="setOnClickListener",listenerType= View.OnClickListener.class,callBackListener="onClick")
public @interface OnClick {
    int[] value();
}

然后在InjectManager中添加一个injectEvent的方法用于处理方法事件的注入,这里用到了动态代理,
目的是确定回调的方法,因为回调的方法有可能是onClick,也可能是onLongClick代码如下:

 /**
     * @param activity
     * 事件注入
     */
    private static void injectEvent(Activity activity) {
        //获取类
        Class clazz = activity.getClass();
        //获取方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            //获取方法上的注解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                //获取注解的类型
                Class annotationType = annotation.annotationType();
                if (annotationType != null) {
                    //获取EventBase注解
                    EventBase eventBaseAnnotation = annotationType.getAnnotation(EventBase.class);
                    //获取三个信息
                    if (eventBaseAnnotation!=null) {
                        //listenerSetter = setOnClickListener
                        String listenerSetter = eventBaseAnnotation.listenerSetter();
                        //listenerType = View.OnClickListener
                        Class listenerType = eventBaseAnnotation.listenerType();
                        //
                        String callBackListener = eventBaseAnnotation.callBackListener();

                        try {
                            //获取OnClick注解的value方法
                            Method valueMethod = annotationType.getDeclaredMethod("value");
                            //获取OnClick注解的的值
                            int[] viewIds = (int[])valueMethod.invoke(annotation);
                            //代理的方式,根据注解类型,匹配对应的回调方法
                            //即View.OnClickListener 回调 onClick(View view)
                            //View.OnLongClickListener() 回调 onLongClick(View v)
                            ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(activity);
                            //添加需要拦截的方法,和替换执行的方法
                            //callBackListener onClick
                            //method clickEvent
                            Log.e("callBackListener:",callBackListener+"");
                            Log.e("method:",method.getName()+"");
                            listenerInvocationHandler.addMethod(callBackListener,method);
                            Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, listenerInvocationHandler);

                            for (int viewId : viewIds) {
                                //获取findViewById方法
                                Method findViewMethod = clazz.getMethod("findViewById", int.class);
                                //执行findViewById方法
                                View view = (View) findViewMethod.invoke(activity, viewId);

                                Method setter = view.getClass().getMethod(listenerSetter, listenerType);
                                setter.invoke(view,listener);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

贴一下代理的实现:


/**
 * desc   : ListenerInvocationHandler
 * 代理的实现
 * AOP切面思想
 */
public class ListenerInvocationHandler implements InvocationHandler {
    //拦截的对象,可能是Activity可能是Fragment
    //这里是MainActivity的OnClick方法
    private Object target;
    //存储方法名和方法
    //这里key:OnClick value : 预期回调的目标方法 clickEvent
    private HashMap methodHashMap=new HashMap<>();

    public ListenerInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (target!=null) {
            String name = method.getName();
            method=methodHashMap.get(name);//集合中有需要拦截的方法名直接拦截替换
            if (method!=null) {
                return method.invoke(target,args);
            }
        }
        return null;
    }

    /**
     * @param methodName 要拦截的方法名methodName:OnClick
     * @param method 需要替换执行的方法method:clickEvent
     * 添加要拦截的方法
     */
    public void addMethod(String methodName,Method method){
        methodHashMap.put(methodName,method);
    }
}

另外OnLongClick的注解如下,和上面OnClick不同的是如果替换onLongClick方法,需要有个boolean返回值

/**
 * desc   : OnLongClick
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter="setOnLongClickListener",listenerType= View.OnLongClickListener.class,callBackListener="onLongClick")
public @interface OnLongClick {
    int[] value();
}

完整代码可见:github

你可能感兴趣的:(Android_IOC容器实现View点击注入)