
前言
在開發(fā)經(jīng)常碰到注解形式,比如我們代碼中常見@Override,也比如我之前發(fā)表的Retrofit之解析xml (詳細)全是注解操作,還有很多第三方注解注入框架,像ButterKnife,Dagger2等,注解的使用很廣泛,今日以ButterKnife為例,來探其究竟.
準備知識
java注解是在java5之后引入的,對于java注解完全不了解的請看秒懂,Java 注解 (Annotation)你可以這樣學,看完可了解注解的基本用法.注解分為運行時注解和編譯時注解,此處ButterKnife為編譯時注解.
核心原理
編譯時注解是通過APT(Annotation Processing Tools)實現(xiàn). APT是一種處理注釋的工具,它對源代碼文件進行檢測找出其中的Annotation,使用Annotation進行額外的處理。 Annotation處理器在處理Annotation時可以根據(jù)源文件中的Annotation生成額外的源文件和其它的文件(文件具體內(nèi)容由Annotation處理器的編寫者決定),APT還會編譯生成的源文件和原來的源文件,將它們一起生成class文件。
項目架構



Android庫Inject處理
Inject為使用方提供方法,先看是MainAcitity中是如何調(diào)用的
public class MainActivity extends AppCompatActivity {
@BindView(R.id.text)
TextView textview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectView.bind(this);
Toast.makeText(this,"---> "+textview, Toast.LENGTH_SHORT).show();
textview.setText("6666666");
}
}
所以此處在InjectView對應處理是,提供相應方法
public class InjectView {
public static void bind(Activity activity)
{
String clsName=activity.getClass().getName();//反射拿到使用方類名
try {
Class<?> viewBidClass= Class.forName(clsName+"$$ViewBinder");//反射拿到新生成的內(nèi)部類
ViewBinder viewBinder= (ViewBinder) viewBidClass.newInstance();//反射后類型
viewBinder.bind(activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
ViewBinder是為了接收拿到的反射后的類型
public interface ViewBinder <T>{
void bind(T tartget);
}
Java庫inject-annotion處理
inject-annotion負責提供注解,此處只創(chuàng)建了BindView注解
@Target(ElementType.FIELD)//表示可以給屬性進行注解
@Retention(RetentionPolicy.CLASS)//代表運行在編譯時
public @interface BindView {
int value();
}
Java庫Inject-compiler處理
Inject-compiler中處理核心邏輯,基本原理是,在編譯時javac編譯器會檢查AbstractProcessor的子類,并且調(diào)用該類型的process函數(shù),然后將添加了注解的所有元素都傳遞到process函數(shù)中,使得開發(fā)人員可以在編譯器進行相應的處理.
此處BindViewProcessor繼承了AbstractProcessor,并通過@AutoService(Processor.class),表明自身為注解處理器,其中主要有四個方法,在init方法中做初始化;在getSupportedAnnotationTypes方法中指明支持哪些注解,此處指明的注解即為BindView;在getSupportedSourceVersion方法中返回最新的JDK版本;在process方法中做主邏輯處理
//用@AutoService 指定注解處理器
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
/**
* 處理Element工具類
* 主要獲取包名
*/
private Elements elementUtils;
/**
* Java文件輸出類(非常重要)生成它是用來java文件
*/
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
filer = processingEnvironment.getFiler();
}
/**
* @return 當前注解處理器 支持哪些注解
* 返回的是set字符串集合
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
// types.add(Override.class.getCanonicalName());
return types;
}
/**
* 支持jdk版本
* 一般選擇最新版本
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* javac編譯器 遇到含有BindView注解的java文件時,就會調(diào)用這個process方法
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//聲明一個緩存的map集合 鍵 是Activity 值 是當前Activity 里面含有BndView成員變量的集合
Map<TypeElement, List<FieldViewBinding>> targetMap = new HashMap<>();
//編譯時只能通過文件輸出打印,不能通過Log,System.out.print打印
FileUtils.print("------------> ");
//for循環(huán)帶有BindView注解的Element
//將每個Element進行分組。分組的形式 是將在一個Activit的Element分為一組
for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
FileUtils.print("elment " + element.getSimpleName().toString());
// enClosingElement可以理解成Element
TypeElement enClosingElement = (TypeElement) element.getEnclosingElement();
// List<FieldViewBinding> 當前Activity 含有注解的成員變量集合
List<FieldViewBinding> list = targetMap.get(enClosingElement);
if (list == null) {
list = new ArrayList<>();
targetMap.put(enClosingElement, list);//
}
//得到包名
String packageName = getPackageName(enClosingElement);
//得到id
int id = element.getAnnotation(BindView.class).value();
// 得到成員變量名 TextView text; 這里得到的是text字符串
String fieldName = element.getSimpleName().toString();
// 當前成員變量的類類型 可以理解成 TextView
TypeMirror typeMirror = element.asType();
// 封裝成FieldViewBinding 類型
FieldViewBinding fieldViewBinding = new FieldViewBinding(fieldName, typeMirror, id);
list.add(fieldViewBinding);
}
//遍歷每一個Activity TypeElement代表類類型
for (Map.Entry<TypeElement, List<FieldViewBinding>> item : targetMap.entrySet()) {
List<FieldViewBinding> list = item.getValue();
if (list == null || list.size() == 0) {
continue;
}
//enClosingElement 表示 activity
TypeElement enClosingElement = item.getKey();
// 得到包名
String packageName = getPackageName(enClosingElement);
//截取字符串 MainActivity
String complite = getClassName(enClosingElement, packageName);
//遵循Javapoet規(guī)范,MainActivity為類類型 在這里封裝成ClassName
ClassName className = ClassName.bestGuess(complite);
// ViewBinder類型
ClassName viewBinder = ClassName.get("com.base.inject", "ViewBinder");
// 開始構建java文件
// 從外層包名 類名開始構建
TypeSpec.Builder result = TypeSpec.classBuilder(complite + "$$ViewBinder")
.addModifiers(Modifier.PUBLIC)
.addTypeVariable(TypeVariableName.get("T", className))
.addSuperinterface(ParameterizedTypeName.get(viewBinder, className));
// 構建方法名
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addAnnotation(Override.class)
.addParameter(className, "target", Modifier.FINAL);
// 構建方法里面的具體邏輯,這里的邏輯代表findViewById
for (int i = 0; i < list.size(); i++) {
FieldViewBinding fieldViewBinding = list.get(i);
//-->android.text.TextView
String pacckageNameString = fieldViewBinding.getType().toString();
ClassName viewClass = ClassName.bestGuess(pacckageNameString);
//$L 代表占位符 和StringFormater類似。$L代表基本類型 $T代表 類類型
methodBuilder.addStatement
("target.$L=($T)target.findViewById($L)", fieldViewBinding.getName()
, viewClass, fieldViewBinding.getResId());
}
result.addMethod(methodBuilder.build());
try {
//生成Java文件頭部的注釋說明,裝逼用
JavaFile.builder(packageName, result.build())
.addFileComment("auto create make")
.build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
//enClosingElement.getQualifiedName().toString()返回的是com.example.administrator.butterdepends.MainActivity
private String getClassName(TypeElement enClosingElement, String packageName) {
int packageLength = packageName.length() + 1;
// replace(".","$")的意思是 如果當前的enClosing為內(nèi)部類的話
// 裁剪掉包名和最后一個點號,將去掉包名后,后面還有點號則替換成$符號
return enClosingElement.getQualifiedName().toString().substring(packageLength).replace(".", "$");
}
private String getPackageName(TypeElement enClosingElement) {
//返回的是 com.example.administrator.butterknifeframwork。通過工具類獲取的
return elementUtils.getPackageOf(enClosingElement).getQualifiedName().toString();
}
}
此處重點說明process方法,第一個for循環(huán)用來拿到所有使用方class,以及該class調(diào)用的所有注解,所以建立一個map集合,TypeElement對應的是使用方class文件,List<FieldViewBinding>>代表該classs文件中用到的所有注解.最早返回的是Element形式,此處將其轉(zhuǎn)化為FieldViewBinding來存儲.
FieldViewBinding代碼
public class FieldViewBinding {
private String name;// textview
private TypeMirror type ;//--->TextView
private int resId;//--->R.id.textiew
public FieldViewBinding(String name, TypeMirror type, int resId) {
this.name = name;
this.type = type;
this.resId = resId;
}
public String getName() {
return name;
}
public TypeMirror getType() {
return type;
}
public int getResId() {
return resId;
}
}
第二個for循環(huán)是生成每個調(diào)用方class相對應的內(nèi)部類class文件,是由外往內(nèi)拼接生成的,先拼接創(chuàng)建包名類名,然后拼接構建方法名,最終的拼接效果為
代碼運行成功可以看到這個新生成的class文件,其實就是一個對應的內(nèi)部類
注意:在jar包中測試代碼不能使用log日志,所以此處使用FileUtils,以文件輸出打印,會在桌面生成一個log.txt文件顯示所有l(wèi)og信息,以便于代碼出bug時進行調(diào)試,奉上FileUtils代碼
public class FileUtils {
public static void print(String text)
{
File file=new File("C:\\Users\\Administrator\\Desktop\\log.txt");
if(!file.exists())
{
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
FileWriter fileWriter=new FileWriter(file.getAbsoluteFile(),true);
fileWriter.write(text+"\n");
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上為手寫注入ButterKnife的全部流程,其實并不復雜,因為只是簡單實現(xiàn)效果,Dagger2等也是相同的道理

結尾
奉上代碼地址https://github.com/HaowangSmith/HaowangButterKnife
相關知識鏈接ButterKnife源碼分析