目錄
- 【Android】註解框架(一)-- 基礎知識Java 反射
- 【Android】註解框架(二)-- 基礎知識(Java註解)& 執行時註解框架
- 【Android】註解框架(三)-- 編譯時註解,手寫ButterKnife
- 【Android】註解框架(四)-- 一行程式碼注入微信支付
apt介紹
作為Android程式設計師應該絕大部分分人都用過ButterKnife,Retrofit等框架,這些框架只需要在用的時候使用註解,就可以直接使用了,非常方便。並且這些框架並沒有減少效能。
那麼這些框架做了哪些東西呢?
我們以ButterKnife為例:
@BindView(R.id.textview)
TextView textview;
private Unbinder bind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bind = ButterKnife.bind(this);
textview.setText("dfjslafjsalfls");
}
@Override
protected void onDestroy() {
bind.unbind();
super.onDestroy();
}
複製程式碼
上面是我們平常使用的時候所使用的程式碼,這些程式碼到底做了什麼呢?
- 我們給元素寫上註解
- 在編譯器執行的環節,系統收集我們所寫註解的元素,並將這些元素組合成Java程式碼
MainActivity_ViewBinding
類 - 當我們呼叫
ButterKnife.bind
的時候,通過動態注入的方式,將MainActivity
和MainActivity_ViewBinding
關聯起來,並給所有的註解所在的元素賦值。
而這些東西的核心就是在編譯期間生成我們所需要的Java檔案,這樣我們在使用的時候就基本沒有效能的影響。
網上找到的APT的流程圖:
手寫ButterKnife
通過上面的圖我們先生成這些module
app - 主專案 butterknife - android lib annotaion - java lib compiler - java lib
在這些module的配置檔案中配置
-
project的build.gradle
// 如果你的gradle版本是3.0以上則不需要配置 buildscript { repositories { google() jcenter() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:3.0.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } allprojects { repositories { google() jcenter() mavenCentral() } } 複製程式碼
-
butterknife-compiler的build.gradle
新增auto-service和javapoet這兩個lib是用來方便我們生成Java程式碼的。
auto-service: https://github.com/google/auto javapoet: http://blog.csdn.net/crazy1235/article/details/51876192
dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') compile 'com.google.auto.service:auto-service:1.0-rc3' compile 'com.squareup:javapoet:1.8.0' implementation project(':butterknife-annotations') } 複製程式碼
-
app的build.gradle
// gradle版本小於3.0 apply plugin: 'com.neenbedankt.android-apt' compile project(':butterknife') compile project(':butterknife-annotations') apt project(':butterknife-compiler') // gradle版本大於3.0 compile project(':butterknife') compile project(':butterknife-annotations') annotationProcessor project(':butterknife-compiler') 複製程式碼
在annotation中寫上註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
複製程式碼
在compiler中編寫註解生成器
java文件: http://www.yq1012.com/api/index.html-overview-summary.html javax.annotation.processing包 javax.lang.model.element包
-
建立類processor繼承AbstractProcessor
@AutoService(Processor.class) public class ButterKnifeProcessor extends AbstractProcessor { private Elements elementUtils; private Filer filer; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); elementUtils = processingEnvironment.getElementUtils(); filer = processingEnvironment.getFiler(); } // 指定SourceVersion @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } } 複製程式碼
-
指定我們所需要的annotation
// 指定processortype public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); Set<Class<? extends Annotation>> supportedAnnotations = getSupportedAnnotations(); for (Class<? extends Annotation> supportedAnnotation : supportedAnnotations) { types.add(supportedAnnotation.getCanonicalName()); } return types; } private Set<Class<? extends Annotation>> getSupportedAnnotations() { Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>(); annotations.add(BindView.class); return annotations; } 複製程式碼
-
在process中處理annotation
首先將所有獲取到的BindView細分到每個Activity中
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class); // 將獲取到的bindview細分到每個class Map<Element, List<Element>> elementMap = new LinkedHashMap<>(); for (Element element : elements) { // 返回activity Element enclosingElement = element.getEnclosingElement(); List<Element> bindViewElements = elementMap.get(enclosingElement); if (bindViewElements == null) { bindViewElements = new ArrayList<>(); elementMap.put(enclosingElement, bindViewElements); } bindViewElements.add(element); } 複製程式碼
通過遍歷的方式處理每個細分的Activity
// 生成程式碼 for (Map.Entry<Element, List<Element>> entrySet : elementMap.entrySet()) { Element enclosingElement = entrySet.getKey(); List<Element> bindViewElements = entrySet.getValue(); // public final class xxxActivity_ViewBinding implements Unbinder // 獲取activity的類名 String activityClassNameStr = enclosingElement.getSimpleName().toString(); System.out.println("------------->" + activityClassNameStr); ClassName activityClassName = ClassName.bestGuess(activityClassNameStr); ClassName unBinderClassName = ClassName.get("com.fastaoe.butterknife", "Unbinder"); TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityClassNameStr + "_ViewBinding") .addModifiers(Modifier.FINAL, Modifier.PUBLIC) .addSuperinterface(unBinderClassName) // 新增屬性 private MainActivity target; .addField(activityClassName, "target", Modifier.PRIVATE); // unbind() ClassName callSuperClassName = ClassName.get("android.support.annotation", "CallSuper"); MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind") .addAnnotation(Override.class) .addAnnotation(callSuperClassName) .addModifiers(Modifier.PUBLIC, Modifier.FINAL); // 建構函式 MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder() .addParameter(activityClassName, "target") .addModifiers(Modifier.PUBLIC) // this.target = target .addStatement("this.target = target"); for (Element bindViewElement : bindViewElements) { // textview String fieldName = bindViewElement.getSimpleName().toString(); // Utils ClassName utilsClassName = ClassName.get("com.fastaoe.butterknife", "Utils"); // R.id.textview int resourceId = bindViewElement.getAnnotation(BindView.class).value(); // target.textview = Utils.findViewById(target, R.id.textview) constructorMethodBuilder.addStatement("target.$L = $T.findViewById(target, $L)", fieldName, utilsClassName, resourceId); // target.textview = null unbindMethodBuilder.addStatement("target.$L = null", fieldName); } classBuilder.addMethod(unbindMethodBuilder.build()) .addMethod(constructorMethodBuilder.build()); // 獲取包名 String packageName = elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString(); try { JavaFile.builder(packageName, classBuilder.build()) .addFileComment("自己寫的ButterKnife生成的程式碼,不要修改!!!") .build().writeTo(filer); } catch (IOException e) { e.printStackTrace(); } } 複製程式碼
在butterknife中編寫注入程式碼
public class ButterKnife {
public static Unbinder bind(Activity activity) {
try {
Class<? extends Unbinder> bindClazz = (Class<? extends Unbinder>)
Class.forName(activity.getClass().getName() + "_ViewBinding");
// 建構函式
Constructor<? extends Unbinder> bindConstructor = bindClazz.getDeclaredConstructor(activity.getClass());
Unbinder unbinder = bindConstructor.newInstance(activity);
return unbinder;
} catch (Exception e) {
e.printStackTrace();
}
return Unbinder.EMPTY;
}
}
複製程式碼
使用
我們的使用方式就和正真的ButterKnife一樣了:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textview)
TextView textview;
private Unbinder bind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bind = ButterKnife.bind(this);
textview.setText("修改後的文字");
}
@Override
protected void onDestroy() {
bind.unbind();
super.onDestroy();
}
}
複製程式碼