高仿XUtils搭建Android ioc框架

2017-01-14 19:44:29来源:CSDN作者:huangxuanheng人点击

什么是IOC?即控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心。 控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。依赖注入应用比较广泛。
本文主要是高仿XUtils注解方式进行依赖注入,包括对控件实例化注入、控件事件监听注入、布局注入等

一、布局注入
在activity中我们常常看到通过这样的方式,渲染布局

@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_container);    }

通过这种方式当然是最简单最方便的,但这种方式是没有任何架构的,不利于拓展和管理,就举个最简单的例子,假设你的activity里面有很多代码,不说多,总代码量是400行还是情理之中的吧?如果要你找到这个activity的布局,然后对布局优化一下,你估计要找好一会儿,对吗?为什么?因为你代码量多,你不记得oncreate在什么位置,你不能保证你写的oncreate方法在第一行,或者是最后一行,对吧?

当然,我们可以通过依赖注入的方式,只需要这样配置,就可以渲染布局了,如下:

@ContentView(R.layout.content_main)public class MainActivity extends BaseActivity {}

代码是不是非常简洁了?OK这是必须的,如果产品说某个页面布局不好看,要去优化一下布局,找到activity,定位到类上面,是不是马上就找到了你引用的布局了?这就是依赖注入,非常方便,并且有简洁

当然,现在有些哥们等不及了,这种方式是如何实现对布局的注入的呢?其实非常简单,大家看到了MainActivity 是继承了BaseActivity 的了吗?我们的注入,实际上就是在BaseActivity 的oncreate方法里面就注入了,这样,以后每新增一个activity,就无需再次手动注入,直接通过配置类似@ContentView(R.layout.content_main)的 方式就可以轻松完成对布局的注入了。
OK,我们来看BaseActivity 的代码:

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

对,就是通过InjectUtils.inject(this);实现注入的,完成注入的步骤如下:
1.新建一个注解类ContentView
2.通过反射获取activity配置的注解ContentView对应的值value,完成对布局的注入

哈哈,so easy,原来就是这么简单,关于注解的定义,不是本文讨论重点,需要了解的自行查找资料

步骤:
1.定义注解类ContentView
2.获取activity对象的字节码,通过字节码获取其配置的 注解类ContentView
3.对注解ContentView 取value值,然后利用activity对象调用setContentView完成布局的渲染

OK,代码如下
1.定义注解类ContentView

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

2.获取activity对象的字节码,通过字节码获取其配置的 注解类ContentView

  Class<? extends BaseActivity> aClass = baseActivity.getClass();  ContentView annotation = aClass.getAnnotation(ContentView.class);

3.对注解ContentView 取value值,然后利用activity对象调用setContentView完成布局的渲染
int layoutId = annotation.value();
baseActivity.setContentView(layoutId);

整体demo:

    /**     * 注入contentView     * @param baseActivity     */    private static void injectContentView(BaseActivity baseActivity) {        Class<? extends BaseActivity> aClass = baseActivity.getClass();        ContentView annotation = aClass.getAnnotation(ContentView.class);        int layoutId = annotation.value();        baseActivity.setContentView(layoutId);    }

看到这里,现在有哥们等不及了?我直接使用setContentView不就好了,一行demo就搞定,你现在通过IOC框架的方式,要写那么多demo,并且最终还是调用setContentView完成,通过这个框架不仅demo量没有减少,反而多了,这样做到底有什么意义呢?
嗯嗯,哥们你说的不错,原理都是一样,都是通过调用setContentView完成对布局的渲染的。另外我们通过框架的方式,代码量也变多了一些,并且也会对性能有一定的影响。既然是框架,如果每次都需要编写这么多demo, 我想是换成谁都受不了,哪还会有人用呢~
其实嘛,框架是这么体现的,只需编写一次代码,后面都是复用,移植性好,容易拓展新功能,这就是框架的好处。

在XUtils中,我们常常看到布局中定义的控件,通过配置注解,就可以实例化控件了,如
@InjectView(R.id.zlv)
private ParallaxListView parallaxListView;

那么,这种方式是如何通过IOC实例化的呢?

