1, IOC Simple Science Popularization
IOC is the Inversion of Control (IOC)
If you need to use many member variables F1 and F2 in a class A.
Traditional writing: if you want to use these member variables, you can use new F1(), new F2(), etc.
The IOC principle is: No! We don't want new. In this way, the coupling is too high. Once the construction methods of dependent F1 and F2 are changed, all places requiring new F1() and new F2() need to be modified!
According to this principle, in order to decouple dependency callers (A) and dependency providers (F1, F2, etc.), IOC decoupling has two ideas: 1. Write configuration files; 2. Use annotations
Of course, with configuration files and annotations, how to inject? That is, how to turn the configuration or annotation information into the required classes?
OK, here we go! In other words, a long time ago, the reflection was very slow. Well, that was a long time ago. Now it is not too slow. I simply tested it. A reflection takes about 2ms, which is basically negligible. PS: don't say what happens if you use 10000 annotations? Because you can't call 10000 methods at the same time. Therefore, performance can be assured.
The so-called: no reflection, no frame!
As for annotation, there are two ways: runtime annotation and compile time annotation.
-
Runtime annotation is to use reflection at runtime to dynamically obtain objects, attributes, methods, etc. This is the case with general IOC frameworks, which may sacrifice a little efficiency.
-
Compile time annotation is to perform some additional operations according to the annotation during program compilation. The famous ButterKnife uses compile time annotation. ButterKnife automatically generates some auxiliary classes according to the annotation when we compile. To play with compile time annotation, you must first rely on apt, r and then write a class to inherit AbstractProcessor, rewrite the process method, and implement how to turn the configuration or annotation information into the required class.
For example, the implementation of Butterknife is to call APT and create an auxiliary class with JavaFileObject according to the annotation during compilation, that is, the legendary code generator: generate code with code!
2, Use of custom IOC framework
1. Instructions
(1) Class annotation:
IContentView: inject ContentView
(2) Field annotation:
IView: inject View
IString: inject String
IColor: inject Color
(3) Method notes:
IClick: injection click event
(4) Tools:
InjectUtil.bind(this): bind Activity
InjectUtil.unbind(this): unbind Activity
2. Example 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() { 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; } } }
3, 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 { //Default field, when used: @ IView(R.id.tv) int value(); //Other fields, when used: @ IView(id=R.id.tv) // int id() default 0; }
Injection view:
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface IView { int value(); }
Injection string
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface IString { int value(); }
Injection color value
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface IColor { int value(); }
Injection click event
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface IClick { int[] value(); }
2. Write injection tool class
Backup knowledge:
-
Get class: activity.getClass();
-
Get fields: activity. Getdeclaraedfields();
-
Get method: activity.getDeclaredMethods();
-
Determine whether an annotation exists: isAnnotationPresent()
-
Get annotation: getAnnotation()
-
The field to get the annotation: annotation.value(), and other fields are the same
-
Set the readability / Writeability of a field: field.setAccessible(true);
-
Modify the field value of the object: field.set(object, value);
Train of thought analysis:
-
Get the value of class annotation and field annotation, and perform relevant operations according to the annotation value: for example, setContentView(value), findViewById(value), etc.
-
Get the value of the method annotation, generate a dynamic proxy class according to the annotation value, call setOnClickListener(), etc
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 tool class * <p> */ public class InjectUtil { /** * binding * * @param activity */ public static void bind(Activity activity) { Class<? extends Activity> clazz = activity.getClass(); //Processing class processType(activity, clazz); //Traverse all fields for (Field field : clazz.getDeclaredFields()) { //Processing field processField(activity, field); } //Traverse all methods for (Method method : clazz.getDeclaredMethods()) { //processing method processMethod(activity, method); } } /** * Unbound * * @param activity */ public static void unbind(Activity activity) { try { Class<? extends Activity> clazz = activity.getClass(); //Traverse all fields for (Field field : clazz.getDeclaredFields()) { LogUtil.print("field=" + field.getName() + "\t" + field.getType()); //Leave all fields blank boolean accessible = field.isAccessible(); field.setAccessible(true); field.set(activity, null); field.setAccessible(accessible); } } catch (IllegalAccessException e) { e.printStackTrace(); } } /** * Processing 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) { //Determine whether the 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); int value = annotation.value(); activity.setContentView(value); } } /** * Process 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); } } } /** * Distribution field annotation */ 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); int value = 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) { //Determine whether the 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 in the annotation int[] ids = annotation.value(); // Generate dynamic proxy when annotated ClassLoader 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 setOnClickListener method to callback in dynamic class onClickMethod.invoke(view, proxy); } } } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
3. Dynamic proxy class of method annotation
/** * Dynamic proxy, call the method in the activity: for example, onClick, etc * <p> */ 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 { // The method of dynamic injection is called here return this.method.invoke(activity, args); } }
Relevant video recommendations
[Android IOC framework design] what is IOC? What is DI?
Zero foundation of Android (Android) development, from entry to mastery
This article is transferred from https://juejin.cn/post/6955771529342746638 , in case of infringement, please contact to delete.