本篇學(xué)習的目的是擼一個編譯時注解框架:
1、本篇只是學(xué)習小結(jié),需要詳細的講解可參考:
- Android注解快速入門和實用解析:http://www.itdecent.cn/p/9ca78aa4ab4d
- Android 如何編寫基于編譯時注解的項目:http://blog.csdn.net/lmj623565791/article/details/51931859
2、基礎(chǔ)小結(jié)
-
元注解:
元注解是由java提供的基礎(chǔ)注解,負責注解其它注解。
元注解有:@Retention:注解保留的生命周期 @Target:注解對象的作用范圍。 @Inherited:@Inherited標明所修飾的注解,在所作用的類上,是否可以被繼承。 @Documented:如其名,javadoc的工具文檔化,一般不關(guān)心。 -
@Retention:
Retention說標明了注解被生命周期,對應(yīng)RetentionPolicy的枚舉,表示注解在何時生效:SOURCE:只在源碼中有效,編譯時拋棄,如@Override。 CLASS:編譯class文件時生效。 RUNTIME:運行時才生效。 -
@Target:
Target標明了注解的適用范圍,對應(yīng)ElementType枚舉,明確了注解的有效范圍。TYPE:類、接口、枚舉、注解類型。 FIELD:類成員(構(gòu)造方法、方法、成員變量)。 METHOD:方法。 PARAMETER:參數(shù)。 CONSTRUCTOR:構(gòu)造器。 LOCAL_VARIABLE:局部變量。 ANNOTATION_TYPE:注解。 PACKAGE:包聲明。 TYPE_PARAMETER:類型參數(shù)。 TYPE_USE:類型使用聲明。 @Inherited
注解所作用的類,在繼承時默認無法繼承父類的注解。除非注解聲明了 @Inherited。同時Inherited聲明出來的注解,只對類有效,對方法/屬性無效。
3、先擼一個運行時注解,直接上代碼:
注解類:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value() default -1;
}
運用例子:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
private TextView mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getAllAnnotationView();
mView.setText("注解成功");
}
/**
* 解析注解,獲取控件
*/
private void getAllAnnotationView() {
//獲得成員變量
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
try {
//判斷注解
if (field.getAnnotations() != null) {
//確定注解類型
if (field.isAnnotationPresent(BindView.class)) {
//允許修改反射屬性
field.setAccessible(true);
BindView getViewTo = field.getAnnotation(BindView.class);
//findViewById將注解的id,找到View注入成員變量中
field.set(this, findViewById(getViewTo.value()));
}
}
} catch (Exception e) {
}
}
}
}
4、編譯時注解:
運行時注解大多使用反射,這會影響效率,BufferKnife不會這樣做,BufferKnife使用的是編譯時注解,在編譯時生成對應(yīng)的java代碼,實現(xiàn)注入。下面就一步步擼一個編譯時注解:
-
首先,準備三個Module:
bindView-annotation:用于存放注解等,Java模塊(創(chuàng)建時選javalib) bindView-compiler:用于編寫注解處理器,Java模塊 bindView-annotation:用于給用戶提供使用的API,Android模塊 -
注解模塊的實現(xiàn)(bindView-annotation):
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.CLASS) public @interface BindView { int value() default -1; } 注解處理器的實現(xiàn)(bindView-compiler):
先添加依賴:compile 'com.google.auto.service:auto-service:1.0-rc2'-
然后創(chuàng)建類繼承AbstractProcessor,找不到這個類的看Module是不是javalib:
public class BindViewProcessor extends AbstractProcessor { private Filer mFileUtils; private Elements mElementUtils; private Messager mMessager; /** * 初始化一些工具。Elements幾個子類:VariableElement //一般代表成員變量 * ExecutableElement //一般代表類中的方法 * TypeElement //一般代表代表類 * PackageElement //一般代表Package * 固定的寫法 */ @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mFileUtils = processingEnvironment.getFiler(); //跟文件相關(guān)的輔助類,生成JavaSourceCode。 mElementUtils = processingEnvironment.getElementUtils(); //跟元素相關(guān)的輔助類,幫助我們?nèi)カ@取一些元素相關(guān)的信息。 mMessager = processingEnvironment.getMessager(); //跟日志相關(guān)的輔助類。 } /** * 返回注解類型, 固定的寫法 */ @Override public Set<String> getSupportedAnnotationTypes(){ Set<String> annotationTypes = new LinkedHashSet<String>(); annotationTypes.add(BindView.class.getCanonicalName()); return annotationTypes; } /** * 返回支持的源碼版本,固定的寫法 */ @Override public SourceVersion getSupportedSourceVersion(){ return SourceVersion.latestSupported(); } /** * 核心的方法 */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } } -
process先不寫,先寫一個代理類ProxyInfo:
/** * 用來保存每個類的所有注解信息 */ public class ProxyInfo { private String packageName; //包名 private String proxyClassName; //代理類名 private TypeElement typeElement; //代表被代理的類 public Map<Integer, VariableElement> injectVariables = new HashMap<>(); //存被注解的成員變量 public static final String PROXY = "OnBindView"; //接口名,對應(yīng)API模塊 public ProxyInfo(Elements elementUtils, TypeElement typeElement) { this.typeElement = typeElement; PackageElement packageElement = elementUtils.getPackageOf(typeElement); //通過元素工具從類元素中拿到包元素 String packageName = packageElement.getQualifiedName().toString(); //通過包元素拿到包名 int packageLen = packageName.length()+1; //包名長度加1 String className //getQualifiedName()拿到的是com.test.MainActivity,這一步就拿到MainActivity,后面的replace是內(nèi)部類的情況 = typeElement.getQualifiedName().toString().substring(packageLen).replace(".","$"); this.packageName = packageName; this.proxyClassName = className + "$$" + PROXY; //代理類名 } /** * 寫代理類 * @return */ public String generateJavaCode() { StringBuilder builder = new StringBuilder(); builder.append("http:// Generated code. Do not modify!\n"); builder.append("package ").append(packageName).append(";\n\n"); builder.append("import com.bindview.*;\n"); builder.append('\n'); builder.append("public class ").append(proxyClassName).append(" implements " + ProxyInfo.PROXY + "<" + typeElement.getQualifiedName() + ">"); builder.append(" {\n"); generateMethods(builder); builder.append('\n'); builder.append("}\n"); return builder.toString(); } /** * 寫findviewbyID * @return */ private void generateMethods(StringBuilder builder) { builder.append("@Override\n "); builder.append("public void inject(" + typeElement.getQualifiedName() + " host, Object source ) {\n"); for (int id : injectVariables.keySet()) { VariableElement element = injectVariables.get(id); String name = element.getSimpleName().toString(); String type = element.asType().toString(); builder.append(" if(source instanceof android.app.Activity){\n"); builder.append("host." + name).append(" = "); builder.append("(" + type + ")(((android.app.Activity)source).findViewById( " + id + "));\n"); builder.append("\n}else{\n"); builder.append("host." + name).append(" = "); builder.append("(" + type + ")(((android.view.View)source).findViewById( " + id + "));\n"); builder.append("\n};"); } builder.append(" }\n"); } public String getProxyClassFullName() { return packageName + "." + proxyClassName; } public TypeElement getTypeElement() { return typeElement; } } -
現(xiàn)在看process的代碼:
private Map<String, ProxyInfo> mProxyMap = new HashMap<String, ProxyInfo>(); @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //------------------------收集信息---------------------------- mProxyMap.clear(); Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class); //收集被注解的元素 for (Element element : elements) { checkAnnotationValid(element, BindView.class); //檢查element類型,比如只能修飾成員變量 VariableElement variableElement = (VariableElement) element; //class type TypeElement classElement = (TypeElement) variableElement.getEnclosingElement(); //full class name String fqClassName = classElement.getQualifiedName().toString(); ProxyInfo proxyInfo = mProxyMap.get(fqClassName); if (proxyInfo == null) { proxyInfo = new ProxyInfo(mElementUtils, classElement); mProxyMap.put(fqClassName, proxyInfo); } BindView bindAnnotation = variableElement.getAnnotation(BindView.class); int id = bindAnnotation.value(); proxyInfo.injectVariables.put(id, variableElement); } //-------------寫代理類-------------------- for (String key : mProxyMap.keySet()) { ProxyInfo proxyInfo = mProxyMap.get(key); try { JavaFileObject jfo = processingEnv.getFiler().createSourceFile( proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement()); Writer writer = jfo.openWriter(); writer.write(proxyInfo.generateJavaCode()); writer.flush(); writer.close(); } catch (IOException e) { error(proxyInfo.getTypeElement(), "Unable to write injector for type %s: %s", proxyInfo.getTypeElement(), e.getMessage()); } } return true; } private boolean checkAnnotationValid(Element annotatedElement, Class clazz) { if (annotatedElement.getKind() != ElementKind.FIELD) { error(annotatedElement, "%s must be declared on field.", clazz.getSimpleName()); return false; } if (annotatedElement.getModifiers().contains(Modifier.PRIVATE)) { error(annotatedElement, "%s() must can not be private.", annotatedElement.getSimpleName()); return false; } return true; } private void error(Element element, String message, Object... args) { if (args.length > 0) { message = String.format(message, args); } processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message, element); } -
compiler模塊寫完,輪到API模塊了:
public interface OnBindView<T> { void inject(T t, Object source); } public class BindView { private static final String SUFFIX = "$$OnBindView"; public static void injectView(Activity activity) { OnBindView proxyActivity = findProxyActivity(activity); proxyActivity.inject(activity, activity); } public static void injectView(Object object, View view) { OnBindView proxyActivity = findProxyActivity(object); proxyActivity.inject(object, view); } private static OnBindView findProxyActivity(Object activity) { try { Class clazz = activity.getClass(); Class injectorClazz = Class.forName(clazz.getName() + SUFFIX); return (OnBindView) injectorClazz.newInstance(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } throw new RuntimeException(String.format("can not find %s , something when compiler.", activity.getClass().getSimpleName() + SUFFIX)); } } -
最后,使用:
public class MainActivity extends AppCompatActivity { @Bind(R.id.id_textview) TextView mTv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fragment); ViewInjector.injectView(this); }