高仿XUtils搭建Android ioc框架

什么是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 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 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 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 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 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 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 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 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 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 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 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等

你可能感兴趣的:(android)