說(shuō)明:本文主要用于記錄學(xué)習(xí)筆記
原文參見(jiàn):https://mp.weixin.qq.com/s/fJKAwpqn_hXRXRS4oh1dEw
注解使用范圍:類(lèi)、方法、變量、參數(shù)都可以被注解
可以干啥:
- 利用注解可以標(biāo)記源碼以便編譯器為源碼
生成文檔和檢查代碼 - 也可以讓編譯器和注解處理器在
編譯時(shí)根據(jù)注解自動(dòng)生成代碼,甚至可以保留到運(yùn)行時(shí)以便改變運(yùn)行時(shí)的行為。
Java 內(nèi)置了一些注解,如 @Override 注解用來(lái)表明該方法是重寫(xiě)父類(lèi)方法,編譯器會(huì)負(fù)責(zé)檢查該方法與父類(lèi)方法的聲明是否一致。@Deprecated 注解用來(lái)表明該元素已經(jīng)被廢棄不建議使用了。@SuppressWarnings 注解用來(lái)表示編譯器可以忽略特定警告。
注解類(lèi)型的聲明和接口的聲明類(lèi)似,不過(guò)需要使用 @interface 和元注解(用來(lái)定義注解的注解)描述,每個(gè)方法聲明定義了注解類(lèi)型的一個(gè)元素,且方法聲明不能包含任何參數(shù)或 throws,方法的返回類(lèi)型必須是原語(yǔ)類(lèi)型、String、Class、枚舉、注解和這些類(lèi)型的數(shù)組,方法可以有默認(rèn)值,如:
public @interface RequestForEnhancement {
int id();
String synopsis();
String engineer() default "[unassigned]";
String date(); default "[unimplemented]";
}
定義完注解類(lèi)型后,就可以用它去注解一些聲明了。注解是一種特殊的修飾符,可以像 public、static 或 final 修飾符一樣使用,不過(guò)通常注解要寫(xiě)在這些修飾符之前。使用時(shí)為 @ 符號(hào)加注解類(lèi)型加元素值對(duì)列表并用括號(hào)括起來(lái),如:
@RequestForEnhancement(
id = 2868724,
synopsis = "Enable time-travel",
engineer = "Mr. Peabody",
date = "4/1/3007"
)
public static void travelThroughTime(Date destination) { ... }
注解類(lèi)型也可以沒(méi)有方法/元素,被稱(chēng)為標(biāo)記注解類(lèi)型,如:
public @interface Preliminary { }
@Preliminary public class TimeTravel { ... }
如果注解類(lèi)型只有一個(gè)元素,那么元素應(yīng)該命名為 value,使用時(shí)也就可以忽略元素名和等號(hào)了,如:
public @interface Copyright {
String value();
}
@Copyright("2002 Yoyodyne Propulsion Systems")
public class OscillationOverthruster { ... }
除了這些,很多注解還需要元注解(用來(lái)定義注解的注解)去描述,如:
@Documented//標(biāo)明改注解類(lèi)型可以被javadoc等工具文檔化
@Retention(RetentionPolicy.RUNTIME)//注解類(lèi)型保留時(shí)長(zhǎng)(枚舉)
@Target(ElementType.METHOD)//注解使用范圍
@Inherited//表示注解類(lèi)型被自動(dòng)繼承
public @interface MethodInfo {
String author() default "Peabody";
String date();
int version() default 1;
}
@Documented 表明該注解類(lèi)型可以被 javadoc 等工具
文檔化@Retention 表明該注解類(lèi)型可以
保留多長(zhǎng)時(shí)間,值為枚舉值 RetentionPolicy:
RetentionPolicy.SOURCE(只保留在源碼中,會(huì)被編譯器丟棄)
RetentionPolicy.CLASS(注解會(huì)被編譯器記錄在class文件中,但不需要被VM保留到運(yùn)行時(shí),這也是默認(rèn)的行為)
RetentionPolicy.RUNTIME(注解會(huì)被編譯器記錄在class文件中并被VM保留到運(yùn)行時(shí),所以可以通過(guò)反射獲取)
- @Target 表明該注解類(lèi)型
可以注解哪些程序元素,如果注解類(lèi)型不使用 @Target 描述那么表明默認(rèn)可以注解所有程序元素,值是枚舉數(shù)組 ElementType[]:
ElementType.TYPE(類(lèi)、接口(包括注解類(lèi)型)、枚舉的聲明)
ElementType.FIELD(字段(包括枚舉常量)的聲明)
ElementType.METHOD(方法的聲明)
ElementType.PARAMETER(形參的聲明)
ElementType.CONSTRUCTOR(構(gòu)造器的聲明)
ElementType.LOCAL_VARIABLE(本地變量的聲明)
ElementType.ANNOTATION_TYPE(注解類(lèi)型的聲明)
ElementType.PACKAGE(包的聲明)
ElementType.TYPE_PARAMETER(泛型參數(shù)的聲明)
ElementType.TYPE_USE(泛型的使用)
- @Inherited 表明
該注解類(lèi)型將被自動(dòng)繼承。也就是說(shuō),如果注解類(lèi)型被 @Inherited 注解,此時(shí)用戶(hù)查詢(xún)一個(gè)類(lèi)聲明的注解,而類(lèi)聲明沒(méi)被該注解類(lèi)型注解,那么將自動(dòng)查詢(xún)?cè)擃?lèi)父類(lèi)的注解類(lèi)型,以此類(lèi)推直到找到該注解類(lèi)型或達(dá)到頂層 Object 對(duì)象。
Android Support Library 提供了很多實(shí)用注解,如可以使用 @NonNull 注解進(jìn)行空檢查,使用 @UiThread、@WorkerThread 注解進(jìn)行線程檢查,使用 @IdRes 表明這個(gè)整數(shù)代表資源引用,還可以通過(guò) @IntDef、@StringDef 注解自定義注解來(lái)代替枚舉,如描述應(yīng)用中使用的字體文件:
public final class TypefaceManager {
//真正的變量值,以前我們可以用枚舉(內(nèi)存占用大),或者直接定義字段(填錯(cuò)了不會(huì)報(bào)錯(cuò))
public static final int FONT_TYPE_ICONIC = 0;
public static final int FONT_TYPE_IMPACT = 1;
public static final int FONT_TYPE_HELVETICA = 2;
public static final int FONT_TYPE_DIN = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({FONT_TYPE_ICONIC, FONT_TYPE_IMPACT, FONT_TYPE_HELVETICA, FONT_TYPE_DIN})
@interface FontType {
}
private Context mContext;
private SparseArray<Typeface> mTypefaceSparseArray;
public TypefaceManager(Context context) {
this.mContext = context;
this.mTypefaceSparseArray = new SparseArray<>();
}
public static void setTypeface(TextView textView, @FontType int fontType) {
Typeface localTypeface = MyApplication.getInstance().getTypefaceManager().getTypeface(fontType);
if (localTypeface != null && localTypeface != textView.getTypeface()) {
textView.setTypeface(localTypeface);
}
}
public static void setTypeface(Paint paint, @FontType int fontType) {
Typeface localTypeface = MyApplication.getInstance().getTypefaceManager().getTypeface(fontType);
if (localTypeface != null && localTypeface != paint.getTypeface()) {
paint.setTypeface(localTypeface);
}
}
public Typeface getTypeface(@FontType int fontType) {
Typeface typeface = mTypefaceSparseArray.get(fontType);
if (typeface == null) {
try {
String path = null;
if (fontType == FONT_TYPE_ICONIC) {
path = "fonts/fontawesome-webfont.ttf";
} else if (fontType == FONT_TYPE_IMPACT) {
path = "fonts/impact.ttf";
} else if (fontType == FONT_TYPE_HELVETICA) {
path = "fonts/Helvetica.ttf";
} else if (fontType == FONT_TYPE_DIN) {
path = "fonts/ptdin.ttf";
}
typeface = Typeface.createFromAsset(mContext.getAssets(), path);
this.mTypefaceSparseArray.put(fontType, typeface);
} catch (Exception e) {
e.printStackTrace();
}
}
return typeface;
}
}
下面來(lái)看看IntDef注解的定義
@Retention(RetentionPolicy.SOURCE)//只保留在源碼中,會(huì)被編譯器丟棄
@Target({ElementType.ANNOTATION_TYPE})//表示是用于注解A對(duì)直接B的聲明,可以從上方用法理解
public @interface IntDef {
int[] value() default {};
boolean flag() default false;
}
我們自己寫(xiě)個(gè)示例,對(duì)上面的用法做一個(gè)全面理解:
public static final int CHECK_METHOD_COMPLETE = 1;//校驗(yàn)方法完整性
public static final int TEST_METHOD = 2;//調(diào)試模式(測(cè)試方法調(diào)用)
public static final int RELEASE = 0;//正式環(huán)境使用
//調(diào)試模式
public static @ModeType int mModeType = RELEASE;
/**
* 調(diào)試模式 0、正式使用(默認(rèn)模式) 1、校驗(yàn)方法是否完整實(shí)現(xiàn) 2、調(diào)試方法
*/
@IntDef({CHECK_METHOD_COMPLETE, TEST_METHOD, RELEASE})
@Retention(RetentionPolicy.SOURCE)//定義僅在源碼中保留,編譯后自動(dòng)移除
public @interface ModeType {}
public static void setModeType(@ModeType int type) {
mModeType = type;
}
然后我們接著去調(diào)用setModeType方法會(huì)發(fā)現(xiàn):同樣傳遞的值都是1,第一種寫(xiě)法就會(huì)報(bào)錯(cuò),而第二種則正常,這種寫(xiě)法既簡(jiǎn)捷又不容易出錯(cuò)。

