First, IOC simple popular science

IOC means Inversion of Control.

Let’s say you have A class A that has many member variables F1, F2, and so on.

The traditional way: you want to use these member variables, then you use new F1(), new F2(), etc.

The IOC principle is: No! We don’t want new, this is too high coupling degree, once the dependent F1, F2 construction method changes, all need new F1(), new F2() will need to change!

According to this principle, in order to understand the decoupling of dependent caller (A) and dependent provider (F1, F2, etc.), IOC decouples in two ways: 1. Write configuration files. 2. Use annotations

Of course, with configuration files and annotations, how do you inject? That is, how do you turn configuration or annotation information into the desired class?

OK, reflection is on! A long time ago, reflection was slow. Well, that was a long time ago. Now it’s not too slow. PS: Don’t say what happens if you use 10,000 annotations. Because you can’t call 10,000 methods at once. So, performance can be assured.

Is the so-called: no reflection, no frame!

For annotations, there are two ways: runtime annotations and compile-time annotations.

  1. Runtime annotations are simply run-time reflections that dynamically retrieve objects, properties, methods, etc. This is typically the case with IOC frameworks, which may sacrifice a bit of efficiency.

  2. Compile-time annotations are a way of doing extra things with annotations at compile time. This is what ButterKnife is famous for. ButterKnife automatically generates helper classes from annotations when we compile. To play with compile-time annotations, you have to rely on apt and R and then write your own class that inherits AbstractProcessor, overrides the Process method, and implements how to turn configuration or annotation information into the desired class.

For example, the implementation of Butterknife: is to call APT, using JavaFileObject at compile time according to annotations to create a helper class, also known as legend ** code generator: with code to generate code! ** Don’t ask me why I know so much about ButterKnife, because I’ve written about it before: ButterKnife (7.0.1).

Preliminary knowledge:

  • Java reflection
  • Java annotations
  • Java proxy

Second, the use of custom IOC framework

1. Instructions

(1) Class Notes:

IContentView: inject ContentView

(2) Field annotation:

IView: injects the View

IString: injects a String

IColor: Infuses Color

(3) Method Notes:

IClick: Inject click events

(4) Tools:

InjectUtil. Bind (this) : binding Activity

InjectUtil. Unbind (this) : a solution to the Activity

2. sample code

package com.che.baseutil;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.che.fast_ioc.InjectUtil;
import com.che.fast_ioc.annotation.IClick;
import com.che.fast_ioc.annotation.IColor;
import com.che.fast_ioc.annotation.IContentView;
import com.che.fast_ioc.annotation.IString;
import com.che.fast_ioc.annotation.IView;

import java.util.Random;

@IContentView(R.layout.activity_main)
public class MainActivity extends Activity {
    @IView(R.id.tv)
    TextView tv;
    @IView(R.id.bt)
    Button bt;
    @IString(R.string.text_home)
    String title;
    @IColor(R.color.turquoise)
    int turquoiseColor;
    @IColor(R.color.moccasin)
    int moccasinColor;
    private Random random;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtil.bind(this);
        random = new Random();
        tv.setText(title);
        tv.setBackgroundColor(turquoiseColor);
        bt.setBackgroundColor(moccasinColor);
    }

    @Override
    protected void onDestroy(a) {
        InjectUtil.unbind(this);
        super.onDestroy();
    }

    @IClick({R.id.tv, R.id.bt})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.tv:
                bt.setText(random.nextInt(100) + "");
                break;
            case R.id.bt:
                tv.setText("I want to change.");
                Intent intent = new Intent();
                intent.setAction(IntentKey.ACTIVITY_SECOND);
                startActivity(intent);
                break; }}}Copy the code

Iii. How to implement a custom IOC framework

1. Define the annotations you need

Injection layout:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface IContentView {
    // The default field, when used: @iView (r.i.D.tv)
    int value(a);

    @iView (id= R.I.T.V) @iView (id= R.I.T.V)
// int id() default 0;
}
Copy the code

Injection view:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface IView {
    int value(a);
}
Copy the code

Injection string

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface IString {
    int value(a);
}
Copy the code

Injection color value

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface IColor {
    int value();
}
Copy the code

Inject click events

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface IClick {
    int[] value();
}
Copy the code

2. Write injection utility classes

Spare knowledge:

  1. GetClass: activity.getclass ();

  2. For field: activity. GetDeclaredFields ();

  3. Access methods: activity. GetDeclaredMethods ();

  4. IsAnnotationPresent ()

  5. Get the annotation: getAnnotation()

  6. The field that gets the annotation: annotation.value(), as with any other field

  7. Set the value of this object to read and write: field.setaccessible (true);

  8. Set (object, value);


Analysis of ideas:

  1. Get the values of class annotations and field annotations, and perform related operations such as setContentView(Value), findViewById(value), and so on.

  2. Gets the value of the method annotation, from which a dynamic proxy class is generated, calls setOnClickListener(), and so on

