Using an IOC Framework in Android to make the code more refreshing



Inversion of control (Inversion of Control, the English abbreviation for IoC) is coupled to an important problem facing the corner of rules programmed to cut a computer program, the core is lightweight Spring framework. Inversion of Control is generally divided into two types, dependency injection (Dependency Injection, abbreviated DI) and dependent lookup (Dependency Lookup). Dependency injection used widely.


Popular speak is what IOC does?


Once upon a time, we create an item is created with both hands on the line, when we have a machine, the machine will replace the people, help people, create artifacts, reverse this process upside down. In Android, you get control, you need to manually get, then reversed its name suggests is the program automatically gets control.


I believe that learning through the Spring Java Web framework in the system who is no stranger to IOC should, then you know how apply it in Android, which could set up a little more and knowledge, including reflection, Java class notes, agency Design Patterns mode of knowledge. Here I will demonstrate how to get the IOC to run the controls, set the application callback method.


1. Obtain controls in Android


We know that all the layout Android phone interface are configured in the resource file XML, if you want to get a layout, under normal circumstances, we use the following method to get the:


setContentView(R.layout.activity_main);


Now we come to this subversion of your application, let's look at the application IOC framework how to obtain this application.


① First, we create our class notes ContentView.


In Android Studio creation process is as follows:


Click on the package name to create a class (i)



(Ii) select the class and create annotations



And (iii) a method of acquiring the ID passed in the other


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


@Target表示注解可以用于什么地方,这里用于类,其他参数如:FILED(成员),METHOD(方法),PACKAGE(包),ANNOTATION_TYPE(注解的注解),CONSTRUCTOR(构造函数),PARAMETER(参数),LOCAL_VARIABLE(局部变更)


@Retention什么时候加载注解类,一般都是RetentionPolicy.RUNTIME运行的时候。


②新建一个注入工具类InjectUtils.class:


public class InjectUtils {
    public static final String ACTIVITY_MAIN_CONTENTVIEW="setContentView";

    /**
     * 注入所有
     * @param activity
     */
    public static void injectAll(Activity activity){
        injectContentView(activity);
    }

    /**
     * 注入ContentView
     * @param activity
     */
    public static void injectContentView(Activity activity){
        Class<? extends Activity> clazz = activity.getClass();//获取该类信息
        ContentView contentView = clazz.getAnnotation(ContentView.class);//获取该类ContentView的注解
        //如果有注解
        if(contentView!=null){
            int viewId=contentView.value();//获取注解类参数
            try {
                Method method=clazz.getMethod(ACTIVITY_MAIN_CONTENTVIEW,int.class);//获取该方法的信息
                method.setAccessible(true);//获取该方法的访问权限
                method.invoke(activity,viewId);//调用该方法的,并设置该方法参数
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}


注解虽然详细,有可能不写点,有的人理解method.invoke有点困难。下面我来解释一下。


method.invoke(activity,viewId);你可能这样理解,activity调用了method设置了viewID,如果还不够形象,看方法下面:


setContentView.invoke(MainActivity.this,R.layout.activity_main)这样虽然不伦不类,但是你应该理解了,就是invoke的第一个参数调用method方法,该方法的参数就是invoke的第二个参数。


获取注解类的参数,如下代码中:


@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

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


@ContentView(R.layout.activity_main)中的R.layout.activity_main的值。然后用调用工具类调用设置其setContentView方法,这样布局文件就加载进来了。


运行后得到如下图所示:




2.用IOC加载控件


相信大家加载控件的方法,基本上都是千篇一律,如下所示:


this.but=(Button)findViewById(R.id.but);


假如现在有N个控件,估计你整个屏幕都被控件包围,写其他代码还需要滑动滚动条。这样是不是重复劳动是不是不值得?


下面我们来使用IOC框架来获取控件。


①创建注解类InjectControl,代码如下:


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


相信经过上面的介绍,这个很容易理解。


②在MainActivity配置注解类。


代码如下:


@InjectControl(value = R.id.content)
private TextView content;
@InjectControl(value = R.id.myBut1)
private Button myBut1;
@InjectControl(value = R.id.myBut2)
private Button myBut2;


③最后就是注入所有控件。


代码在InjectUtils中,如下:


public static final String ACTIVITY_MAIN_FINDVIEWBYID="findViewById";
/**
 *注入控件
 * @param activity
 */
public static void injectControl(Activity activity){
    Class<? extends Activity> clazz = activity.getClass();//获取该类信息
    Field[] fields=clazz.getDeclaredFields();//获致所有成员变更
    for (Field field:fields) {
        InjectControl injectControl = field.getAnnotation(InjectControl.class);
        if(injectControl!=null){
            int viewId=injectControl.value();
            try {
                Method method=clazz.getMethod(ACTIVITY_MAIN_FINDVIEWBYID, int.class);
                method.setAccessible(true);
                field.setAccessible(true);
                Object object=method.invoke(activity, viewId);
                field.set(activity,object);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}


这里几点需要解释一下,为什么用getDeclaredFields()获取成员变更,不用getFields(),因为getFields只能获取类中公有的成员变量,如果要获取包括私有的成员变量,就需要使用上面的方法,而我们定义的都是私有,故必须使用该方法,


这里调用了再次invoke的方法,一次是方法调用的,一次是成员变量调用的,为什么这样设计,因为,这里必须用二步才能完成。


我们就算直接调用findViewById也是两步,首先我们要调用this.findViewById()等到View后才赋值给某个变量,虽然看上去只有一条语句,但是却是两个部分。同理,我们必须调用activity.findViewById()方法获取到View后,才能把View赋值给field。所以这里有两步。


为了验证代码,我在MainActivity的onCreate()方法里面加入了如下代码:


if(myBut1!=null && myBut2!=null){
    Toast.makeText(this,"按钮已经获取到了",Toast.LENGTH_SHORT).show();
}


运行结果如下:





其他的上面的解释了,这里就不在叙述了。下面难点来了。


3.给按钮设置点击监听事件


平常我们给按钮设置监听事件的代码如下:


this.myBut2.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {

    }
});


马上颠覆你的所见,步骤如下:


①创建注解类InjectOnClick。


代码如下:


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectOnClick {
    int[] value();
}


你会发现,少了什么对吗?没错,接口类型,需要实现的方法,以及要调用设置的方法,这就需要下面第二步。


②设置注解类的注解类OnClickEvent。


代码如下:


@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClickEvent {
    Class<?> listenerType();//接口类型
    String listenerSetter();//设置的方法
    String methodName();//接口里面要实现的方法
}


修改InjectOnClick:


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnClickEvent(listenerType = View.OnClickListener.class,listenerSetter = "setOnClickListener",methodName = "onClick")
public @interface InjectOnClick {
    int[] value();
}


③在MainActivity里设置注解。


代码如下:


@InjectOnClick({R.id.myBut1,R.id.myBut2})
public void setButtonOnClickListener(View view){
    switch (view.getId()){
        case R.id.myBut1:
            Toast.makeText(this,"你好",Toast.LENGTH_SHORT).show();
            break;
        case R.id.myBut2:
            Toast.makeText(this,"我很帅",Toast.LENGTH_SHORT).show();
            break;
        default:
            break;
    }
}



④注入点击事件


代码在InjectUtils中,如下:


/**
 * 注入点击事件
 * @param activity
 */
public static void injectOnClickListener(Activity activity){
    Class<? extends Activity> clazz = activity.getClass();
    Method[] methods= clazz.getMethods();//获取所有声明为公有的方法
    for (Method method:methods){//遍历所有公有方法
        Annotation[] annotations = method.getAnnotations();//获取该公有方法的所有注解
        for (Annotation annotation:annotations){//遍历所有注解
            Class<? extends Annotation> annotationType = annotation.annotationType();//获取具体的注解类
            OnClickEvent onClickEvent = annotationType.getAnnotation(OnClickEvent.class);//取出注解的onClickEvent注解
            if(onClickEvent!=null){//如果不为空
                try {
                    Method valueMethod=annotationType.getDeclaredMethod("value");//获取注解InjectOnClick的value方法
                    int[] viewIds= (int[]) valueMethod.invoke(annotation,null);//获取控件值
                    Class<?> listenerType = onClickEvent.listenerType();//获取接口类型
                    String listenerSetter = onClickEvent.listenerSetter();//获取set方法
                    String methodName = onClickEvent.methodName();//获取接口需要实现的方法
                    MyInvocationHandler handler=new MyInvocationHandler(activity);//自己实现的代码,负责调用
                    handler.setMethodMap(methodName,method);//设置方法及设置方法
                    Object object= Proxy.newProxyInstance(listenerType.getClassLoader(),new Class<?>[]{listenerType},handler);//创建动态代理对象类
                    for (int viewId:viewIds){//遍历要设置监听的控件
                        View view=activity.findViewById(viewId);//获取该控件
                        Method m=view.getClass().getMethod(listenerSetter, listenerType);//获取方法
                        m.invoke(view,object);//调用方法
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


虽然注解很详细,但还是有必要说明一下:


㈠关于因为我们用的是Java的动态代理,每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法invoke方法:


Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数


㈡Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是newProxyInstance这个方法:


public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上


㈢getFields()与getDeclaredFields()区别:getFields()只能访问类中声明为公有的字段,私有的字段它无法访问,能访问从其它类继承来的公有方法.getDeclaredFields()能访问类中所有的字段,与public,private,protect无关,不能访问从其它类继承来的方法 


getMethods()与getDeclaredMethods()区别:getMethods()只能访问类中声明为公有的方法,私有的方法它无法访问,能访问从其它类继承来的公有方法.getDeclaredFields()能访问类中所有的字段,与public,private,protect无关,不能访问从其它类继承来的方法



我们实现的InvocationHandler接口,代码如下:


public class MyInvocationHandler implements InvocationHandler {
    private Object object;
    private Map<String, Method> methodMap = new HashMap<String, Method>(1);
    public MyInvocationHandler(Object object) {
        this.object = object;
    }
    public void setMethodMap(String name, Method method) {
        this.methodMap.put(name, method);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (object != null) {
            String name = method.getName();
            method= this.methodMap.get(name);
            if (method != null) {
                return method.invoke(object, args);
            }
        }
        return null;
    }
}


这就不用我过多的注释了,因为,大部分代码与前面的并无不同,代理顾名思义就是帮你调用一些方法。


其代码难点就在这个代理里面,其他的代码与上面获取布局,控件一样,唯一不同的是这个代码用到了注释的注释,嵌套层级有点多,所以用到了许两层For循环,时间复杂度可能增加一个数量级,但这在手机上也是可以忽略不计的。


把方法加入InjectUtils的injectAll中。


/**
 * 注入所有
 * @param activity
 */
public static void injectAll(Activity activity){
    injectContentView(activity);
    injectControl(activity);
    injectOnClickListener(activity);
}



运行效果如下:





4.看一下代码有多简洁


代码如下:


@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
    @InjectControl(value = R.id.content)
    private TextView content;
    @InjectControl(value = R.id.myBut1)
    private Button myBut1;
    @InjectControl(value = R.id.myBut2)
    private Button myBut2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtils.injectAll(MainActivity.this);
    }
    @InjectOnClick({R.id.myBut1,R.id.myBut2})
    public void setButtonOnClickListener(View view){
        switch (view.getId()){
            case R.id.myBut1:
                Toast.makeText(this,"你好",Toast.LENGTH_SHORT).show();
                break;
            case R.id.myBut2:
                Toast.makeText(this,"我很帅",Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
    }
}


可能有的人会问,我用一句findViewById或者一句setOnClickListener就可以解决的事,没事给自己找这复杂的代码写,我是不是有病啊。可是你忽略了一点,一但我将这个代码打包,这代码就可能复用到我所有的项目中,如果,你只开发小项目或一个项目,这样写确实不划算,但是如果你总是换项目就算不换,增加新界面的时候,总是需要写findViewById,那么当累计的一定的数量时,这样写是一定节省很多时间的


发布了94 篇原创文章 · 获赞 115 · 访问量 75万+

Guess you like

Origin blog.csdn.net/liyuanjinglyj/article/details/48653273