很早以前,大量書寫findViewById的時候,有人就開始思考,如何將開發者從繁雜的重複的勞動中釋放出來,因而,最初的大神jakewharton編寫了一個ButterKnife框架,使用Java的APT技術,實現這個功能,讓咱們一塊兒來看看這個的實現。java
咱們先認識一下ButterKnife的目錄結構,通常來講APT框架只須要三個包就能夠了,一個註解包(java-library),一個api包(項目的入口),一個compiler包(註解使用)。android
咱們如今來講annotation包的相關東西:ButterKnife經常使用的註解包括BindView這個,是綁定findviewbyid的,咱們從這個入手,咱們看下他的形態:api
@Retention(RUNTIME)
@Target(FIELD) // TYPE: 類或接口; FIELD: 成員變量; METHOD: 方法;
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
複製代碼
其中註解標準的形式:@interface這個就不用多說了。緩存
一、RetentionPolicy.SOURCE:註解只保留在源文件,當Java文件編譯成class文件的時候,註解被遺棄;bash
二、RetentionPolicy.CLASS:註解被保留到class文件,但jvm加載class文件時候被遺棄,這是默認的生命週期;三、RetentionPolicy.RUNTIME:註解不只被保存到class文件中,jvm加載class文件以後,仍然存在; 這3個生命週期分別對應於:Java源文件(.java文件) ---> .class文件 ---> 內存中的字節碼。明確生命週期長度 SOURCE < CLASS < RUNTIME ,因此前者能做用的地方後者必定也能做用。通常若是須要在運行時去動態獲取註解信息,那隻能用 RUNTIME 註解;若是要在編譯時進行一些預處理操做,好比生成一些輔助代碼,就用 CLASS註解;若是隻是作一些檢查性的操做,好比 @Override**和 **@SuppressWarnings,則可選用 SOURCE 註解。數據結構
這裏以前是class,不知道在哪一個版本就改爲了RunTime,可是咱們只在APT階段使用,效果差很少app
接受類型:框架
ButterKnife在初始化的時候使用了緩存技術。源碼在ButterKnife這個文件中。SDK入口方法是:jvm
ButterKnife.bind(this);
複製代碼
最後都會進入bind方法中,咱們看下這裏面幹了什麼:ide
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
return constructor.newInstance(target, source);
}
複製代碼
純看代碼也能看的出來,就是找到class(通常是Activity,在這裏將butterknife與activity綁定),經過這個class的名字找到一個生成的類,最後反射一下建立實例(這是個常規操做,幾乎全部APT的框架都存在這種實現,反射調用經過原className建立的一個文件)。這裏很簡單,咱們直接進findBindingConstructorForClass這裏看這裏作了什麼優化。
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
return bindingCtor;
}
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
複製代碼
能夠看到裏面作了一個優化,就是加入了BINDINGS,他是一個map,保證只加載一次。這裏也很簡單,就是找到一個命名爲classname+__ViewBinding的文件,並加載他的構造函數,且這個文件是繼承Unbinder,咱們看下Unbinder這個是什麼東西:
public interface Unbinder {
@UiThread void unbind();
Unbinder EMPTY = () -> { };
}
複製代碼
這裏面實際上就一個方法,解綁操做,這個就不用說了,確定是爲了優化相關的操做。看到這裏初始化就沒啥了,後面就是APT的部門了,若是咱們也要寫APT這樣的框架,這種流程上的操做仍是十分流水線且簡單的。
APT:咱們只須要繼承AbstractProcessor這個類,並添加@AutoService(Processor.class)這個來實現註解處理器的註冊,註冊到 javac 後,在項目編譯時就能執行註解處理器了,他是SPI的一種實現。這個在後面會引伸一下。
咱們繼承AbstractProcessor,須要關心這幾個實現:
初始化:init()
:註解處理器運行掃描源文件時,以獲取元素(Element)相關的信息,維度:類、成員變量、方法
解析類:process()
處理解析相關流程
其它:getSupportedXXX()
,一些配置:指定 java 版本,指定待解析的註解
咱們須要關心的就是process:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk, debuggable);//處理生成文件
try {
javaFile.writeTo(filer);
} catch (IOException e) { }
}
return false;
}
複製代碼
第一句就是掃描相關的代碼,findAndParseTargets,咱們經過這個方法,找咱們關心的bindview,先將掃描獲得的註解相關信息保存到builderMap
,最後對這些信息進行從新整理返回一個以TypeElement
爲 key 、BindingSet
爲 value 的 Map,其中TypeElement
表明使用了 ButterKnife 的類,即 Activity、Fragment等。這個步驟就是爲了生成文件前的準備。用來存儲要生成類的基本信息以及註解元素的相關信息。
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
複製代碼
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
// 首先要注意,此時element是VariableElement類型的,即成員變量enclosingElement是當前元素的父類元素,通常就是咱們使用ButteKnife時定義的View類型成員變量所在的類,能夠理解爲以前例子中的MainActivity
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 進行相關校驗,不是重點,此處主要爲一些限定性驗證
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
TypeMirror elementType = element.asType();
.......
// 得到元素使用BindView註解時設置的屬性值,即 View 對應的xml中的id
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = builderMap.get(enclosingElement);
Id resourceId = elementToId(element, BindView.class, id);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(resourceId);
if (existingBindingName != null) {return; }
} else {
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
}
複製代碼
看起來很繁瑣,可是內部最核心的就是builder.addField(resourceId, new FieldViewBinding(name, type, required));只須要關注id,元素名,類型就行,最後生成的文件也就這幾個主要元素,其餘都是一些校驗,這些校驗踩坑纔多了天然就知道了。這裏面還存在不少AST,JavaC相關的知識,建議這個階段只作瞭解,好比elementToId方法:
private Id elementToId(Element element, Class<? extends Annotation> annotation, int value) {
JCTree tree = (JCTree) trees.getTree(element, getMirror(element, annotation));
if (tree != null) {
rScanner.reset();
tree.accept(rScanner);
if (!rScanner.resourceIds.isEmpty()) {
return rScanner.resourceIds.values().iterator().next();
}
}
return new Id(value);
}
複製代碼
通常都是直接獲取到id,可是這裏又經過JCTree重新獲取了一遍,理論上值是相同的(這個你們能夠試驗一下)。
好了,看到這裏,你們確定疑惑,好像只處理R.id,對於ButterKnife特有的R2.id是怎麼處理的呢,就在下面
這裏的Id是後面的獲取的核心,
private static final ClassName ANDROID_R = ClassName.get("android", "R");
private static final String R = "R";
Id(int value, @Nullable Symbol rSymbol) {
this.value = value;
if (rSymbol != null) {
//這裏默認取出R(在這裏就拋棄了R2的概念),classname是包名.R.id這種數據
ClassName className = ClassName.get(rSymbol.packge().getQualifiedName().toString(), R,
rSymbol.enclClass().name.toString());
String resourceName = rSymbol.name.toString();
//將要使用的核心,code,將code拼成包名.R.id.XXX
this.code = className.topLevelClassName().equals(ANDROID_R)
? CodeBlock.of("$L.$N", className, resourceName)
: CodeBlock.of("$T.$N", className, resourceName);
this.qualifed = true;
} else {
this.code = CodeBlock.of("$L", value);
this.qualifed = false;
}
}
複製代碼
最後在BindingSet中生成了這個文件相關的東西,能夠看到新建的文件名,這個就是以後建立的文件名:_ViewBinding
static ClassName getBindingClassName(TypeElement typeElement) {
String packageName = getPackage(typeElement).getQualifiedName().toString();
String className = typeElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');
return ClassName.get(packageName, className + "_ViewBinding");
}
複製代碼
我看看下findviewbyid
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
if (binding.isSingleFieldBinding()) {
FieldViewBinding fieldBinding = requireNonNull(binding.getFieldBinding());
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName());
boolean requiresCast = requiresCast(fieldBinding.getType());
if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
if (requiresCast) {
builder.add("($T) ", fieldBinding.getType());
}
builder.add("source.findViewById($L)", binding.getId().code);//這裏是核心,用上面的id裏面的code直接替換就變成咱們的想要的樣子,這裏的code就是android.R.color.transparent這種樣式
} else {
builder.add("$T.find", UTILS);//構造了butterknife.internal.utils這個類
builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
if (requiresCast) {
builder.add("AsType");
}
builder.add("(source, $L", binding.getId().code);
if (fieldBinding.isRequired() || requiresCast) {
builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
}
if (requiresCast) {
builder.add(", $T.class", fieldBinding.getRawType());
}
builder.add(")");
}
result.addStatement("$L", builder.build());
return;
}
複製代碼
butterknife生成代碼使用的是JavaPoet,這個不強制,你也能夠用字符串寫。使用Javapoet的好處就是他能夠構造文件系統,生成的java程序是符合語法邏輯的。僅此而已。
JavaFile brewJava(int sdk, boolean debuggable) {
TypeSpec bindingConfiguration = createType(sdk, debuggable);//代碼生成相關
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
複製代碼
這東西沒啥好說的,就是javapoet的使用。這種代碼看起來其實沒啥意思,就是javapoet的使用,咱們能夠直接看代碼最後的生成,反推javapoet的寫法,
public DrawerToggleActivity_ViewBinding(DrawerToggleActivity target, View source) {
this.target = target;
target.textview = Utils.findRequiredViewAsType(source, R.id.drawer_layout, "field 'drawerLayout'", DrawerLayout.class);
Context context = source.getContext();
target.guideColor = ContextCompat.getColor(context, R.color.half_transparent);
}
複製代碼
代碼生成的比較統一。
AutoService是google提供的框架,其源碼比較簡單:
public class AutoServiceProcessor extends AbstractProcessor //能夠看到autoservice也是一個APT,就是套娃 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
return processImpl(annotations, roundEnv);
} catch (Exception e) {
return true;}
}
private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
generateConfigFiles();
} else {
processAnnotations(annotations, roundEnv);
}
return true;
}
private void generateConfigFiles() {//就是一個建立文件的過程
Filer filer = processingEnv.getFiler();
for (String providerInterface : providers.keySet()) {
String resourceFile = "META-INF/services/" + providerInterface;
try {
SortedSet<String> allServices = Sets.newTreeSet();
try {
FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
allServices.addAll(oldServices);
} catch (IOException e) {
log("Resource file did not already exist.");
}
allServices.addAll(newServices);
log("New service file contents: " + allServices);
FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
OutputStream out = fileObject.openOutputStream();
ServicesFiles.writeServiceFile(allServices, out);
out.close();
} catch (IOException e) {
return;
}
}
}
複製代碼
在官方文檔上介紹的使用@AutoService()修飾後,會生成META-INF/services/javax.annotation.processing.Processor文件夾生成文件,這種實現就是JAVA原生的ServiceLoader
是SPI
的是一種實現,所謂SPI
,即Service Provider Interface
,用於一些服務提供給第三方實現或者擴展,能夠加強框架的擴展或者替換一些組件,他能夠自動加載這個類了
咱們學習了butterknife的實現,那咱們有兩個問題:
1.若是實現相同的功能,咱們還能怎麼作
2.若是學習了相似的寫法,咱們能寫出來什麼樣的功能。
1.kotlin的實現
kotlin是使用kotlin-android-extensions這個技術實現的,
使用:
import kotlinx.android.synthetic.main.xml文件的名稱.*
複製代碼
咱們經過反編譯能夠其實咱們的源碼又多了一些東西:
private HashMap _$_findViewCache;
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
((TextView)this._$_findCachedViewById(id.helloTv)).setText((CharSequence)"Hello Kotlin!");
}
public View _$_findCachedViewById(int var1) {
if(this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
if(var2 == null) {
var2 = this.findViewById(var1);
this._$_findViewCache.put(Integer.valueOf(var1), var2);
}
return var2;
}
public void _$_clearFindViewByIdCache() {
if(this._$_findViewCache != null) {
this._$_findViewCache.clear();
}
}
複製代碼
這個只能說在編譯期層面使用插件(kotlin-android-extensions)上都作了相關的操做,最終的代碼其實不是咱們看到的那樣(具體實現沒有看過,插件代碼太多了)。
2.AST的實現
AST是抽象語法樹,計算機內存的一種樹狀數據結構,便於計算機理解和閱讀。咱們能夠經過修改抽象語法樹對源碼的結構作增刪改查,這個技術普遍應用於Lint上,這個的實現有興趣能夠研究一下,源碼參考Lint相關實現。
3.ASM的實現
和上面的AST的機制差很少,他構建的是class的結構,ASM操做起來比較麻煩,須要考慮的東西也不少,這個的實現有興趣能夠研究一下,源碼參考ByteX.
Android中有個LocalBroadcast的東西,也就是本地廣播,咱們怎麼經過利用本地廣播來構形成EventBus的外表。咱們能夠利用APT的實現。
首先咱們先構造初始化的方法,學習Butterknife同樣,
private static Unbinder createBroadCast(@NonNull Object activity) {
Class<?> targetClass = activity.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(activity);
} 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);
}
}
@Nullable
@CheckResult
@UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
try {
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName +BROCASE_SUFFIX);
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls);
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;
}
複製代碼
能夠看到代碼差很少,核心就是APT的process方法裏面的東西了,
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
mProxyMap.clear();
Set<? extends Element> elesWithBind = roundEnv.getElementsAnnotatedWith(LocalBind.class);
for (Element element : elesWithBind) {
checkAnnotationValid(element, LocalBind.class);
ExecutableElement variableElement = (ExecutableElement) element;
//class type
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
//full class name
String fqClassName = classElement.getQualifiedName().toString();
LocalProxyInfo proxyInfo = mProxyMap.get(fqClassName);
if (proxyInfo == null) {
proxyInfo = new LocalProxyInfo(elementUtils, classElement);//構造生成類
mProxyMap.put(fqClassName, proxyInfo);
}
LocalBind bindAnnotation = variableElement.getAnnotation(LocalBind.class);
proxyInfo.setValus(bindAnnotation.value());
proxyInfo.injectVariables.put(fqClassName, variableElement);
}
for (String key : mProxyMap.keySet()) {
LocalProxyInfo 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;
}
複製代碼
能夠看到,很簡單的代碼,先根據類名,參數等構造出文件生成器LocalProxyInfo:
public LocalProxyInfo(Elements elementUtils, TypeElement classElement) {
this.typeElement = classElement;
PackageElement packageElement = elementUtils.getPackageOf(classElement);
String packageName = packageElement.getQualifiedName().toString();
//classname
String className = ClassValidator.getClassName(classElement, packageName);
this.packageName = packageName;
this.proxyClassName = className + PROXY;
}
複製代碼
而proxyInfo.generateJavaCode()則是執行寫入文件的入口了
public String generateJavaCode() {
StringBuilder builder = new StringBuilder();
builder.append("// Generated code. Do not modify!\n");
builder.append("package ").append(packageName).append(";\n\n");
builder.append("import com.longshihan.broca_api.*;\n");
builder.append("import android.content.IntentFilter;\n");
builder.append("import android.support.v4.content.LocalBroadcastManager;\n");
builder.append("import android.content.BroadcastReceiver;\n");
builder.append("import android.content.Context;\n");
builder.append("import android.content.Intent;\n");
builder.append("import android.support.annotation.UiThread;\n");
builder.append("import ").append(typeElement.getQualifiedName()).append(" ;\n");
builder.append('\n');
builder.append("public class ").append(proxyClassName).append(" implements Unbinder");
builder.append(" {\n");
builder.append("private LocalReceiver localReceiver;\n");
builder.append("private LocalBroadcastManager localBroadcastManager;\n");
builder.append(" private IntentFilter intentFilter;\n");
builder.append(" private ").append(typeElement.getQualifiedName()).append(" host;\n");
generateCMethods(builder);
generateMethods(builder);
generateDMethods(builder);
builder.append('\n');
builder.append("}\n");
return builder.toString();
}
複製代碼
我這裏代碼比較簡單,直接用string生成代碼就好了。固然,其餘方式也行。
到這裏一個功能就實現了,如今看下效果,猜也能猜出大概。
源文件MainActivity:
@LocalBind({"123", "345","4586"})
public void getBroadcastService(Intent intent) {
Log.d("打印Action", intent.getAction());
switch (intent.getAction()){
case "123":
break;
case "345":
break;
case "4586":
break;
}
}
複製代碼
生成的文件(build/generated/source/apt/debug/包名/MainActivity$BroadcastInject):
public class MainActivity$BroadcastInject implements Unbinder {
private LocalReceiver localReceiver;
private LocalBroadcastManager localBroadcastManager;
private IntentFilter intentFilter;
private com.longshihan.aopbrocad.MainActivity host;
@UiThread
public MainActivity$BroadcastInject(com.longshihan.aopbrocad.MainActivity host) {
localBroadcastManager = LocalBroadcastManager.getInstance(host);
intentFilter = new IntentFilter();
this.host=host;
intentFilter.addAction("123");
intentFilter.addAction("345");
intentFilter.addAction("4586");
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter);
}
public class LocalReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
host.getBroadcastService(intent);
}
}
@Override
public void unbind(){
localBroadcastManager.unregisterReceiver(localReceiver);
}
}
複製代碼
OK ,寫到這裏你們對於APT的使用也瞭然於心了吧,對於相關應用也有不少發散思惟之處,好比Ali的ARouter,Dagger等等,這個能夠交到你們以後學習了。