Android 架构师之路 APT (1)
Android-Developers.jpg
今天我们先给出 demo 的代码,随后会就 APT 技术进行详细详情
APT
目标通过自己视图绑定功能来理解如何 APT 帮助我们在编译期间自动生成代码。在网上收集少量资料自己实现一下。
- 目标使用 APT 实现相似 Butter Knife 库提供的通过注解实现视图绑定的功能
最近使用 binding 实现视图绑定功能。有点渐渐远离了 ButterKnife ,不过这个库还是给我留下美好的回忆,简洁明了的 API 让人印象深刻。
创立一个空 Android 项目,接下我们增加模块到项目,每一个模块在 APT 中实现不同功能。
创立模块
创立 zi-annotation 注意这是一个 (java 模块),在这个模块中,可以创立注解 ZiViewBind
apt_create_java_module.JPG
创立 zi-api (android 模块)提供客户可以调用 api ,这里实现 view 的绑定。
apt_create_android_module.JPG
- 创立 zi-compiler(java 模块) 编写注解解决 。
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'
解决依赖关系
- build.gradle(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')}
到此我们已经完成模块创立、增加以及他们之间依赖关系,剩下的工作就是为这些模块增加代码。
project_architeure.JPG
zi-annotation 模块
创立 ZiBindView 注解
@Retention(RetentionPolicy.CLASS)@Target(ElementType.FIELD)public @interface ZiBindView {}
@Target
说明了Annotation所修饰的对象范围
值 | 说明 |
---|---|
CONSTRUCTOR | 用于形容构造器 |
FIELD | 用于形容域 |
LOCAL_VARIABLE | 用于形容局部变量 |
METHOD | 用于形容方法 |
PACKAGE | 用于形容包 |
PARAMETER | 用于形容参数 |
TYPE | 用于形容类、接口(包括注解类型) 或者enum公告 |
zi-compiler 模块
创立 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,而后注解进行收集所需的信息,这些信息用于后期生产对象
生产代理商类(在编译时,将文本生产为类的生产代理商类 ClassCreatorProxy
针对每一个都会生产一个注解类
初始化
public interface ProcessingEnvironment { Elements getElementUtils(); Types getTypeUtils(); Filer getFiler(); Locale getLocale(); Messager getMessager(); Map<String, String> getOptions(); SourceVersion getSourceVersion(); }
ZiBindViewProcessor 完整代码
@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(); } }
- 返回用来创立新的类的 filer
ClassCreatorProxy 完整代码
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; }}
zi-api 模块
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 版本过高,需要我们自己手动创立,
- 首先将目录从 Android 切换到 Project 视图
- 在
zi-compiler
目录下创立如图结构resources/META-INF/services
文件目录 - 而后在目录下增加一个问题
file
就可,在 file中增加如下内容
001.JPG
也就是我们刚刚创立 ZiBindViewProcessor文件包名加文件名
002.JPG
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Android 架构师之路 APT (1)