自定義注解
RUNTIME 注解&應(yīng)用示例
對(duì)于 @Retention(RetentionPolicy.RUNTIME) 的注解,注解會(huì)被編譯器記錄在 class 文件中并被 VM 保留到運(yùn)行時(shí),所以可以通過(guò)反射獲取,如:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodInfo {
String author() default "Peabody";
String date();
int version() default 1;
}
使用
public class App {
@MethodInfo(
author = “frank”,
date = "2018/02/27",
version = 2)
public String getDescription() {
return "no description";
}
}
可以寫(xiě)個(gè)工具在運(yùn)行時(shí)利用反射獲取注解
public static void main(String[] args) {
try {
Class cls = Class.forName("com.frank.App");
for (Method method : cls.getMethods()) {
MethodInfo methodInfo = method.getAnnotation(
MethodInfo.class);
if (methodInfo != null) {
System.out.println("method author:" + methodInfo.author());
System.out.println("method version:" + methodInfo.version());
System.out.println("method date:" + methodInfo.date());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
這里需要注意,注解也是可以被混淆的,如果不想被混淆,也需要額外keep。
CLASS 注解&應(yīng)用示例
對(duì)于 @Retention(RetentionPolicy.CLASS) 的注解,注解會(huì)被編譯器記錄在 class 文件中,但不需要被 VM 保留到運(yùn)行時(shí),如:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
@IdRes int value();
}
也就是說(shuō),這種編譯時(shí)注解適合用來(lái)在編譯時(shí)自動(dòng)生成代碼,這就需要 apt(Annotation Processing Tool)工具查找并執(zhí)行注解處理器(Annotation Processor)以生成源碼和文件,最終 javac 會(huì)編譯這些原始源文件和自動(dòng)生成的文件。Android Gradle 插件的 2.2 版本開(kāi)始支持注解處理器,你只需要使用 annotationProcessor 依賴(lài)注解處理器或者使用 javaCompileOptions.annotationProcessorOptions {} DSL指定注解處理器即可。定義注解處理器最簡(jiǎn)單的方式就是繼承 AbstractProcessor,在其 process 實(shí)現(xiàn)方法中實(shí)現(xiàn)注解元素的分析和源碼文件的生成。
以簡(jiǎn)化一系列 findViewById 為例:
public class MainActivity extends AppCompatActivity {
TextView mTitleTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTitleTextView = (TextView) findViewById(R.id.titleTextView);
mTitleTextView.setText("Hello World!");
}
}
使用注解處理器后可以這么寫(xiě)
public class MainActivity extends AppCompatActivity {
@BindView(R.id.titleTextView)
TextView mTitleTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SimpleButterKnife.bind(this);//一行代碼搞定所有findviewbyid
mTitleTextView.setText("Hello World!");
}
}
實(shí)現(xiàn)方式就是利用注解和注解編譯器在編譯時(shí)自動(dòng)生成一個(gè)這樣的文件:
public class MainActivity_ViewBinding {
public MainActivity target;
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
target.mTitleTextView = (TextView) source.findViewById(2131165300);
}
}
在 SimpleButterKnife.bind(this); 的實(shí)現(xiàn)中加載這個(gè)類(lèi)并執(zhí)行構(gòu)造器就可以了。 實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,先新建一個(gè) java-library (這里注意是java-library)的module:simplebutterknife-annotations,用來(lái)聲明注解:
@Retention(RetentionPolicy.CLASS)//存在字節(jié)碼中,但是不會(huì)保留到運(yùn)行時(shí)
@Target(ElementType.FIELD)//表示用于修飾字段
public @interface BindView {
@IdRes int value();//入?yún)⑹且粋€(gè)int值 IdRes表示資源類(lèi)型的int值
}
simplebutterknife-annotations module依賴(lài):
//編譯用的android api
compileOnly 'com.google.android:android:4.1.1.4'
//注解
api 'com.android.support:support-annotations:27.0.2'
再新建一個(gè)注解處理器 java-library的module:simplebutterknife-compiler,用來(lái)對(duì)注解的元素進(jìn)行分析和生成源碼文件:
simplebutterknife-compiler module依賴(lài):
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':simplebutterknife-annotations')//自定義注解
implementation 'com.google.auto:auto-common:0.10'
api 'com.squareup:javapoet:1.9.0'//用于方便的生成java類(lèi)
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'//避免手動(dòng)創(chuàng)建meta文件 參見(jiàn) http://www.itdecent.cn/p/deeb39ccdc53
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'//加上這個(gè),避免高版本gradle生成失敗
}
注解處理器代碼:
package com.example.simplebutterknife_compiler;
import com.example.simplebutterknife_annotations.BindView;
import com.google.auto.common.MoreElements;
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.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.ArrayList;
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.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
/**
* 注解處理器
* <p>
* 利用了 Google 的 AutoService 為注解處理器自動(dòng)生成 metadata 文件并將注解處理器jar文件加入構(gòu)建路徑,
* 這樣也就不需要再手動(dòng)創(chuàng)建并更新 META-INF/services/javax.annotation.processing.Processor 文件了
*參考 java sip:http://www.itdecent.cn/p/deeb39ccdc53
*
*/
@AutoService(Processor.class)
public class SimpleButterKnifeProcessor extends AbstractProcessor {
/**
* 方法指定可以支持最新的 Java 版本(直接指定最新的就可以了)
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 方法指定該注解處理器用于處理哪些注解(我們這里只處理 @BindView 注解)
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
//本例子 就是 處理BINDView
types.add(BindView.class.getCanonicalName());
return types;
}
/**
* 檢索注解元素并生成代碼的是 process 方法的實(shí)現(xiàn):
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
/**
* 需要為每個(gè)包含注解的 Activity 都生成一個(gè)對(duì)應(yīng)的 _ViewBinding 文件,
* 所以使用 Map 來(lái)存儲(chǔ)。BindingSet 存儲(chǔ) Activity 信息和它的 View 綁定信息,
* View 綁定信息(ViewBinding)包括綁定 View 的類(lèi)型、View 的 ID 以及 View 的變量名。
*/
Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
/**
* 查找所有被 @BindView 注解的程序元素(Element),為了簡(jiǎn)化,
* 這里只認(rèn)為被注解的元素是 View 字段且它的外層元素(EnclosingElement)為 Activity 類(lèi):
*/
for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
// 注解元素的外側(cè)元素,即 View 的所在 Activity 類(lèi)
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 注解的 value 值,即 View 的 id
int id = element.getAnnotation(BindView.class).value();
// 注解元素的名字,即 View 變量名
Name simpleName = element.getSimpleName();
String name = simpleName.toString();
// 注解元素的類(lèi)型,即 View 的類(lèi)型
TypeMirror elementType = element.asType();
TypeName type = TypeName.get(elementType);
//然后把這些信息存到 Activity 對(duì)應(yīng)的 View 綁定中:
BindingSet bindingSet = bindingMap.get(enclosingElement);
if (bindingSet == null) {
bindingSet = new BindingSet();
TypeMirror typeMirror = enclosingElement.asType();
TypeName targetType = TypeName.get(typeMirror);
String packageName = MoreElements.getPackage(enclosingElement).getQualifiedName().toString();
String className = enclosingElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
bindingSet.targetTypeName = targetType;
bindingSet.bindingClassName = bindingClassName;
bindingMap.put(enclosingElement, bindingSet);
}
if (bindingSet.viewBindings == null) {
bindingSet.viewBindings = new ArrayList<>();
}
ViewBinding viewBinding = new ViewBinding();
viewBinding.type = type;
viewBinding.id = id;
viewBinding.name = name;
bindingSet.viewBindings.add(viewBinding);
/**
* 確定完 Activity 信息和它對(duì)應(yīng)的 View 綁定信息后,為每個(gè) Activity 生成對(duì)應(yīng)的 XXX_ViewBinding.java 文件,
* 文件內(nèi)容就是前面所說(shuō)類(lèi)綁定類(lèi)
*
* 雖然通過(guò)字符串拼接可以拼出這樣的文件內(nèi)容,但我們還得考慮 import,還得考慮大括號(hào)和換行,
* 甚至還得考慮注釋和代碼美觀,所以利用 JavaPoet 來(lái)生成 .java 文件是個(gè)不錯(cuò)的選擇:
*/
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
TypeName targetTypeName = binding.targetTypeName;
ClassName bindingClassName = binding.bindingClassName;
List<ViewBinding> viewBindings = binding.viewBindings;
/**
* 生成binding類(lèi) public class MainActivity_ViewBinding {}
*/
TypeSpec.Builder viewBindingBuilder = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(Modifier.PUBLIC);
/**
* 生成binding類(lèi)中的成員變量 public MainActivity target;
*/
// public的target字段用來(lái)保存 Activity 引用
viewBindingBuilder.addField(targetTypeName, "target", Modifier.PUBLIC);
/**
* 生成構(gòu)造函數(shù)
*
* public MainActivity_ViewBinding(MainActivity target) {
* this(target, target.getWindow().getDecorView());
* }
*/
MethodSpec.Builder activityViewBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(targetTypeName, "target");
activityViewBuilder.addStatement("this(target, target.getWindow().getDecorView())");
viewBindingBuilder.addMethod(activityViewBuilder.build());
/**
* 生成構(gòu)造函數(shù)2
*
* public MainActivity_ViewBinding(MainActivity target, View source) {
* this.target = target;
*
* target.mTitleTextView = (TextView) source.findViewById(2131165300);
* }
*/
MethodSpec.Builder viewBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(targetTypeName, "target")
.addParameter(ClassName.get("android.view", "View"), "source");
viewBuilder.addStatement("this.target = target");
viewBuilder.addCode("\n");
for (ViewBinding temp : viewBindings) {//這里就是遍歷所有要綁定的元素,進(jìn)行findviewbyId
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", temp.name);
builder.add("($T) ", temp.type);
builder.add("source.findViewById($L)", CodeBlock.of("$L", temp.id));
viewBuilder.addStatement("$L", builder.build());
}
viewBindingBuilder.addMethod(viewBuilder.build());
// 輸出 Java 文件
JavaFile javaFile = JavaFile.builder(bindingClassName.packageName(), viewBindingBuilder.build())
.build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
}
}
}
return false;
}
class BindingSet {
TypeName targetTypeName;
ClassName bindingClassName;
List<ViewBinding> viewBindings;
}
class ViewBinding {
TypeName type;
int id;
String name;
}
}
在 app module 中需要依賴(lài)注解 module 并注冊(cè)注解處理器 module:
dependencies {
...
api project(':simplebutterknife-annotations')
annotationProcessor project(':simplebutterknife-compiler')
}
這里有個(gè)注意點(diǎn),如果我們需要在kotlin代碼使用對(duì)用注解,需要做一些引入調(diào)整
首先在主工程引入kapt插件
apply plugin: 'kotlin-kapt'//引入kapt插件
然后把 annotationProcessor 變更為 kapt
dependencies {
...
api project(':simplebutterknife-annotations')
kapt project(':simplebutterknife-compiler')
}
app module 中的工具類(lèi) SimpleButterKnife 的 bind 方法只需要加載這個(gè)自動(dòng)生成的類(lèi)并執(zhí)行它的構(gòu)造器就行了:
public final class SimpleButterKnife {
public static void bind(Activity target) {
View sourceView = target.getWindow().getDecorView();
Class<?> targetClass = target.getClass();
String targetClassName = targetClass.getName();
Constructor constructor;
try {
Class<?> bindingClass = targetClass.getClassLoader().loadClass(targetClassName + "_ViewBinding");
constructor = bindingClass.getConstructor(targetClass, View.class);
} catch (ClassNotFoundException e) {
// TODO Not found. should try search its superclass
throw new RuntimeException("Not found. should try search its superclass of " + targetClassName, e);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + targetClassName, e);
}
try {
constructor.newInstance(target, sourceView);
} 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);
}
}
}
重新構(gòu)建下工程,就可以在 build\generated\source\apt\debug 目錄中查看自動(dòng)生成的文件了:
package com.frank.simplebutterknife;
import android.view.View;
import android.widget.TextView;
public class MainActivity_ViewBinding {
public MainActivity target;
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
target.mTitleTextView = (TextView) source.findViewById(2131165300);
}
}
重新構(gòu)建下工程,就可以在 build\generated\source\apt\debug 目錄中查看自動(dòng)生成的文件了:

自測(cè)demo練習(xí)部分
demoall-sample工程

除了上面的butterknife,還有簡(jiǎn)單的注解處理并生成map集合的應(yīng)用處理,具體參見(jiàn)筆記代碼