package com.che.fast_ioc;

import android.app.Activity;
import android.view.View;

import com.che.base_util.LogUtil;
import com.che.fast_ioc.annotation.IClick;
import com.che.fast_ioc.annotation.IColor;
import com.che.fast_ioc.annotation.IContentView;
import com.che.fast_ioc.annotation.IString;
import com.che.fast_ioc.annotation.IView;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/** ** Annotation tools * 

*

public class InjectUtil { /** * bind **@param activity */ public static void bind(Activity activity) { Class<? extends Activity> clazz = activity.getClass(); / / processing processType(activity, clazz); // Iterate over all fields for (Field field : clazz.getDeclaredFields()) { // Process the fields processField(activity, field); } // Iterate over all the methods for (Method method : clazz.getDeclaredMethods()) { // The processing methodprocessMethod(activity, method); }}/** * unbind **@param activity */ public static void unbind(Activity activity) { try { Class<? extends Activity> clazz = activity.getClass(); // Iterate over all fields for (Field field : clazz.getDeclaredFields()) { LogUtil.print("field=" + field.getName() + "\t" + field.getType()); // Empty all fields boolean accessible = field.isAccessible(); field.setAccessible(true); field.set(activity, null); field.setAccessible(accessible); }}catch(IllegalAccessException e) { e.printStackTrace(); }}/** * handle class annotations */ private static void processType(Activity activity, Class<? extends Activity> clazz) { List<Class<? extends Annotation>> annoList = new ArrayList<>(); annoList.add(IContentView.class); for (Class<? extends Annotation> annotationType : annoList) { // Check whether an IContentView annotation exists if(clazz.isAnnotationPresent(annotationType)) { dispatchType(activity, clazz, annotationType); }}}/** * Distribution class annotation */ private static void dispatchType(Activity activity, Class<? extends Activity> clazz, Class<? extends Annotation> annotationType) { if (annotationType == IContentView.class) { IContentView annotation = clazz.getAnnotation(IContentView.class); intvalue = annotation.value(); activity.setContentView(value); }}/** * handle field annotations */ private static void processField(Activity activity, Field field) { List<Class<? extends Annotation>> annoList = new ArrayList<>(); annoList.add(IView.class); annoList.add(IColor.class); annoList.add(IString.class); for (Class<? extends Annotation> annotationType : annoList) { if (field.isAnnotationPresent(annotationType)) { boolean accessible = field.isAccessible(); field.setAccessible(true); dispatchFiled(activity, field, annotationType); field.setAccessible(accessible); }}}/** * distribute field annotations */ private static void dispatchFiled(Activity activity, Field field, Class annotationType) { try { if (annotationType == IView.class) { IView anno = field.getAnnotation(IView.class); int value = anno.value(); field.set(activity, activity.findViewById(value)); } if (annotationType == IString.class) { IString anno = field.getAnnotation(IString.class); int value = anno.value(); field.set(activity, activity.getString(value)); } if (annotationType == IColor.class) { IColor anno = field.getAnnotation(IColor.class); intvalue = anno.value(); field.set(activity, activity.getResources().getColor(value)); }}catch(IllegalAccessException e) { e.printStackTrace(); }}/** ** processing method annotation */ private static void processMethod(Activity activity, Method method) { List<Class<? extends Annotation>> annoList = new ArrayList<>(); annoList.add(IClick.class); for (Class<? extends Annotation> annotationType : annoList) { // Check whether an IContentView annotation exists if(method.isAnnotationPresent(annotationType)) { dispatchMethod(activity, method, annotationType); }}}/** * Distribution method annotation */ private static void dispatchMethod(Activity activity, Method method, Class<? extends Annotation> annotationType) { try { if (annotationType == IClick.class) { IClick annotation = method.getAnnotation(IClick.class); // Get the id inside the annotation int[] ids = annotation.value(); // Generate dynamic proxies when there are annotationsClassLoader classLoader = View.OnClickListener.class.getClassLoader(); Class<? >[] interfaces = {View.OnClickListener.class}; Object proxy = Proxy.newProxyInstance(classLoader, interfaces,new DynaHandler(activity, method)); for (int id : ids) { View view = activity.findViewById(id); Method onClickMethod = view.getClass().getMethod("setOnClickListener", View.OnClickListener.class); // Call the setOnClickListener callback in the dynamic classonClickMethod.invoke(view, proxy); }}}catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch(InvocationTargetException e) { e.printStackTrace(); }}}Copy the code

3. Dynamic proxy classes for method annotations

/** * call method in the activity: e.g., onClick, etc. * 

*

public class DynaHandler implements InvocationHandler { private Activity activity; private Method method; public DynaHandler(Activity activity, Method method) { this.activity = activity; this.method = method; } @Override public Object invoke(Object o, Method method, Object[] args) throws Throwable { // Call the dynamically injected method here return this.method.invoke(activity, args); }}Copy the code