步骤:
1.定义注解InjectView
2.获取activity字节码,通过反射得到所有声明的字段
3.获取声明字段声明的注解InjectView,获取其value(就是布局中控件对应的id)值
4.通过activity对象调用findViewById实例化控件,再通过字段字节码反射设置给声明的控件

1.定义注解InjectView

/**
* Created by harry on 2016/12/17.
* activity 实例化控件的注解
*/

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

2.获取activity字节码,通过反射得到所有声明的字段
Class

    /**     * 实例化有注解的控件     * @param baseActivity     */    private static void injectView(BaseActivity baseActivity) {        Class<? extends BaseActivity> aClass = baseActivity.getClass();        Field[] fields = aClass.getDeclaredFields();        if(fields!=null&&fields.length>0){            for (Field field:fields){                field.setAccessible(true);                InjectView annotation = field.getAnnotation(InjectView.class);                if(annotation!=null){                    int id = annotation.value();                    try {                         View view = baseActivity.findViewById(id);                        field.set(baseActivity,view);                    } catch (IllegalAccessException e) {                        e.printStackTrace();                    }                }            }        }    }

代码还算是通俗易懂的,这里就不再写注释了,上述步骤就是注释了。

话说,如果我要给控件定义点击事件,这时候要怎么做呢?
通常,我们在activity中是这样设置控件的点击事件的,如:

view.setOnClickListener(new OnclickListener(){    public void onClick(View v){      }    });view.setOnLongClickListener(new OnLongClickListener(){        public boolean onLongClick(View v){        }    });

等等,如果使用了IOC注入的方式,通常,我们只需这么定义

    @OnClick(R.id.tv)   //这id,就是在布局文件中定义的控件id,要设置的控件的点击事件    public void mac(View view){    }

我们在回顾传统的事件监听,它们都有一些共同的属性,譬如事件方法名称,事件类型,事件回调方法
由此,我们可以抽取出这些事件类型的共性,它包含的属性有
事件方法名称
事件类型
回调方法名称

步骤:
1.定义抽取出的事件基类注解EventBase,作为注解的注解
2.获取activity上声明的方法,并筛选出定义有EventBase注解的注解
3.得到注解的注解EventBase,获取其值
4.通过动态代理,为控件设置点击事件

1.定义抽取出的事件基类注解EventBase,作为注解的注解

/** * Created by harry on 2016/12/17. *  事件类型注解 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)  //注解的注解public @interface EventBase {    /**     * 事件监听的set方法     * @return     */    String listenerSetter();    /**     * 事件监听的类型     * @return     */    Class<?> listenerType();    /**     * 事件被触发之后,执行的回调方法名称     * @return     */    String callbackMethod();}@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@EventBase(listenerSetter ="setOnClickListener",listenerType = View.OnClickListener.class,callbackMethod ="onClick" )public @interface OnClick {    int[] value();}

