ButterKnife手原始碼分析和手寫
阿新 • • 發佈:2019-01-07
參考butterknife原始碼進行手寫,首先分model:butterknife,compiler和annomation
首先butterknife原始碼分析:使用butterknife之後會自動生成這個類
package com.peakmain.butterkinfe;
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this (target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
target.textView1 = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'textView1'", TextView.class);
target.textView2 = Utils.findRequiredViewAsType(source, R.id.tv2, "field 'textView2'" , TextView.class);
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.textView1 = null;
target.textView2 = null;
}
}
app關聯三個model,並修改其中的compiler和新增外掛
compile project(':butterknife_annotations')
apt project(':butterknife_compiler')
compile project(':butterknif
apply plugin: 'com.neenbedankt.android-apt'
complier關聯annotation(主要用來放註解)
@Retention(RetentionPolicy.CLASS)//編譯時註解
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
ButterknifeProcessor生成器:新增依賴
compile 'com.google.auto.service:auto-service:1.0-rc3'
compile 'com.squareup:javapoet:1.8.0'
/*支援中文*/
tasks.withType(JavaCompile){
options.encoding='UTF-8'
}
//ButterKnifeProcessor 原始碼
AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
private Filer mFiler;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mElementUtils = processingEnvironment.getElementUtils();
}
// 1. 指定處理的版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
// 2. 給到需要處理的註解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
// 需要解析的自定義註解 BindView OnClick
annotations.add(BindView.class);
return annotations;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// System.out.println("------------------------>");
// System.out.println("------------------------>");
// System.out.println("------------------------>");
// System.out.println("------------------------>");
// process 方法代表的是,有註解就都會進來 ,但是這裡面是一團亂麻
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
/*for (Element element : elements) {
Element enclosingElement = element.getEnclosingElement();
System.out.println("------------------------>"+element.getSimpleName().toString()+" "+enclosingElement.getSimpleName().toString());
}*/
// 解析 屬性 activity -> List<Element>
Map<Element, List<Element>> elementsMap = new LinkedHashMap<>();
for (Element element : elements) {
Element enclosingElement = element.getEnclosingElement();
List<Element> viewBindElements = elementsMap.get(enclosingElement);
if (viewBindElements == null) {
viewBindElements = new ArrayList<>();
elementsMap.put(enclosingElement, viewBindElements);
}
viewBindElements.add(element);
}
// 生成程式碼
for (Map.Entry<Element, List<Element>> entry : elementsMap.entrySet()) {
Element enclosingElement = entry.getKey();
List<Element> viewBindElements = entry.getValue();
// public final class xxxActivity_ViewBinding implements Unbinder
String activityClassNameStr = enclosingElement.getSimpleName().toString();
ClassName activityClassName = ClassName.bestGuess(activityClassNameStr);
ClassName unbinderClassName = ClassName.get("com.butterknife","Unbinder");
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityClassNameStr+"_ViewBinding")
.addModifiers(Modifier.FINAL,Modifier.PUBLIC).addSuperinterface(unbinderClassName)
.addField(activityClassName,"target",Modifier.PRIVATE);
// 實現 unbind 方法
// android.support.annotation.CallSuper
ClassName callSuperClassName = ClassName.get("android.support.annotation","CallSuper");
MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC,Modifier.FINAL)
.addAnnotation(callSuperClassName);
unbindMethodBuilder.addStatement("$T target = this.target",activityClassName);
unbindMethodBuilder.addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\");");
// 建構函式
MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
.addParameter(activityClassName,"target");
// this.target = target;
constructorMethodBuilder.addStatement("this.target = target");
// findViewById 屬性
for (Element viewBindElement : viewBindElements) {
// target.textView1 = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'textView1'", TextView.class);
// target.textView1 = Utils.findViewById(source, R.id.tv1);
String filedName = viewBindElement.getSimpleName().toString();
ClassName utilsClassName = ClassName.get("com.butterknife","Utils");
int resId = viewBindElement.getAnnotation(BindView.class).value();
constructorMethodBuilder.addStatement("target.$L = $T.findViewById(target, $L)",filedName,utilsClassName,resId);
// target.textView1 = null;
unbindMethodBuilder.addStatement("target.$L = null",filedName);
}
classBuilder.addMethod(unbindMethodBuilder.build());
classBuilder.addMethod(constructorMethodBuilder.build());
// 生成類,看下效果
try {
String packageName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
JavaFile.builder(packageName,classBuilder.build())
.addFileComment("butterknife 自動生成")
.build().writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
System.out.println("翻車了!");
}
}
return false;
}
最後model是Butterknife目的如butterknife外掛使用的使用的時候butterknife.bind(this);
public class ButterKnife {
public static Unbinder bind(Activity activity){
// xxxActivity_ViewBinding viewBinding = new xxxActivity_ViewBinding(this)
try {
Class<? extends Unbinder> bindClassName=(Class<? extends Unbinder>)
Class.forName(activity.getClass().getName()+"_ViewBinding");
//建構函式 new MainActivity_ViewBinding()
Constructor<? extends Unbinder> bindConstructor = bindClassName.getDeclaredConstructor(activity.getClass());
Unbinder unbinder = bindConstructor.newInstance();
return unbinder;
} catch (Exception e) {
e.printStackTrace();
return Unbinder.EMPTY;
}
}
}
Unbinder是複製butterknife的原始碼
public interface Unbinder {
@UiThread
void unbind();
Unbinder EMPTY = new Unbinder() {
@Override public void unbind() { }
};
}
public class Utils {
public static <T extends View> T findViewById(Activity activity,int viewId){
return (T) activity.findViewById(viewId);
}
}
使用
@BindView(R.id.tv1)
TextView textView1;
@BindView(R.id.tv2)
TextView textView2;
@BindView(R.id.tv2)
TextView textView3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Unbinder unbinder = ButterKnife.bind(this);
/*textView1.setText("textView1");
textView2.setText("textView2");*/
}
這時候就完成了但是這時候的生成的id是一串整形資料,這時候我們需要根據id的數字獲得到R.id.xxx,複製butterknife的相關原始碼即可
package com.butterknife.annotation;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import com.sun.source.tree.ClassTree;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeScanner;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
/**
* 作者:Peakmain
* 版本:1.0
* 建立日期:2018/3/6 22:31
* 郵件:[email protected]
* 描述:
*/
@AutoService(Processor.class)
public class ButterknifeProcessor extends AbstractProcessor {
private Filer mFiler;
private Elements mElementUtils;
private final Map<QualifiedId, Id> symbols = new LinkedHashMap<>();
private Trees trees;
private static final List<String> SUPPORTED_TYPES = Arrays.asList(
"array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "string"
);
private Types typeUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler();
mElementUtils = processingEnv.getElementUtils();
trees = Trees.instance(processingEnv);
typeUtils = processingEnv.getTypeUtils();
}
//1.獲得當前版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();//最大版本
}
// 2. 給到需要處理的註解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
//添加註解
annotations.add(BindView.class);
return annotations;
}
//所有註解都會走向這裡
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
/* System.out.println("------------------------>");
System.out.println("------------------------>");
System.out.println("------------------------>");
System.out.println("------------------------>");*/
// process 方法代表的是,有註解就都會進來 ,但是這裡面是一團亂麻
/*Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {//element.getSimpleName().toString() 自己定義的名字
Element enclosingElement = element.getEnclosingElement();//代表MainActivity或者LoginActivity
System.out.println("------------------------>"+
element.getSimpleName().toString()+" "+enclosingElement.getSimpleName().toString());
}*/
//解析屬性 activity->list<Element>
scanForRClasses(roundEnv);
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
Map<Element, List<Element>> elementsMap = new LinkedHashMap<>();
for (Element element : elements) {
Element enclosingElement = element.getEnclosingElement();//獲得的是activity的名字
List<Element> viewBindElement = elementsMap.get(enclosingElement);//根據key獲得所有註解下自己定義的名字,預設是空
if (viewBindElement == null) {
viewBindElement = new ArrayList<>();
elementsMap.put(enclosingElement, viewBindElement);
}
viewBindElement.add(element);//新增節點
}
//生成程式碼
for (Map.Entry<Element, List<Element>> entry : elementsMap.entrySet()) {
Element enclosingElement = entry.getKey();//獲得key
List<Element> viewBindElement = entry.getValue();//獲得value
//生成public final class xxxActivity_ViewBinding implements Unbinder
String activityClassNameStr = enclosingElement.getSimpleName().toString();//如MainActivity
ClassName activityName = ClassName.bestGuess(activityClassNameStr);
ClassName unbinderClassName = ClassName.get("com.butterknife", "Unbinder");//獲得類名
//報錯需要新增依賴 生成public final class xxxActivity_ViewBinding implements Unbinder
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityClassNameStr + "_ViewBinding")
.addModifiers(Modifier.FINAL, Modifier.PUBLIC)
.addSuperinterface(unbinderClassName)
.addField(activityName, "target", Modifier.PRIVATE);//新增屬性
// 實現 unbind 方法
// android.support.annotation.CallSuper
ClassName callSuperClassName = ClassName.get("android.support.annotation", "CallSuper");
MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addAnnotation(callSuperClassName);
//新增構造
MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
.addParameter(activityName, "target");
// this.target = target
constructorMethodBuilder.addStatement(" this.target = target");
//findvById
for (Element element : viewBindElement) {
// target.textView1 = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'textView1'", TextView.class);
// target.textView1 = Utils.findViewById(source, R.id.tv1);
String filedName = element.getSimpleName().toString();//屬性的名字,如tv_name
ClassName utilsClassName = ClassName.get("com.butterknife", "Utils");
int resId = element.getAnnotation(BindView.class).value();
QualifiedId qualifiedId = elementToQualifiedId(element, resId);
Id id = getId(qualifiedId);
CodeBlock codeBlock = id.code;
constructorMethodBuilder.addStatement("target.$L =$L.findViewById(target,$L)", filedName, utilsClassName, codeBlock);
// target.textView1 = null;
unbindMethodBuilder.addStatement("target.$L = null", filedName);
}
classBuilder.addMethod(unbindMethodBuilder.build());
classBuilder.addMethod(constructorMethodBuilder.build());
//生成類,看下效果
try {
//生成包名
String packName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
JavaFile.builder(packName, classBuilder.build())
.addFileComment("butterknife 自動生成")
.build().writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
System.out.println("生成失敗!");
}
}
return false;
}
private QualifiedId elementToQualifiedId(Element element, int id) {
return new QualifiedId(mElementUtils.getPackageOf(element), id);
}
private Id getId(QualifiedId qualifiedId) {
if (symbols.get(qualifiedId) == null) {
symbols.put(qualifiedId, new Id(qualifiedId.id));
}
return symbols.get(qualifiedId);
}
private void scanForRClasses(RoundEnvironment env) {
if (trees == null) return;
RClassScanner scanner = new RClassScanner();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
for (Element element : env.getElementsAnnotatedWith(annotation)) {
JCTree tree = (JCTree) trees.getTree(element, getMirror(element, annotation));
if (tree != null) { // tree can be null if the references are compiled types and not source
scanner.setCurrentPackage(mElementUtils.getPackageOf(element));
tree.accept(scanner);
}
}
}
for (Map.Entry<PackageElement, Set<Symbol.ClassSymbol>> packageNameToRClassSet
: scanner.getRClasses().entrySet()) {
PackageElement respectivePackageName = packageNameToRClassSet.getKey();
for (Symbol.ClassSymbol rClass : packageNameToRClassSet.getValue()) {
parseRClass(respectivePackageName, rClass, scanner.getReferenced());
}
}
}
private void parseRClass(PackageElement respectivePackageName, Symbol.ClassSymbol rClass,
Set<String> referenced) {
TypeElement element;
try {
element = rClass;
} catch (MirroredTypeException mte) {
element = (TypeElement) typeUtils.asElement(mte.getTypeMirror());
}
JCTree tree = (JCTree) trees.getTree(element);
if (tree != null) { // tree can be null if the references are compiled types and not source
IdScanner idScanner =
new IdScanner(symbols, mElementUtils.getPackageOf(element), respectivePackageName,
referenced);
tree.accept(idScanner);
} else {
parseCompiledR(respectivePackageName, element, referenced);
}
}
private void parseCompiledR(PackageElement respectivePackageName, TypeElement rClass,
Set<String> referenced) {
for (Element element : rClass.getEnclosedElements()) {
String innerClassName = element.getSimpleName().toString();
if (SUPPORTED_TYPES.contains(innerClassName)) {
for (Element enclosedElement : element.getEnclosedElements()) {
if (enclosedElement instanceof VariableElement) {
String fqName = mElementUtils.getPackageOf(enclosedElement).getQualifiedName().toString()
+ ".R."
+ innerClassName
+ "."
+ enclosedElement.toString();
if (referenced.contains(fqName)) {
VariableElement variableElement = (VariableElement) enclosedElement;
Object value = variableElement.getConstantValue();
if (value instanceof Integer) {
int id = (Integer) value;
ClassName rClassName =
ClassName.get(mElementUtils.getPackageOf(variableElement).toString(), "R",
innerClassName);
String resourceName = variableElement.getSimpleName().toString();
QualifiedId qualifiedId = new QualifiedId(respectivePackageName, id);
symbols.put(qualifiedId, new Id(id, rClassName, resourceName));
}
}
}
}
}
}
}
private static class RClassScanner extends TreeScanner {
// Maps the currently evaluated rPackageName to R Classes
private final Map<PackageElement, Set<Symbol.ClassSymbol>> rClasses = new LinkedHashMap<>();
private PackageElement currentPackage;
private Set<String> referenced = new HashSet<>();
@Override public void visitSelect(JCTree.JCFieldAccess jcFieldAccess) {
Symbol symbol = jcFieldAccess.sym;
if (symbol != null
&& symbol.getEnclosingElement() != null
&& symbol.getEnclosingElement().getEnclosingElement() != null
&& symbol.getEnclosingElement().getEnclosingElement().enclClass() != null) {
Set<Symbol.ClassSymbol> rClassSet = rClasses.get(currentPackage);
if (rClassSet == null) {
rClassSet = new HashSet<>();
rClasses.put(currentPackage, rClassSet);
}
referenced.add(getFqName(symbol));
rClassSet.add(symbol.getEnclosingElement().getEnclosingElement().enclClass());
}
}
Map<PackageElement, Set<Symbol.ClassSymbol>> getRClasses() {
return rClasses;
}
Set<String> getReferenced() {
return referenced;
}
void setCurrentPackage(PackageElement packageElement) {
this.currentPackage = packageElement;
}
}
private static String getFqName(Symbol rSymbol) {
return rSymbol.packge().getQualifiedName().toString()
+ ".R."
+ rSymbol.enclClass().name.toString()
+ "."
+ rSymbol.name.toString();
}
private static class IdScanner extends TreeScanner {
private final Map<QualifiedId, Id> ids;
private final PackageElement rPackageName;
private final PackageElement respectivePackageName;
private final Set<String> referenced;
IdScanner(Map<QualifiedId, Id> ids, PackageElement rPackageName,
PackageElement respectivePackageName, Set<String> referenced) {
this.ids = ids;
this.rPackageName = rPackageName;
this.respectivePackageName = respectivePackageName;
this.referenced = referenced;
}
@Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
for (JCTree tree : jcClassDecl.defs) {
if (tree instanceof ClassTree) {
ClassTree classTree = (ClassTree) tree;
String className = classTree.getSimpleName().toString();
if (SUPPORTED_TYPES.contains(className)) {
ClassName rClassName = ClassName.get(rPackageName.getQualifiedName().toString(), "R",
className);
VarScanner scanner = new VarScanner(ids, rClassName, respectivePackageName, referenced);
((JCTree) classTree).accept(scanner);
}
}
}
}
}
private static class VarScanner extends TreeScanner {
private final Map<QualifiedId, Id> ids;
private final ClassName className;
private final PackageElement respectivePackageName;
private final Set<String> referenced;
private VarScanner(Map<QualifiedId, Id> ids, ClassName className,
PackageElement respectivePackageName, Set<String> referenced) {
this.ids = ids;
this.className = className;
this.respectivePackageName = respectivePackageName;
this.referenced = referenced;
}
@Override public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
if ("int".equals(jcVariableDecl.getType().toString())) {
String resourceName = jcVariableDecl.getName().toString();
if (referenced.contains(getFqName(jcVariableDecl.sym))) {
int id = Integer.valueOf(jcVariableDecl.getInitializer().toString());
QualifiedId qualifiedId = new QualifiedId(respectivePackageName, id);
ids.put(qualifiedId, new Id(id, className, resourceName));
}
}
}
}
private static AnnotationMirror getMirror(Element element,
Class<? extends Annotation> annotation) {
for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
if (annotationMirror.getAnnotationType().toString().equals(annotation.getCanonicalName())) {
return annotationMirror;
}
}
return null;
}
}
Id
public final class Id {
private static final ClassName ANDROID_R = ClassName.get("android", "R");
final int value;
final CodeBlock code;
final boolean qualifed;
Id(int value) {
this.value = value;
this.code = CodeBlock.of("$L", value);
this.qualifed = false;
}
Id(int value, ClassName className, String resourceName) {
this.value = value;
this.code = className.topLevelClassName().equals(ANDROID_R)
? CodeBlock.of("$L.$N", className, resourceName)
: CodeBlock.of("$T.$N", className, resourceName);
this.qualifed = true;
}
@Override public boolean equals(Object o) {
return o instanceof Id && value == ((Id) o).value;
}
@Override public int hashCode() {
return value;
}
@Override public String toString() {
throw new UnsupportedOperationException("Please use value or code explicitly");
}
}
QualifiedId
public final class QualifiedId {
final PackageElement packageName;
final int id;
QualifiedId(PackageElement packageName, int id) {
this.packageName = packageName;
this.id = id;
}
@Override public String toString() {
return "QualifiedId{packageName='" + packageName + "', id=" + id + '}';
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof QualifiedId)) return false;
QualifiedId other = (QualifiedId) o;
return id == other.id
&& packageName.equals(other.packageName);
}
@Override public int hashCode() {
int result = packageName.hashCode();
result = 31 * result + id;
return result;
}