Android课程学习记录
注解是 在Java SE5 这个版本中引入的
1、什么是注解
在代码中最常见的一个就是 @Override
,看一下它的语法定义:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
可能会遇到的一个问题就是:请讲解一下什么注解?
答:注解,写法上来说就是上述的形式,一般常用来标记到方法、类、参数、变量上面
一句话就能解释清楚,好像说清楚了,但是又好像什么都没说清。
首先来看一下关注注解的一些基本定义
元注解 - 专门负责注解其他注解的注解,常用的元注解有@Target
、@Retention
、@Documented
、 @Inherited
@Target
是标记这个注解在哪里使用的,参数类型是定义在ElementType
中的
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */域声明
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */ 普通参数
PARAMETER,
/** Constructor declaration */ 构造方法
CONSTRUCTOR,
/** Local variable declaration */ 局部变量
LOCAL_VARIABLE,
/** Annotation type declaration */ 注解
ANNOTATION_TYPE,
/** Package declaration */ 包
PACKAGE,
/**
* Type parameter declaration
* Java 1.8 加入的,用于类型参数的声明
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
* Java 1.8 加入的,用于一个类型的使用
* @since 1.8
*/
TYPE_USE
用 @Target
标注指定类型后,就能高速编译器做类型限制
@Retention
表示需要在什么级别保留该注解信息。参数类型是在RetentionPolicy
中定义
public enum RetentionPolicy {
/** 表示注解仅在源码中可用,将会被编译器丢掉. */
SOURCE,
/**表示注解会被编译器记录在 class 文件中,但在运行时虚拟机(VM)不会保留*/
CLASS,
/**
* 表示注解会被编译器记录在 class 文件中,而且在运行时虚拟机(VM)会保留注解。所以这里可以通过反射读取注解的信息
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
2、 使用场景
单独的讲一个注解是没啥意义的,它必须和其他的一些技术或者场景集合起来,才能发挥其作用
2.1 语法检查
这个用法在Android 系统的源码也用的比较多,看一下下边这个例子:
/**定义一个方法,参数用 {@link @DrawableRes} 标记*/
private static void setImg(@DrawableRes int imgRes){
Log.d("TAG", "setImg: "+imgRes);
}
可以看到,传入不是 @DrawableRes
类型的 int 参数会提示传参有误,这个用法可用来规范传参,防止误传
2.2 代替枚举
枚举的每一个元素占用会比较大,是一个对象,有对象头等等,用注解定义的元素一般是基础数据类型,例如int等,能节省一部分开销,算是一个小小的优化项
2.3 注解+APT
一些使用比较广泛的开源框架就是利用这个技术点来实现的,例如 ButterKnife、Dagger2、Hilt等
2.3.1 APT是啥
APT 全称 Annotation Processing Tool,翻译过来就是注解处理程序,在Java 的.java 文件编译成.class 文件过程中做一些动作,简单画个图
2.3.2 操作流程
下边用一个demo简单说明下 APT 的实现流程
- 建一个新的Android项目
- 创建一个java-library 用来存放定义的注解,命名 annotation
// build.gradle
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "7"
targetCompatibility = "7"
这里定义一个 找view id的注解 BindView
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
int value();
}
- 在创建一个 java-library,用来处理定义的注解,命名 annotation_compiler,定义如下
// build.gradle
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
implementation project(path: ':annotations')
}
sourceCompatibility = "7"
targetCompatibility = "7"
TIPS:
- 需要引入
com.google.auto.service:auto-service:1.0-rc4
依赖库 - 把前一个module annotation添加到依赖里面
接下来就是 apt 的处理逻辑代码,创建一个处理类:
// 注意需要添加 @AutoService(Processor.class) 这个标记
@AutoService(Processor.class)
public class AnnotationsCompiler extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
apt 的模板代码 就写好了,具体的处理逻辑 在 process()
里面去实现就好,这里先简单介绍几个 可能会用到的一些其他 父类方法
- 第一个
/** 支持的版本,一般使用 SourceVersion.latestSupported() 就好 */
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
- 第二个
/** 把需要处理的注解装到一个集合里面,比如这里要处理的 BindView 注解 */
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
- 第三个
/** 初始化入口 */
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
}
- 第四个
/** 处理具体逻辑的地方 */
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
接下来就根据 添加注解自动找ID这个功能做一个简单的实现,整个注解处理的逻辑如下:
@AutoService(Processor.class)
public class AnnotationsCompiler extends AbstractProcessor {
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
/** 定义一个对象,用来生成APT目录下面的文件 */
Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
}
/**
* 所有的坏事都在这个方法中实现
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "test---------------" + set);
//获取APP中所有用到了BindView注解的对象
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
// TypeElement//类
// ExecutableElement//方法
// VariableElement//属性
//开始对elementsAnnotatedWith进行分类
Map<String, List<VariableElement>> map = new HashMap<>();
for (Element element : elementsAnnotatedWith) {
VariableElement variableElement = (VariableElement) element;
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
Class<?> aClass = variableElement.getEnclosingElement().getClass();
List<VariableElement> variableElements = map.get(activityName);
if (variableElements == null) {
variableElements = new ArrayList<>();
map.put(activityName, variableElements);
}
variableElements.add(variableElement);
}
//开始生成文件
// package com.example.aptdemo;
// import com.example.aptdemo.IBinder;
// public class MainActivity_ViewBinding implements IBinder<com.example.aptdemo.MainActivity> {
// @Override
// public void bind(com.example.aptdemo.MainActivity target) {
// target.textView = (android.widget.TextView) target.findViewById(2131165359);
//
// }
// }
if (map.size() > 0) {
Writer writer = null;
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String activityName = iterator.next();
List<VariableElement> variableElements = map.get(activityName);
//得到包名
TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
try {
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
writer = sourceFile.openWriter();
// package com.example.aptdemo;
writer.write("// source code is generated by CusButterKnife\n\n");
writer.write("package " + packageName + ";\n");
// import com.example.aptdemo.IBinder;
writer.write("import " + packageName + ".IBinder;\n");
// public class MainActivity_ViewBinding implements IBinder<
// com.example.aptdemo.MainActivity>{
writer.write("public class " + activityName + "_ViewBinding implements IBinder<" +
packageName + "." + activityName + ">{\n");
// public void bind(com.example.aptdemo.MainActivity target) {
writer.write(" @Override\n" +
" public void bind(" + packageName + "." + activityName + " target){\n");
//target.tvText=(android.widget.TextView)target.findViewById(2131165325);
for (VariableElement variableElement : variableElements) {
//得到名字
String variableName = variableElement.getSimpleName().toString();
//得到ID
int id = variableElement.getAnnotation(BindView.class).value();
//得到类型
TypeMirror typeMirror = variableElement.asType();
writer.write("\ttarget." + variableName + "=(" + typeMirror + ")target.findViewById(" + id + ");\n");
}
writer.write("\t}\n}");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
return false;
}
}
到此,注解处理的逻辑就完成了,接下来,在app module 中写上一些工具类
- 首先定义一个接口,用于绑定有添加对应注解
@BindView
的界面,把当前界面 与 apt生成的代码 关联起来,apt自动生成的代码类是实现该接口的
public interface IBinder<T> {
void bind(T target);
}
2.顶一个工具类,用来传入当前界面
public class CusButterKnife {
public static void bind(@NonNull Activity activity) {
String name = activity.getClass().getName() + "_ViewBinding";
try {
Class<?> aClass = Class.forName(name);
IBinder iBinder = (IBinder) aClass.newInstance();
iBinder.bind(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.看下Activity的使用
@BindView(R.id.tvText)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CusButterKnife.bind(this);
textView.setText("123456");
}
4.在 annotation_compiler代码写完之后,build 一下工程,就能找到我们自己实现的需要生成的代码:
// source code is generated by CusButterKnife
package com.example.aptdemo;
import com.example.aptdemo.IBinder;
public class MainActivity_ViewBinding implements IBinder<com.example.aptdemo.MainActivity> {
@Override
public void bind(com.example.aptdemo.MainActivity target) {
target.textView = (android.widget.TextView) target.findViewById(2131165359);
}
}
至此,这个注解+APT 实现自动找id 的功能就完成了
2.4 注解+反射+动态代理
比如于xutils 这个框架的核心处理逻辑就是通过这个来实现的
也通过一个demo实现一个功能:给方法添加注解,自动实现view 的点击事件
众所周知,常规实现一个按钮的点击实现,是需要这样的代码:
Button btn1 = findViewById(R.id.btn1);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
这里需要实现的功能是 写一个方法,添加一个注解(点击事件定义注解为@OnClick
),直接实现点击功能,无需再使用上述的常规写法注册点击事件,代码如下:
@OnClick(R.id.btn1)
public void abc(@NonNull View view) {
Toast.makeText(this, "触发点击", Toast.LENGTH_SHORT).show();
}
具体实现如下
- 第一步,把注册点击事件这段代码划分成三个部分,如下图所示:
- 通过注解 @OnClick(R.id.btn1),就要能拿到对应的这三部分用于实现点击事件,那就创建一个注解类,用来存放这三个部分
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
//1.订阅关系 setOnClickListener
String listenerSetter();
//2.事件本身 new View.OnClickListener()
Class<?> listenerType();
//3.事件处理程序 onClick方法
String callbackMethod();
}
- 把这个注解 标记到我们的功能注解
@OnClick
上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnClickListener"
,listenerType = View.OnClickListener.class
,callbackMethod = "onClick")
public @interface OnClick {
int[] value() default -1;
}
- 接下来,写一个工具类,通过反射的语法,把
@OnCLick
注解的具体逻辑给它实现
public class InjectUtils {
public static void inject(Activity context) {
inJeckEvent(context);
}
private static void inJeckEvent(Activity context) {
Class<?> clazz = context.getClass();
// 反射获取传入类所有的方法
Method[] methods = clazz.getDeclaredMethods();
//遍历,筛选出有所需注解的方法
for (Method method : methods) {
//拿到有注解的方法的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
Class<?> annotationClass = annotation.annotationType();
//遍历方法注解,判断是否有 EventBase 注解
EventBase eventBase = annotationClass.getAnnotation(EventBase.class);
//判断是不是事件处理程序 onClick onLongClink
if (eventBase == null) {
continue;
}
//1.setOnClickListener 订阅关系
// String listenerSetter();
String listenerSetter = eventBase.listenerSetter();
//2.new View.OnClickListener() 事件本身
// Class<?> listenerType();
Class<?> listenerType = eventBase.listenerType();
//3.事件处理程序
// String callbackMethod();
String callBackMethod = eventBase.callbackMethod();
//得到3要素之后,就可以执行代码了
Method valueMethod = null;
try {
//拿到注解 value (传入的id)
valueMethod = annotationClass.getDeclaredMethod("value");
int[] viewId = (int[]) valueMethod.invoke(annotation);
for (int id : viewId) {
//为了得到Button对象,使用findViewById
Method findViewById = clazz.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(context, id);
if (view == null) {
continue;
}
//context===activity click=method
ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(context, method);
//new View.OnClickListener()
Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, listenerInvocationHandler);
// view.setOnClickListener(new View.OnClickListener())
Method onClickMethod = view.getClass().getMethod(listenerSetter, listenerType);
onClickMethod.invoke(view, proxy);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
这一段代码,是使用了动态代理,把组件原本onclick代理到 有对应注解的方法上,看一下相关实现代码
/**
* 这个类用来代理 事件本身 (new View.OnClickListener())
* 并执行这个对象身上的onClick方法
*/
public class ListenerInvocationHandler implements InvocationHandler {
//需要在onClick中执行activity.click();
private Activity activity;
private Method activityMethod;
public ListenerInvocationHandler(Activity activity, Method activityMethod) {
this.activity = activity;
this.activityMethod = activityMethod;
}
/**
* 就表示onClick的执行
* 程序执行onClick方法,就会转到这里来
* 因为框架中不直接执行onClick
* 所以在框架中必然有个地方让invoke和onClick关联上
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在这里去调用被注解了的click();
return activityMethod.invoke(activity,args);
}
}
- 最后在 activity 中调用
InjectUtils.inject
,就完成了,看下运行
到此 ,APT+注解+动态代理 的常见使用方式就讲完了
补充
这里只是实现了 点击事件,那比如要实现注解实现长按事件呢?
先前已经定了一个注解用来存放 一个事件注册的及要素嘛,这个时候,要在进行扩展就比较容易了,长按事件的注解实现,就只需要定义一个这样的注解类就行了:
@EventBase(listenerSetter = "setOnLongClickListener"
,listenerType = View.OnLongClickListener.class
,callbackMethod = "onLongClick")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnLongClick {
int[] value() default -1;
}
是不是很眼熟,跟上面 OnCLick 注解的定义止呕三要素的不同,传不同的参数到 @EventBase
注解里面就行了,这个就是一个 注解的多态 这个概念的实际应用了。
在想要扩展Android 中的其他各种出入事件监听,就一次类推,定义对应的注解就行了;
好了,结束
标签:场景,void,class,new,Override,注解,Android,public From: https://www.cnblogs.com/qiyuexiaxun/p/16983603.html