个人博客
http://www.milovetingting.cn
使用APT实现Android中View的注入 前言 APT
是Annotation Processing Tool
的简写,通过在Java编译
时期,处理注解
,生成代码。APT在ButterKnife、Dagger2
等框架中都有应用。下面通过使用APT,实现一个类似ButterKnife的简单的View注入的框架。(参考Jett
老师的课程)
ButterKnife的实现原理 既然准备实现类似ButterKnife的框架,那么我们就需要了解ButterKnife的实现原理。
ButterKnife的使用是从ButterKnife.bind()
开始的:
1 2 3 4 5 @NonNull @UiThread public static Unbinder bind (@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return bind(target, sourceView); }
可以看到,bind
方法中又调用了内部的bind
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @NonNull @UiThread public 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; } 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
方法获取到构造函数,然后返回具体的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Nullable @CheckResult @UiThread private 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" ); 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()); 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @UiThread public MainActivity_ViewBinding (MainActivity target, View source) { this .target = target; target.tv = Utils.findRequiredViewAsType(source, R.id.tv, "field 'tv'" , TextView.class); } 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); } 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注入框架 了解了原理后,就可以自己来实现简单的View注入框架了。
新建annotation模块 新建Java Library
类型的Module
,名称为annotation,用来定义注解
新建annotation_compiler模块 然后,同样的方法新建名为annotation_compiler的模块,用来处理注解
新建Binder模块 我们还需要新建一个名为Binder的模块,用来供用户直接调用
添加依赖 新建这三个Modeule后,需要为相应的Module添加依赖
。app模块需要依赖上面的三个模块,annotation_compiler需要依赖annotation。
编写annotation模块代码 在annotation模块下新建BindView
注解
1 2 3 4 5 @Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface BindView { int value () ; }
编写annotation_compiler模块代码 要使用APT,需要添加相关依赖,在annotation_compiler
模块下的build.gradle
文件中编辑
1 2 3 4 5 dependencies { 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
1 2 3 4 @AutoService(Processor.class) public class AnnotationsCompiler extends AbstractProcessor { }
需要重写三个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override public SourceVersion getSupportedSourceVersion () { return SourceVersion.latestSupported(); } @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
方法,主要的逻辑都在这里实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 @Override public boolean process (Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { Set<? extends Element > elements = roundEnvironment.getElementsAnnotatedWith(BindView.class); 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(); 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
类
1 2 3 4 5 6 7 8 9 10 public interface IBinder <T> { void bind (T t) ; }
新建ViewBinder类,这个类是直接供用户调用的
1 2 3 4 5 6 7 8 9 10 11 12 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中使用
1 2 3 4 5 6 7 8 9 10 11 @BindView(R.id.tv) TextView tv; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewBinder.bind(this ); tv.setText("Hi,ViewBinder!" ); }
运行应用后,可以看到已经更改了TextView的显示,从而证明我们自己定义的ViewBinder是可以正常运行的。
结束 使用APT实现Android中View的注入,具体步骤就是上面描述的。当然,这个只是一个简单的示例,如果要开发出完善的框架,还有很多需要注意和优化的,这里只是记录开发的一般流程,以便后面需要时查找资料。
源码 源码地址:https://github.com/milovetingting/Samples/tree/master/ViewBinder