一、ButterKnife
ButterKnife作爲常用框架之一,主要用於簡化代碼,減少重複代碼。
這裏主要注重原理與核心,將分步驟手寫它的核心代碼。
ButterKnife最常用的是去除代碼中的findViewById,以註解的方式代替原有的代碼,這裏也從這裏入手。
Android之註解的使用——綁定android控件 這是前文中通過反射方式實現,可對比查看
二、原理說明
public class MainActivity extends AppCompatActivity {
@BindView(R.id.hello)
TextView hello;
@BindView(R.id.btn)
Button btn;
...
核心原理:
1、ButterKnife的用法,如上圖所示,以註解的方式標識控件。
2、通過apt註解處理器,處理該註解BindView,將註解上的參數傳入到處理器中,生成代碼。
3、通過調用生成的代碼,實現findviewById等。
三、手寫實現
1、定義註解BindView
//編譯時起效
@Retention(RetentionPolicy.CLASS)
//針對的是屬性
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
2、註解處理器的編寫
//註解處理器的依賴,此處有注意點: dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) //依賴註解 implementation project(path: ':bind-annotation') //如果是3.6+的android studio,auto-service需要按如下依賴 compileOnly'com.google.auto.service:auto-service:1.0-rc4' annotationProcessor'com.google.auto.service:auto-service:1.0-rc4' }
//註冊編譯處理器到系統
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor {
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//filter 用於後續寫文件
filer = processingEnvironment.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//roundEnvironment中根據annotation獲取節點
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//區分上面獲取到的節點,獲取類名和該類下的BindView節點====> 形成一個Map<類名,BindView標記的View集合>這樣的結構。
//後續根據這個結構生成一個或者多個java文件
Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator();
Map<String,List<VariableElement>> map = new HashMap<>();
List<VariableElement> variableElements;
while (iterator.hasNext()){
//節點集合
VariableElement variableElement = (VariableElement)iterator.next();
//類名
String className = variableElement.getEnclosingElement().getSimpleName().toString();
variableElements = map.get(className);
if(map.get(className) == null){
variableElements = new ArrayList<>();
map.put(className,variableElements);
}
variableElements.add(variableElement);
}
//寫文件
Writer writer = null;
Iterator<String> iteratorNames = map.keySet().iterator();
while (iteratorNames.hasNext()){
String currentClassName = iteratorNames.next();
List<VariableElement> currentVariableElements = map.get(currentClassName);
//包名
String packageName = processingEnv.getElementUtils().getPackageOf(currentVariableElements.get(0)).toString();
try {
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + currentClassName + "$$ViewBind");
writer = sourceFile.openWriter();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("package "+packageName+";\n");
stringBuilder.append("import com.sunny.bind_api.IBindView;\n");
stringBuilder.append("public class "+currentClassName + "$$ViewBind implements IBindView<"+packageName+"."+currentClassName+">{"+"\n");
stringBuilder.append("public void bind("+packageName+"."+currentClassName+" target){\n");
for(VariableElement currentElement :currentVariableElements){
//控件名字
String filedName = currentElement.getSimpleName().toString();
//控件類型
TypeMirror typeMirror = currentElement.asType();
//控件resId
int resourceId = currentElement.getAnnotation(BindView.class).value();
stringBuilder.append("target."+filedName +" = ("+typeMirror+")target.findViewById("+resourceId+");\n");
}
stringBuilder.append("}\n}\n");
writer.write(stringBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(writer != null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return processingEnv.getSourceVersion();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
}
這裏的核心在於:如何獲取到Field類型的註解控件,然後 獲取該控件的類型,resourceId,並把以上控件信息,放置到一個集合中,後續遍歷並生成文件。
1、variableElement代表屬性節點
2、ExecutableElement代表方法節點
3、TypeElement代表最外層的類節點
此處在生成代碼時,實現了一個接口:IBindView,主要是爲了後續在調用代碼時更有指向性與範圍。
public interface IBindView<T> {
void bind(T t);
}
3、調用代碼的編寫
public class ButterKnife {
public static void bind(Activity activity){
String className = activity.getClass().getName()+"$$ViewBind";
try {
Class<?> aClass = Class.forName(className);
//IBindView的使用
IBindView o = (IBindView)aClass.newInstance();
//具體調用
o.bind(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
目前在用的ButterKnife,複雜性與完整性,比以上的核心簡寫複雜很多,但本質原理就是上面描述的這樣:用apt的方式,簡化代碼與去除重複代碼。
apt對於去重代碼、生成代碼、切面編程等,十分有效,多數涉及到框架的,都會用到該用法。