Manual Implementation of IOC Framework

Keywords: ButterKnife Android

The popular annotation framework is butterknife. The main purpose of using annotation framework is to improve the coding efficiency and write as few duplicate codes as possible, such as findviewbyid. Annotations are divided into compile-time annotations and run-time annotations, butterknife is compile-time annotations, and the framework implemented by myself is run-time annotations. Compile-time annotations are difficult and do not affect performance.
The Annotation Reflection Framework in Android consists of three parts:
1. Layout layout annotations; 2. view annotations; 3. Event annotations.
Framework establishment analysis is as follows:
The goal of the framework is to automate the following three tasks:
1. Layout's annotations address this.setContentView(R.layout.mainLayout) of activity, so the value of the layout's annotations only needs to pass an int value, and the context object needs to be passed in. Then its annotation class is designed as follows:

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

Specific methods:

 private static void injectLayout(Context context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class clazz = context.getClass();
        Method mtd_setContentView = clazz.getMethod("setContentView", int.class);
        Method mtd_findViewById = clazz.getMethod("findViewById", int.class);
        Annotation layoutAnnotation = clazz.getAnnotation(InjectLayout.class);
        if (layoutAnnotation != null && layoutAnnotation instanceof InjectLayout) {
            int layoutId = ((InjectLayout) layoutAnnotation).value();
            mtd_setContentView.invoke(context, layoutId);
        }
}

Usage in Activity:

@InjectLayout(value=R.layout.activity_main)
public class MainActivity extends BaseActivity {}

2. View annotation, which implements this.findViewById(R.id.xxx), so its annotation class only needs to pass an integer id value. The annotation class is designed as follows:

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

Specific methods:

private static void injectView(Context context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class clazz = context.getClass();
        Field[] fields = clazz.getDeclaredFields();
        Method mtd_findViewById = clazz.getMethod("findViewById", int.class);
        for (Field field : fields) {
            Annotation[] annotations = field.getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation instanceof InjectView) {
                    int viewId = ((InjectView) annotation).value();
                    field.setAccessible(true);
                    View view = (View) mtd_findViewById.invoke(context, viewId);
                    field.set(context, view);
                }
            }
        }
}

Usage in activity:

   @InjectView(value = R.id.button)
    Button myBtn;

3. Annotation of the method. In fact, it refers to the annotation of event monitoring in the form of:

  view.setOnClickListener(new View.OnClickListener(){
                       Public void onClick(View view){
                       Xxxx;
                      }
                      });

Or view.setOnLongClickListener(); view.setOnTouchListener() and so on.
Because there are anonymous inner classes in it, dynamic proxy is needed. To implement this method, you need to know which view binds listeners, so annotations need to include R.id.xxx; you need to know which view listens to, so you need a string to represent the specific method name (the method implemented by the anonymous inner class listener); you also need to know the full class name of the anonymous inner class when implementing dynamic proxy, so you also need a Class; So its annotation class is designed as follows

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBus(value = 1)
public @interface InjectOnClick {
    String onClickListener();
    String onClickMethod();
    String bindListerMethod();
    int[] bindViewId();
    Class type();
}

Annotation implementation method:

private static void injectOnClick(Context context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
        Class clazz = context.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        if (null == methods || methods.length == 0) return;
        HashMap<String, Method> methodMap = new HashMap<>();
        for (Method method : methods) {
            //method:onClickText
            Annotation[] annotations = method.getAnnotations();
            if (null == annotations || annotations.length == 0)
                continue;
            for (Annotation annotation : annotations)
                if (annotation instanceof InjectOnClick) {
                    String onClickName = ((InjectOnClick) annotation).onClickMethod();
                    methodMap.put(onClickName, method);
                    int[] viewIds = ((InjectOnClick) annotation).bindViewId();
                    for (int viewId : viewIds) {
                        //Annotation eventBus = (Annotation)annotation.annotationType();
                        Method mtd_findViewById = clazz.getMethod("findViewById", int.class);
                        //Get VIEW
                        View view = (View) mtd_findViewById.invoke(context, viewId);
                        Log.d(TAG, "injectOnClick: " + "");
                        //Class clazz_annoymous = Class.forName("android.view.View.OnClickListener");
                        Method setOnclickListener = view.getClass().getMethod(((InjectOnClick) annotation).bindListerMethod(), ((InjectOnClick) annotation).type());
                        Class clz = ((InjectOnClick) annotation).type();
                        //Dynamic proxy
                        XutilsInvocationHandler handler = new XutilsInvocationHandler(context, methodMap);//This class inherits InvocationHandler.
                        Object proxy = (Proxy) Proxy.newProxyInstance(context.getClassLoader(), new Class[]{clz}, handler);
                        setOnclickListener.invoke(view, proxy);
                    }
                }
        }
    }

How to use it in activity:

    @InjectOnClick(onClickListener = "View.OnClickListener",bindListerMethod ="setOnClickListener",
                   onClickMethod ="onClick",bindViewId = {R.id.button},type = View.OnClickListener.class)
    public void onButtonClick(View view){//The parameter view here cannot be omitted.
        Log.d("liudong", "onClick: ");
        Toast.makeText(this,"BUTTON Clicked",Toast.LENGTH_SHORT).show();
    }

The overall usage is to create a new BaseActivity extends Activity, which implements layout view onClick annotations in its onCreate().
public void onCreate(){
InjectUtils.injectLayout(this);
InjectUtils.injectView(this);
InjectUtils.injectOnClick(this);
}
Subsequent Activities then inherit the Base Activity.

Posted by Patch^ on Thu, 04 Apr 2019 17:21:31 -0700