今天我们先给出 demo 的代码,随后会就 APT 技术进行详细详情
目标通过自己视图绑定功能来理解如何 APT 帮助我们在编译期间自动生成代码。在网上收集少量资料自己实现一下。
创立一个空 Android 项目,接下我们增加模块到项目,每一个模块在 APT 中实现不同功能。
创立 zi-annotation 注意这是一个 (java 模块),在这个模块中,可以创立注解 ZiViewBind
创立 zi-api (android 模块)提供客户可以调用 api ,这里实现 view 的绑定。
No. | 模块名 | 模块类型 | 说明 |
---|---|---|---|
1 | zi-annotation | java 模块 | 在这个模块中,可以创立注解 |
2 | zi-api | Android 模块 | 提供客户可以调用 api |
3 | zi-compiler | java 模块 | 编写注解解决 |
Android Studio 在当我们创立好模块自动在 setting 文件中写入了这些模块
rootProject.name='zi application'include ':app'include ':zi-annotation'include ':zi-api'include ':zi-compiler'
apply plugin: 'java-library'dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.google.auto.service:auto-service:1.0-rc2' implementation 'com.squareup:javapoet:1.10.0' implementation project(':zi-annotation')}sourceCompatibility = "7"targetCompatibility = "7"
zi-compiler
依赖于 zi-annotation
模块implementation 'com.google.auto.service:auto-service:1.0-rc2'implementation 'com.squareup:javapoet:1.10.0'
在主项目中增加刚刚创立好的几个模块,值得注意 zi-compiler 为 annotationProcessor
dependencies { ... implementation project(':zi-annotation') implementation project(':zi-api') annotationProcessor project(':zi-compiler')}
到此我们已经完成模块创立、增加以及他们之间依赖关系,剩下的工作就是为这些模块增加代码。
@Retention(RetentionPolicy.CLASS)@Target(ElementType.FIELD)public @interface ZiBindView {}
@Target
说明了Annotation所修饰的对象范围
值 | 说明 |
---|---|
CONSTRUCTOR | 用于形容构造器 |
FIELD | 用于形容域 |
LOCAL_VARIABLE | 用于形容局部变量 |
METHOD | 用于形容方法 |
PACKAGE | 用于形容包 |
PARAMETER | 用于形容参数 |
TYPE | 用于形容类、接口(包括注解类型) 或者enum公告 |
创立 ZiBindViewProcessor 和 ClassCreatorProxy
在代码中不涉及的注解这里就不解释了
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另少量却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另少量在class被装载时将被读取(请注意并不影响class的执行,由于Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
注解解决器需要继承于 AbstractProcessor ,其中部分代码写法基本是固定的。
public class ZiBindViewProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; }}
getSupportedAnnotationTypes()
返回支持的注解类型getSupportedSourceVersion()
返回支持的源码版本
@Override public Set<String> getSupportedAnnotationTypes() { HashSet<String> supportTypes = new LinkedHashSet<>(); //获取类名 supportTypes.add(ZiBindView.class.getCanonicalName()); return supportTypes; }
这里,getName()返回的是虚拟机里面的class的表示,而getCanonicalName()返回的是更容易了解的表示。其实对于大部分class来说这两个方法没有什么不同的。但是对于array或者内部类来说是有区别的。
另外,类加载(虚拟机加载)的时候需要类的名字是getName。
@Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }
谓信息收集,就是根据我们公告,得到对应 Element,而后注解进行收集所需的信息,这些信息用于后期生产对象
针对每一个都会生产一个注解类
public interface ProcessingEnvironment { Elements getElementUtils(); Types getTypeUtils(); Filer getFiler(); Locale getLocale(); Messager getMessager(); Map<String, String> getOptions(); SourceVersion getSourceVersion(); }
@SupportedSourceVersion(SourceVersion.RELEASE_7)@SupportedAnnotationTypes({"zidea.example.com.zi_compiler.ZiBindViewProcessor"})@AutoService(Processor.class)public class ZiBindViewProcessor extends AbstractProcessor { //跟日志相关的辅助类 private Messager mMessager; //跟元素相关的辅助类,帮助我们去获取少量元素相关的信息。 private Elements mElementUtils; private Map<String,ClassCreatorProxy> mProxyMap = new HashMap<>(); //利用 ProcessingEnvironment 对象获取 Elements @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mMessager = processingEnvironment.getMessager(); mElementUtils = processingEnvironment.getElementUtils(); } @Override public Set<String> getSupportedAnnotationTypes() { HashSet<String> supportTypes = new LinkedHashSet<>(); //获取类名 supportTypes.add(ZiBindView.class.getCanonicalName()); return supportTypes; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } // 收集信息 // 所谓信息收集,就是根据我们公告,得到对应 Element,而后注解进行收集所需的信息,这些信息用于后期生产对象 // 生产代理商类(在编译时,将文本生产为类的生产代理商类 ClassCreatorProxy,针对每一个都会生产一个注解类 @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //输出信息 mMessager.printMessage(Diagnostic.Kind.NOTE,"processing..."); //根据注解获取所需信息 //用来获取,注解所修饰的Element对象,getElementsAnnotatedWith 用于获取注解 ZiBindView 的 Element 对象 Set<? extends Element> elements =roundEnvironment.getElementsAnnotatedWith(ZiBindView.class); //遍历说有在类中,增加了 ZiBindView 类 for (Element element : elements){ VariableElement variableElement = (VariableElement) element; TypeElement classElement = (TypeElement) variableElement.getEnclosingElement(); String fullClassName = classElement.getQualifiedName().toString(); ClassCreatorProxy proxy = mProxyMap.get(fullClassName); if(proxy == null){ proxy = new ClassCreatorProxy(mElementUtils,classElement); mProxyMap.put(fullClassName,proxy); } ZiBindView bindAnnotation = variableElement.getAnnotation(ZiBindView.class); int id = bindAnnotation.value(); proxy.putElement(id,variableElement); } for(String key:mProxyMap.keySet()){ ClassCreatorProxy proxyInfo = mProxyMap.get(key); JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCode2()).build(); try { javaFile.writeTo(processingEnv.getFiler()); }catch (IOException e){ e.printStackTrace(); } } mMessager.printMessage(Diagnostic.Kind.NOTE,"process finish ..."); return true; }}
for(String key:mProxyMap.keySet()){ ClassCreatorProxy proxyInfo = mProxyMap.get(key); JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCode2()).build(); try { javaFile.writeTo(processingEnv.getFiler()); }catch (IOException e){ e.printStackTrace(); } }
public class ClassCreatorProxy { //绑定的类名称 private String mBindingClassName; //绑定的包名称 private String mPackageName; private TypeElement mTypeElement; private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>(); public ClassCreatorProxy(Elements elementUtils, TypeElement classElement){ this.mTypeElement = classElement; PackageElement packageElement = elementUtils.getPackageOf(mTypeElement); String packageName = packageElement.getQualifiedName().toString(); String className = mTypeElement.getSimpleName().toString(); this.mPackageName = packageName; this.mBindingClassName = className + "_ViewBinding"; } public void putElement(int id, VariableElement element){ mVariableElementMap.put(id,element); } public String generateJavaCode(){ StringBuilder builder = new StringBuilder(); builder.append("package ").append(mPackageName).append(";\n\n"); builder.append("import com.example.gavin.apt_library.*;\n"); builder.append('\n'); builder.append("public class ").append(mBindingClassName); builder.append(" {\n"); generateMethods(builder); builder.append('\n'); builder.append("}\n"); return builder.toString(); } private void generateMethods(StringBuilder builder) { builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host ) {\n"); for (int id : mVariableElementMap.keySet()) { VariableElement element = mVariableElementMap.get(id); String name = element.getSimpleName().toString(); String type = element.asType().toString(); builder.append("host." + name).append(" = "); builder.append("(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));\n"); } builder.append(" }\n"); } public String getProxyClassFullName() { return mPackageName + "." + mBindingClassName; } public TypeElement getTypeElement() { return mTypeElement; }//JavaPoet是提供于自动生成java文件的构建工具类框架,使用该框架可以方便根据我们所注解的内容在编译时进行代码构建。 public TypeSpec generateJavaCode2() { TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName) .addModifiers(Modifier.PUBLIC) .addMethod(generateMethods2()) .build(); return bindingClass; } private MethodSpec generateMethods2() { ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString()); MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind") .addModifiers(Modifier.PUBLIC) .returns(void.class) .addParameter(host, "host"); for (int id : mVariableElementMap.keySet()) { VariableElement element = mVariableElementMap.get(id); String name = element.getSimpleName().toString(); String type = element.asType().toString(); methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));"); } return methodBuilder.build(); } public String getPackageName(){ return mPackageName; }}
public class ZiBindViewTools { public static void bind(Activity activity) { Class clazz = activity.getClass(); try { Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding"); Method method = bindViewClass.getMethod("bind", activity.getClass()); method.invoke(bindViewClass.newInstance(), activity); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }}
Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");Method method = bindViewClass.getMethod("bind", activity.getClass());method.invoke(bindViewClass.newInstance(), activity);
我们通过
### 使用 Api 我们已经完成 APT 部分代码,那么如何使用我们创立好 ZiBindView 呢?具体使用方法和 butterKnife 基本一样。```javapublic class MainActivity extends AppCompatActivity { @ZiBindView(value = R.id.main_activity_textView) TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ZiBindViewTools.bind(this); textView.setText("Zidea"); }}
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
但是当我们运行时候可能还无法生存代码,这是由于使用 gradle 版本过高,需要我们自己手动创立,
zi-compiler
目录下创立如图结构resources/META-INF/services
文件目录file
就可,在 file中增加如下内容九千兆hz源码WAP自适应+APP+激活+抢单+排单+短信匹配完美经营版huzhu源码
企业宣传站源码独家修复和二开版本
仿《东方头条网》源码PC版+手机版带采集
幼儿早教机构教育培训机构网站源码【排名良好】幼儿教育行业站(带手机版数据同步)
最新PHP牙科口腔医院门诊网站代码+PC端+手机WAP+微信+繁简+中英文双语多合一建站源码
【带音效与演示一致】天鹅城农场理财游戏|仿皮皮果乐园游戏|农场开心游戏
红色大型装修设计家装软装别墅装修网站织梦模板
Thinkphp小说自动采集响应式粉色浪漫色调书城
咖啡奶茶类企业公司网站源码 食品餐饮店织梦模板(含手机站)
莫奈优惠券小程序V3.1.7