美文网首页
Android 注解-layout,view,click

Android 注解-layout,view,click

作者: RookieRun | 来源:发表于2020-03-30 20:20 被阅读0次

本文的注解主要基于运行时的反射机制

一.注解

1.关键字Annotation,新建一个注解类也很简单


Annotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)//注解作用在类上面
//@Target(ElementType.FIELD)//注解作用在字段上面
//@Target(ElementType.METHOD)//注解作用在方法上面
//@Target(ElementType.ANNOTATION_TYPE)//注解作用在注解上面

@Retention(RetentionPolicy.SOURCE)//注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃,对应于:Java源文件(.java文件)
//@Retention(RetentionPolicy.CLASS)//注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期,对应于 .class文件
//@Retention(RetentionPolicy.RUNTIME)//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,对应于内存中的字节码
//生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用
//一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注解
//如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;
//如果只是做一些检查性的操作,比如@Override 和@SuppressWarnings,使用SOURCE 注解。

//注解@Override用在方法上,当我们想重写一个方法时,在方法上加@Override,当我们方法的名字出错时,编译器就会报错
//注解@Deprecated,用来表示某个类或属性或方法已经过时,不想别人再用时,在属性和方法上用@Deprecated修饰
//注解@SuppressWarnings用来压制程序中出来的警告,比如在没有用泛型或是方法已经过时的时候

public @interface TestAnnotation {
    int value();//方法的返回值就对应于注解的类型
}

二.布局的注解

2.1.新建注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *
 */
@Target(ElementType.TYPE)//作用在类上面
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    int value();//由于注解的是布局id,所以返回值是个int
}

2.2布局注解的使用

@ContentView(R.layout.activity_ioc)
public class IOCActivity extends AppCompatActivity {
    @InjectView(R.id.tv)
    TextView tv;
    @InjectView(R.id.btn)
    Button button;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectManager.inject(this);
    }
}

2.3布局注解的注入

public class InjectManager {
    public static void inject(Activity activity) {
        injectLayout(activity);
    }
 private static void injectLayout(Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        if (contentView != null) {
            int value = contentView.value();
            //直接通过activity调用setcontentView()
            activity.setContentView(value);
            //通过反射调用
            try {
//                Method setContentView = clazz.getMethod("setContentView", int.class);
//                setContentView.invoke(activity, value);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}

三.属性的注解

3.1新建注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

3.2注解的使用

@ContentView(R.layout.activity_ioc)
public class IOCActivity extends AppCompatActivity {
    @InjectView(R.id.tv)
    TextView tv;
    @InjectView(R.id.btn)
    Button button;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectManager.inject(this);
    }
}

3.3属性注解的注入

 private static void injectView(Activity activity) {
        Class<? extends Activity> aClass = activity.getClass();
        try {
            Method findViewById = aClass.getMethod("findViewById", int.class);
            //1.拿到所有属性
            Field[] declaredFields = aClass.getDeclaredFields();
            for (int i = 0; i < declaredFields.length; i++) {
                Field declaredField = declaredFields[i];
                //2.拿到具有InjectView注解的属性
                InjectView injectView = declaredField.getAnnotation(InjectView.class);
                if (injectView != null) {
                    //3.拿到对应注解的值
                    int viewResId = injectView.value();
                    //4.将注解的值使用findViewById()的返回值设置给对应属性
                    Object invoke = findViewById.invoke(activity, viewResId);
                    declaredField.setAccessible(true);
                    declaredField.set(activity, invoke);
                }
            }


        } catch (Exception e) {
            e.printStackTrace();
        }

    }

四.单一事件的注入(以onClick为例)

4.1新建注解

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

4.2使用注解

 @InjectClick(R.id.btn)
    public void btnClick(View view) {
        Toast.makeText(IOCActivity.this, "btn click", Toast.LENGTH_SHORT).show();
    }

4.3单一事件注解的注入

 /**
     * zhuru zhuru
     * 注入onClick事件
     * 该方法的局限性在于,每次只能注入单一事件,比如,onClick 或者 onLongClick
     *
     * @param activity
     */
    private static void injectOnClickEvent(final Activity activity) {
        final Class<? extends Activity> aClass = activity.getClass();
        try {
            Method findViewById = aClass.getMethod("findViewById", int.class);
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (final Method method : declaredMethods) {
                InjectClick injectClick = method.getAnnotation(InjectClick.class);
                if (injectClick != null) {
                    int[] clickResId = injectClick.value();//获取到注解click的资源id
                    final Object invoke = findViewById.invoke(activity, clickResId[0]);
                    ((View) invoke).setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            try {
                                method.invoke(activity);
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

多事件的注解(以OnClick和OnLongClick为例)

1.新建注解

(此处注解会涉及到两种,一种是直接作用域控件上的,如InjectClick或InjectOnLongClick)
另一种是,作用在上面注解上面的注解,用来后面动态代理设置对应的属性
1.1作用在控件上的注解:(此处只给出了OnClick的注解,LongClick同理)

import android.view.View;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@MultiEvents(listenerSetterMethod = "setOnClickListener", listenerField = View.OnClickListener.class, listenerCallBackMethod = "onClick")
public @interface InjectClick {
    int[] value();
}

1.2作用在上面注解上的注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiEvents {
    //1.设置监听的方法(setOnClickListener)
    String listenerSetterMethod();

    //2.设置监听的对象(onClickListener)
    Class<?> listenerField();

    //3.执行方法的回掉(onClick)
    String listenerCallBackMethod();
}

1.3注解的使用(同单一的事件注解)

 @InjectClick(R.id.btn)//如需要,此处可更换为LongClick等其他事件
    public void btnClick(View view) {
        Toast.makeText(IOCActivity.this, "btn click", Toast.LENGTH_SHORT).show();
    }

1.4注解的注入

此处涉及到了动态代理,以保证对注解事件的低耦合,也就是,注解事件可以自定义,以免局限于onClick或者onLongClick等

  private static void injectMultiEvents(Activity activity) {
        Class<? extends Activity> aClass = activity.getClass();
        Method[] declaredMethods = aClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            //1.获取到带有OnClick/OnLongCLick注解的方法,以及对应的控件Id
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                Class<? extends Annotation> annotationType = annotation.annotationType();
                if (annotationType != null) {
                    MultiEvents multiEvents = annotationType.getAnnotation(MultiEvents.class);
                    if (multiEvents != null) {
                        //获取到MultiEvents对应的三个属性
                        String listenerCallBackMethod = multiEvents.listenerCallBackMethod();
                        Class<?> listenerField = multiEvents.listenerField();
                        String listenerSetterMethod = multiEvents.listenerSetterMethod();
//                        Log.e("test", "listenerCallBackMethod--->" + listenerCallBackMethod);
//                        Log.e("test", "listenerField--->" + listenerField);
//                        Log.e("test", "listenerSetterMethod--->" + listenerSetterMethod);
                        try {
//                            //获取到带有InjeckClick注解的所有控件
                            Method valueMethod = annotationType.getDeclaredMethod("value");
                            int[] viewIds = (int[]) valueMethod.invoke(annotation);
////                            Log.e("test", "--------invoke---->" + viewIds.getClass().getSimpleName());
                            ListenerInvocationHandler invocationHandler = new ListenerInvocationHandler(activity);
                            invocationHandler.addMethod(listenerCallBackMethod, method);
                            Object proxy = Proxy.newProxyInstance(listenerField.getClassLoader(), new Class[]{listenerField}, invocationHandler);
                            for (int viewId : viewIds) {
                                View view = activity.findViewById(viewId);
                                if (view != null) {
                                    //注意getMethod(获取当前类和父类的所有public方法)与getDeclareMethod(获取当前类的方法)的区别
                                    Method setListenerMethod = view.getClass().getMethod(listenerSetterMethod, listenerField);
                                    setListenerMethod.invoke(view, proxy);
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }

            }
        }
       
    }

五.上面用到的代理

import android.util.Log;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * 将系统的方法回调,比如onClick更改为开发者自定义的事件名称,比如btnClick,tvClick等
 * 使用时,将需要更改的方法增加到定义的map中,然后写invoke
 */
public class ListenerInvocationHandler implements InvocationHandler {
    private Object target;
    //key 为原方法名,如onClick, value 为新的方法 如btnClick等
    private HashMap<String, Method> methodHashMap = new HashMap<>();

    public ListenerInvocationHandler(Object object) {
        this.target = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Log.e("test", "args--->" + args[0].getClass().getSimpleName());
        if (target == null) {
            return null;
        }
        String oldMethodName = method.getName();
        if (methodHashMap.containsKey(oldMethodName)) {
            method = methodHashMap.get(oldMethodName);
            if (method != null) {
                return method.invoke(target, args);
            }
        }
        return null;
    }

    public void addMethod(String oldMethodName, Method newMethod) {
        methodHashMap.put(oldMethodName, newMethod);
    }
}

引用:
1.Retention
2.Java的代理模式

相关文章

网友评论

      本文标题:Android 注解-layout,view,click

      本文链接:https://www.haomeiwen.com/subject/oflbuhtx.html