2.获取activity上声明的方法,并筛选出定义有EventBase注解的注解
3.得到注解的注解EventBase,获取其值

 private static void injectEventBase(Object obj, Object view) {        Class<?> aClass = obj.getClass();        Method[] methods = aClass.getDeclaredMethods();        if(methods!=null&&methods.length>0){            for (Method method:methods){                //得到方法的注解                Annotation[] annotations = method.getAnnotations();                if(annotations!=null&&annotations.length>0){                    for (Annotation annotation:annotations){                        Class<? extends Annotation> annotationType = annotation.annotationType();                        //得到基类注解                        EventBase eventBase = annotationType.getAnnotation(EventBase.class);                        if(eventBase!=null){                            String listenerSetter = eventBase.listenerSetter();                            Class<?> listenerType = eventBase.listenerType();                            String callbackMethod = eventBase.callbackMethod();                            //1.获取方法上的注解信息                            try {                                Method value = annotationType.getDeclaredMethod("value");                                int[] ids = (int[]) value.invoke(annotation);                                for (int id:ids){                                    //2.实例化控件                                    View fieldView=null;                                    if(view instanceof View){                                        fieldView = ((View) view).findViewById(id);                                    }else if(view instanceof Activity){                                        fieldView = ((Activity) view).findViewById(id);                                    }                                    if(fieldView==null){                                        return;                                    }                                    Class<? extends View> viewClass = fieldView.getClass();                                    Method setter = viewClass.getMethod(listenerSetter, listenerType);                                    //3有了setter方法,就可以代理监听事件了                                    Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),new Class[]{listenerType} , new OnListenerInvocationHandler(obj,callbackMethod,method));                                    setter.invoke(fieldView,proxy);                                }                            } catch (Exception e) {                                e.printStackTrace();                            }                        }                    }                }            }        }    }

4.通过动态代理,为控件设置点击事件

/** * Created by harry on 2016/12/18. * 动态代理 */public class OnListenerInvocationHandler implements InvocationHandler {    private Method method;    private Object target;    private String callBackMethod;    public OnListenerInvocationHandler( Object target,String callBackMethod, Method method) {        this.target =target;        this.method=method;        this.callBackMethod=callBackMethod;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        //传入的回调方法名称与需要调用的方法名称一致的时候,反射调用带有注解的方法        if(method.getName().equals(callBackMethod)&&this.method!=null){            return this.method.invoke(target,args);        }        return method.invoke(proxy,args);    }}

到此,我们的事件注入就告一大段落了!

注入工具类完整代码:

package com.ishow.androidlibs.utils;import android.app.Activity;import android.support.annotation.NonNull;import android.view.View;import com.ishow.androidlibs.activity.BaseActivity;import com.ishow.androidlibs.annotation.ContainerView;import com.ishow.androidlibs.annotation.ContentView;import com.ishow.androidlibs.annotation.CreateView;import com.ishow.androidlibs.annotation.EventBase;import com.ishow.androidlibs.annotation.InjectView;import com.ishow.androidlibs.fragment.RootFragment;import com.ishow.androidlibs.ioc.proxy.OnListenerInvocationHandler;import java.lang.annotation.Annotation;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * Created by harry on 2016/12/17. * 注入工具 */public class InjectUtils {    /**     * 注入fragment     * @param rootFragment     * @return     */    public static View injectFragment(RootFragment rootFragment){        Class<? extends RootFragment> aClass = rootFragment.getClass();        CreateView annotation = aClass.getAnnotation(CreateView.class);        if(annotation!=null){            int layoutId = annotation.value();            View view = rootFragment.getActivity().getLayoutInflater().inflate(layoutId, null);            inject(rootFragment,view);            return view;        }        return null;    }    /**     * 根据指定对象及其view注入该对象下配置的控件     * @param obj 将要注入实例化的对象     * @param view view     */    public static void inject(@NonNull Object obj,View view) {        injectView(obj,view);        injectEvent(obj, view);    }    private static void injectEvent(Object obj, View view) {        injectEventBase(obj, view);    }    private static void injectEventBase(Object obj, Object view) {        Class<?> aClass = obj.getClass();        Method[] methods = aClass.getDeclaredMethods();        if(methods!=null&&methods.length>0){            for (Method method:methods){                Annotation[] annotations = method.getAnnotations();                if(annotations!=null&&annotations.length>0){                    for (Annotation annotation:annotations){                        Class<? extends Annotation> annotationType = annotation.annotationType();                        EventBase eventBase = annotationType.getAnnotation(EventBase.class);                        if(eventBase!=null){                            String listenerSetter = eventBase.listenerSetter();                            Class<?> listenerType = eventBase.listenerType();                            String callbackMethod = eventBase.callbackMethod();                            //1.获取方法上的注解信息                            try {                                Method value = annotationType.getDeclaredMethod("value");                                int[] ids = (int[]) value.invoke(annotation);                                for (int id:ids){                                    //2.实例化控件                                    View fieldView=null;                                    if(view instanceof View){                                        fieldView = ((View) view).findViewById(id);                                    }else if(view instanceof Activity){                                        fieldView = ((Activity) view).findViewById(id);                                    }                                    if(fieldView==null){                                        return;                                    }                                    Class<? extends View> viewClass = fieldView.getClass();                                    Method setter = viewClass.getMethod(listenerSetter, listenerType);                                    //3有了setter方法,就可以代理监听事件了                                    Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),new Class[]{listenerType} , new OnListenerInvocationHandler(obj,callbackMethod,method));                                    setter.invoke(fieldView,proxy);                                }                            } catch (Exception e) {                                e.printStackTrace();                            }                        }                    }                }            }        }    }    /**     * 注入控件     * @param obj     * @param view     */    private static void injectView(Object obj, View view) {        //1.获取obj对象字节码,反射获取配置字段注解        Class<?> aClass = obj.getClass();        Field[] fields = aClass.getDeclaredFields();        if(fields!=null&&fields.length>0){            for (Field field:fields){                InjectView annotation = field.getAnnotation(InjectView.class);                if(annotation!=null){                    int value = annotation.value();                    field.setAccessible(true);                    View viewById = view.findViewById(value);                    try {                        field.set(obj,viewById);                    } catch (IllegalAccessException e) {                        e.printStackTrace();                    }                }            }        }        //2.将注解值通过view来findViewById实例化字段    }    public static void inject(@NonNull BaseActivity baseActivity) {        injectContentView(baseActivity);        injectView(baseActivity);        injectEvent(baseActivity);    }    /**     * 注入事件     * @param baseActivity     */    private static void injectEvent(BaseActivity baseActivity) {        injectEventBase(baseActivity,baseActivity);    }    /**     * 实例化有注解的控件     * @param baseActivity     */    private static void injectView(BaseActivity baseActivity) {        Class<? extends BaseActivity> aClass = baseActivity.getClass();        Field[] fields = aClass.getDeclaredFields();        if(fields!=null&&fields.length>0){            for (Field field:fields){                field.setAccessible(true);                InjectView annotation = field.getAnnotation(InjectView.class);                if(annotation!=null){                    int id = annotation.value();                    try {                        View view = baseActivity.findViewById(id);                        field.set(baseActivity,view);                    } catch (IllegalAccessException e) {                        e.printStackTrace();                    }                }            }        }    }    /**     * 注入contentView     * @param baseActivity     */    private static void injectContentView(BaseActivity baseActivity) {        Class<? extends BaseActivity> aClass = baseActivity.getClass();        ContentView annotation = aClass.getAnnotation(ContentView.class);        int layoutId = annotation.value();        baseActivity.setContentView(layoutId);    }    /**     * 获取fragment容器ID     * @param baseActivity     * @return int     */    public static int getContainerViewId(@NonNull BaseActivity baseActivity){        Class<? extends BaseActivity> aClass = baseActivity.getClass();        ContainerView annotation = aClass.getAnnotation(ContainerView.class);        return annotation.value();    }}

好了,看我是这么使用的,如果是activity中渲染布局,为了更好的体现出框架的好处,我们定义一个基类activity—BaseActivity

public abstract class BaseActivity extends Activity{    private StackManager stackManager;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        InjectUtils.inject(this);    } }

然后我们只需这么使用,就可以加载布局上来了,以后所有

@ContentView(R.layout.content_main)public class MainActivity extends BaseActivity {}

类似的,如果是fragment,我们也抽出BaseFragment

public abstract  class BaseFragment extends Fragment implements ICompotCallBack {    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        //注入view        return InjectUtils.injectFragment(this);    }  }

这样,子类只需进行如下配置,就可以直接使用framgent了

@CreateView(R.layout.home)public class HomeFragment extends BaseFragment {}

如果要在activity或者fragment中实例化布局中定义的控件,只需如下配置,就可以了

@InjectView(R.id.zlv)
ParallaxListView parallaxListView;

点击事件的设置,只需如下配置:

    @OnClick(R.id.tv)    public void mac(View view){        //事件的业务逻辑...    }

如果要长按,我们可能再拓展,编写一个长按的注解,配置就好,如:

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

然后这样使用:

    @OnLongClick (R.id.tv)    public boolean toast(View view){        //事件的业务逻辑...        return false;    }

注:方法参数和返回值必须要与需要设置的点击类型的回调方法一致,譬如控件的OnLongClickListener的回调方法是public boolean onLongClick(View view),所以这里的方法的返回值和参数必须与回调方法一致,否则配置的点击事件就不起作用

看起来虽然代码很多,但只需一次编写,重复调用,可拓展性好,优点还是蛮多的,因为这是在运行时注入的,会影响性能。现在很多好的框架都是在编译时注入的,这样对性能影响不大,譬如ButterKnife等

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台