Android编译时注解需要掌握的知识点:
1、编译时 与运行时的区别
2、注解
3、反射
。。。。。
一、注解
说的简单点就是Android代码中常见到的:@Override 这一类是不是很简单哈哈,深入了解可以查看其它资料
二、开干
(一)搞一个类似butterknife的简单的项目,帮助理解编译时注解,整个工程目录差不多是这样:
bind-annotation: 注解相关java库
bind-compiler:用来解析bind-annotation中自定义的注解,java库
bind-api:主要是app项目中调用接口 ,android库
app : 简单测试
对了,就是这么简单
(1)bind-annotation: 注解相关java库
只有一个注解文件,灰常简单,直接上代码,关于
package com.apacherio.jondy.rio_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by Administrator on 2018/2/11 0011.
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface RioBind {
int value();
}
(2)在app中试一哈上面写的注解
代码中的@RioBind 就是上面自定义的注解
package com.apacherio.jondy.annotationdemo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Button;
import android.widget.TextView;
import com.apacherio.jondy.bind_api.RioBinder;
import com.apacherio.jondy.rio_annotation.RioBind;
public class MainActivity extends AppCompatActivity {
@RioBind(R.id.tv) TextView tv;
@RioBind(R.id.btn) Button click;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RioBinder.bind(this);
tv.setText("静态编译模拟Butterknife绑定成功!");
}
}
(3)bind-compiler:用来解析bind-annotation中自定义的注解
老规矩,直接上代码:基本都有注释。
重点关注:AbstractProcessor 、@AutoService(Processor.class)
这个类的主要作用是啥?
1、编译时,提取出包含有@RioBind的类,比如MainAcivity,不止一个类
2、在类中提取出使用@RioBind的field,比如 TextView Button,当然一个类中有不少于一个。这些field按类分在一起。
3、构建一个类,什么类呢?看下图 以$ViewBinder结尾的类,这个后缀可以自由发挥。
这个类的作用就是帮助我们实现findViewById()的功能:
TextView textView = (TextView)this. findViewById(R.id.tv);
这个需要field的类别 TextView ,Id ,Activity(Fragment)对象,这些都会在下面的代码中解析得到
@AutoService(Processor.class)
public class RioBindtProcessor extends AbstractProcessor {
private Filer mFiler;
private Messager mMessager;
private Elements mElementsUtil;
private static final String SUFFIX ="ViewBinder";
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler();
mElementsUtil = processingEnv.getElementUtils();
mMessager = processingEnv.getMessager();
}
/*
* Element 的子类有以下4种
* VariableElement //一般代表成员变量
* ExecutableElement //一般代表类中的方法
* TypeElement //一般代表代表类
* PackageElement //一般代表Package
* */
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elementSet = roundEnv.getElementsAnnotatedWith(RioBind.class);
// 收集信息,分类
Map<String, List<VariableElement>> cacheMap = new HashMap<>();
for (Element element : elementSet) {
VariableElement variableElement = (VariableElement) element;
String className = getClassName(variableElement);
//
List<VariableElement> filedList = cacheMap.get(className);
if (null == filedList) {
filedList = new ArrayList<>();
cacheMap.put(className, filedList);
}
filedList.add(variableElement);
}
// 产生java文件
Iterator<String> iterator = cacheMap.keySet().iterator();
while (iterator.hasNext()) {
// 准备好生成java文件产生的信息
// 获取class的名字
String className = iterator.next();
// 获取class中的所有成员属性
List<VariableElement> cacheElements = cacheMap.get(className);
// 获取包名
String packageName = getPackageName(cacheElements.get(0));
// 获取最后生成文件的文件名:className+"$"+SUFFIX
String bindViewClass = className+"$"+SUFFIX;
// 生成额外的文件,x写文件流
Writer writer =null;
try {
JavaFileObject javaFileObject = mFiler.createSourceFile(bindViewClass);
// 拼接字符串
writer = javaFileObject.openWriter();
// 获取简短新代理类名称
String sampleClass =cacheElements.get(0).getEnclosingElement().getSimpleName().toString();
String sampleBindViewClass =sampleClass+"$"+SUFFIX;
writer.write(generateJavaCode(packageName,sampleBindViewClass,className, cacheElements));
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
public String generateJavaCode(String mPackageName,String mProxyClassName,String className, List<VariableElement> cacheElements ){
StringBuilder builder = new StringBuilder();
builder.append("package " + mPackageName).append(";\n\n");
builder.append("import com.apacherio.jondy.bind_api.*;\n");
// builder.append("import android.view.View;\n");
builder.append("public class ").append(mProxyClassName).append(" implements " + SUFFIX + "<" + className + ">");
builder.append("\n{\n");
generateMethod(builder,className,cacheElements);
builder.append("\n}\n");
return builder.toString();
}
private void generateMethod(StringBuilder builder,String className, List<VariableElement> cacheElements) {
builder.append("public void bind("+className+" target ){");
builder.append("\n");
// 处理所有的成员属性
for(VariableElement element :cacheElements){
RioBind bindView = element.getAnnotation(RioBind.class);
String filedName = element.getSimpleName().toString();
TypeMirror typeMirror = element.asType();
int id = bindView.value();
builder.append("target.");
builder.append(filedName );
builder.append("= (");
builder.append(typeMirror.toString());
builder.append(")");
builder.append("target.findViewById(");
builder.append(""+id);
builder.append(");");
builder.append("\n");
}
builder.append("}\n");
}
private String getPackageName(VariableElement variableElement) {
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
// String packeName = mElementsUtil.getPackageOf(typeElement).getQualifiedName().toString();
String packeName = processingEnv.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString();
return packeName;
}
private String getClassName(VariableElement element) {
String packageName =getPackageName(element);
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
String className =typeElement.getSimpleName().toString();
return packageName+"."+className;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(RioBind.class.getCanonicalName());
return annotations;
// return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
// return super.getSupportedSourceVersion();
}
(4) api 怎么搞?
直接上代码了!
RioBinder.java —— app调用接口 RioBinder.bind(this);
public class RioBinder {
private static final String SUFFIX ="$ViewBinder";
public static void bind(Activity target){
Class<?> clazz = target.getClass();
String className =clazz.getName()+SUFFIX;
try {
Class<?> binderClass = Class.forName(className);
ViewBinder rioBind = (ViewBinder) binderClass.newInstance();
rioBind.bind(target);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
ViewBinder接口——用于(3)中构建以$ViewBinder结尾的类的继承 ,方便在RioBinder中直接调用该接口中的bind()方法
public interface ViewBinder<T> {
public void bind(T t);
}
(二)依赖关系及其他
app:
implementation project(':bind-api')
implementation project(':bind-annotation')
annotationProcessor project(':bind-compiler')
bind-api:
implementation project(':bind-annotation')
bind-compiler:
apply plugin: 'java-library'
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':bind-annotation')
implementation 'com.google.auto.service:auto-service:1.0-rc2'
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
bind-annotation:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
(最后)点击As的make后,生成的类如下:
有点瑕疵,稍微更改下代码就可以了,省略....另外 ,类的生成这里可以用java类的生成方式来做,不用拼接字符串。
public class MainActivity$ViewBinder implements ViewBinder<com.apacherio.jondy.annotationdemo.MainActivity>
{
public void bind(com.apacherio.jondy.annotationdemo.MainActivity target ){
target.tv= (android.widget.TextView)target.findViewById(2131165308);
target.click= (android.widget.Button)target.findViewById(2131165219);
}
}
效果:
项目demo: