使用APT实现AndroID中VIEw的注入前言个人博客
http://www.milovetingting.cn
APT
是Annotation Processing Tool
的简写,通过在Java编译
时期,处理注解
,生成代码。APT在ButterKnife、Dagger2
等框架中都有应用。下面通过使用APT,实现一个类似ButterKnife的简单的VIEw注入的框架。(参考Jett
老师的课程)
既然准备实现类似ButterKnife的框架,那么我们就需要了解ButterKnife的实现原理。
ButterKnife的使用是从ButterKnife.bind()
开始的:
@NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { VIEw sourceVIEw = target.getwindow().getDecorVIEw(); return bind(target, sourceVIEw); }
可以看到,bind
方法中又调用了内部的bind
方法
@NonNull @UiThreadpublic static Unbinder bind(@NonNull Object target, @NonNull VIEw source) { Class<?> targetClass = target.getClass(); if (deBUG) Log.d(TAG, "Looking up binding for " + targetClass.getname()); //获取构造函数 Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass); if (constructor == null) { return Unbinder.EMPTY; } //noinspection TryWithIDenticalCatches Resolves to API 19+ only type. try { //返回实例 return constructor.newInstance(target, source); } catch (illegalaccessexception e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InstantiationException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException("Unable to create binding instance.", cause); } }
在这个bind方法中,主要通过findBindingConstructorForClass
方法获取到构造函数,然后返回具体的实例。
@Nullable @CheckResult @UiThreadprivate static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { //从缓存中查找 Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls); if (bindingCtor != null || BINDINGS.containsKey(cls)) { if (deBUG) Log.d(TAG, "HIT: Cached in binding map."); return bindingCtor; } //没有缓存过的,那么通过反射来获取 String clsname = cls.getname(); if (clsname.startsWith("androID.") || clsname.startsWith("java.") || clsname.startsWith("androIDx.")) { if (deBUG) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return null; } try { Class<?> bindingClass = cls.getClassLoader().loadClass(clsname + "_VIEwBinding"); //noinspection unchecked bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, VIEw.class); if (deBUG) Log.d(TAG, "HIT: Loaded binding class and constructor."); } catch (ClassNotFoundException e) { if (deBUG) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getname()); //没有Class,则递归调用,从父类中查找 bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); } catch (NoSuchMethodException e) { throw new RuntimeException("Unable to find binding constructor for " + clsname, e); } //放入到缓存中 BINDINGS.put(cls, bindingCtor); return bindingCtor; }}
在findBindingConstructorForClass
方法中,首先查询缓存中是否有需要的构造函数,如果没有,那么会通过反射查找,最终返回了ButterKnife生成的辅助类XXX_VIEwBinding
的构造函数。
Build工程后,在生成的XXX_VIEwBinding的Java文件的构造方法中,可以看到ButterKnife帮我们自己调用了findVIEwByID
//MainActivity_VIEwBinding类中的方法 @UiThread public MainActivity_VIEwBinding(MainActivity target, VIEw source) { this.target = target; target.tv = Utils.findrequiredVIEwAsType(source, R.ID.tv, "fIEld 'tv'", TextVIEw.class); }//Utils类中方法 public static <T> T findrequiredVIEwAsType(VIEw source, @IDRes int ID, String who, Class<T> cls) { VIEw vIEw = findrequiredVIEw(source, ID, who); return castVIEw(vIEw, ID, who, cls); }//Utils类中方法 public static VIEw findrequiredVIEw(VIEw source, @IDRes int ID, String who) { VIEw vIEw = source.findVIEwByID(ID); if (vIEw != null) { return vIEw; } String name = getResourceEntryname(source, ID); throw new IllegalStateException("required vIEw '" + name + "' with ID " + ID + " for " + who + " was not found. If this vIEw is optional add '@Nullable' (fIElds) or '@Optional'" + " (methods) annotation."); }
可以得出结论,ButterKnife就是利用APT
解析注解,在编译时生成了辅助类,用来帮助我们去调用findVIEwByID方法,从而减少手动使用findVIEwByID
了解了原理后,就可以自己来实现简单的VIEw注入框架了。
新建annotation模块新建Java library
类型的Module
,名称为annotation,用来定义注解
然后,同样的方法新建名为annotation_compiler的模块,用来处理注解
我们还需要新建一个名为Binder的模块,用来供用户直接调用
新建这三个Modeule后,需要为相应的Module添加依赖
。app模块需要依赖上面的三个模块,annotation_compiler需要依赖annotation。
在annotation模块下新建BindVIEw
注解
@Target(ElementType.FIELD)@Retention(RetentionPolicy.soURCE)public @interface BindVIEw { int value();}
编写annotation_compiler模块代码要使用APT,需要添加相关依赖,在annotation_compiler
模块下的build.gradle
文件中编辑
dependencIEs { //注册APT功能 annotationProcessor 'com.Google.auto.service:auto-service:1.0-rc4' compileOnly 'com.Google.auto.service:auto-service:1.0-rc4'}
同步后就可以使用APT了。
在annotation_compiler模块下新建AnnotationsCompiler
类,继承自AbstractProcessor
@autoService(Processor.class)public class AnnotationsCompiler extends AbstractProcessor { //...}
需要重写三个方法
/** * 支持的Java版本 * * @return */ @OverrIDe public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } /** * 支持的注解 * * @return */ @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(); }
重写process
方法,主要的逻辑都在这里实现
@OverrIDe public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { Set<? extends Element> elements = roundEnvironment.getElementsAnnotateDWith(BindVIEw.class); //类:TypeElement //方法:ExecutableElement //属性:VariableElement Map<String, List<VariableElement>> map = new HashMap<>(); for (Element element : elements) { VariableElement variableElement = (VariableElement) element; String activityname = variableElement.getEnclosingElement().getSimplename().toString(); List<VariableElement> variableElements = map.get(activityname); if (variableElements == null) { variableElements = new ArrayList<>(); map.put(activityname, variableElements); } variableElements.add(variableElement); } 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 typeElement = (TypeElement) variableElements.get(0).getEnclosingElement(); String packagename = processingEnv.getElementUtils().getPackageOf(typeElement).toString(); try { JavafileObject sourcefile = filer.createSourcefile(packagename + "." + activityname + "_VIEwBinding"); writer = sourcefile.openWriter(); writer.write("package " + packagename + ";\n"); writer.write("import " + PACKAGE_name_BINDER + ".IBinder;\n"); writer.write("public class "+activityname+"_VIEwBinding implements IBinder<"+packagename+"."+activityname+">{\n"); writer.write("@OverrIDe\n"); writer.write("public voID bind("+packagename+"."+activityname+" target){\n"); for(VariableElement variableElement:variableElements) { //获取名字 String variablename = variableElement.getSimplename().toString(); //获取ID int ID = variableElement.getAnnotation(BindVIEw.class).value(); //得到类型 TypeMirror typeMirror = variableElement.asType(); writer.write("target."+variablename+"=("+typeMirror+")target.findVIEwByID("+ID+");\n"); } writer.write("\n}\n}"); } catch (Exception e) { e.printstacktrace(); } finally { if(writer!=null) { try { writer.close(); } catch (IOException e) { e.printstacktrace(); } } } } } return false; }
编写Binder模块代码在Binder模块下新建IBinder
类
public interface IBinder<T> { /** * 绑定activity * * @param t */ voID bind(T t);}
新建VIEwBinder类,这个类是直接供用户调用的
public class VIEwBinder { public static voID bind(Object activity) { String name = activity.getClass().getname() + "_VIEwBinding"; try { Class<?> clazz = Class.forname(name); IBinder binder = (IBinder) clazz.newInstance(); binder.bind(activity); } catch (Exception e) { e.printstacktrace(); } }}
在app模块调用编写好上面的模块后,执行Build-Rebuild Project后,可以看到生成的java类文件
在app模块的MainActivity中使用
@BindVIEw(R.ID.tv)TextVIEw tv;@OverrIDeprotected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); //调用自己定义的VIEwBinder VIEwBinder.bind(this); tv.setText("Hi,VIEwBinder!");}
运行应用后,可以看到已经更改了TextVIEw的显示,从而证明我们自己定义的VIEwBinder是可以正常运行的。
结束使用APT实现AndroID中VIEw的注入,具体步骤就是上面描述的。当然,这个只是一个简单的示例,如果要开发出完善的框架,还有很多需要注意和优化的,这里只是记录开发的一般流程,以便后面需要时查找资料。
源码源码地址:https://github.com/milovetingting/Samples/tree/master/VIEwBinder
总结以上是内存溢出为你收集整理的使用APT实现Android中View的注入全部内容,希望文章能够帮你解决使用APT实现Android中View的注入所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)