從這次實戰我能學會什麼
實戰要實現的功能
完全仿寫ButterKnife的bindView註解原理,實現在Activity中控件id賦值功能
實戰看點
這次實戰是從源碼出發,仿照ButterKnife的源碼實現其中的BindView的功能,實際上就是把ButterKnife的BindView的邏輯剝離出來單獨實現,代碼量少了許多但與源碼差別不大,達到了練習註解的目的。所以我們學會了這個,你會發現,看ButterKnife的源碼突然變得簡單了
實戰基礎
瞭解並能運用AutoService,JavaPoet,AbstractProcessor
不熟悉的,請先參考我之前的文章:安卓使用註解處理器自動生成代碼操作詳解(AutoService,JavaPoet,AbstractProcessor)
下面我們就來通過實戰探究ButterKnife的註解奧祕
ButterKnife原理的簡單介紹
既然要仿寫,必然要先對仿寫對象的實現原理要有一些瞭解
這是它的簡單使用:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv1)
public TextView textView1;
private Unbinder unbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
unbinder = ButterKnife.bind(this);
//試着運行一下吧
textView1.setText("這是一個賦值測試");
}
@Override
protected void onDestroy() {
super.onDestroy();
unbinder.unbind();
}
}
運行後我們會發現生成了一個MainActivity_ViewBinding
文件,結合我上篇文章我們知道這是使用了註解處理器:
// Generated code from Butter Knife. Do not modify!
package com.jay.bindview;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.CallSuper;
import androidx.annotation.UiThread;
import butterknife.Unbinder;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;
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);
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.textView1 = null;
}
}
欸,是不是執行了MainActivity_ViewBinding
的構造方法就完成了控件賦值了,那麼這個方法在哪執行的呢,看ButterKnife.bind(this)
方法,最終會到這:
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
return constructor.newInstance(target, source);
} 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);
}
}
注意constructor.newInstance(target, source);
這行代碼,這是通過反射獲取到類然後執行這個類的構造方法,這樣一分析,實現流程是不是一目瞭然了,先通過BindView註解獲取到控件的id,控件的類型和控件父類的名稱(用於生成特定的類文件名和控件賦值),然後生成代碼,最後通過反射執行生成的類的構造方法,是不是就實現了我們想要的功能。
下面開始擼碼
BindView代碼實現
既然是仿,我們仿的像一點,先新建一個項目,創建3個library:
- bindview-annotation 注意是java library,用於存放註解
- bindview-compiler 注意還是java library,用於處理註解,生成代碼
- bindviewlib 安卓library,用於反射調用生成的代碼
bindviewlib 和bindview-compiler都需要依賴bindview-annotation
項目結構如圖所示:
我們先在bindview-annotation中創建一個BindView註解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
@IdRes int value();
}
隨後,我們就按照源碼在bindview-compiler
中新建4個類:
- BindingSet 代碼統籌管理類,處理代碼生成邏輯
- FieldViewBinding 用來保存字段的信息
- ID 用來保存id信息
- ViewBinding 用來保存FieldViewBinding 和 ID的實例,方便管理和緩存
先看FieldViewBinding
,就簡單的保存了兩個字段信息,TypeName
是字段的類型,相當於我的生成的代碼裏的TextView
,而name
相當於我們的代碼裏的textview1
final class FieldViewBinding {
//字段名稱
private final String name;
//字段類型
private final TypeName type;
FieldViewBinding(String name,TypeName type){
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public TypeName getType() {
return type;
}
public ClassName getRawType() {
if (type instanceof ParameterizedTypeName) {
return ((ParameterizedTypeName) type).rawType;
}
return (ClassName) type;
}
}
再看ID
類,value
就是我們從註解中獲取到的id,傳入到CodeBlock
中方便生成代碼
final class ID {
/**
* value及註解中的value id
*/
final CodeBlock code;
ID(int value){
this.code = CodeBlock.of("$L", value);
}
}
再來看一下ViewBinding
類,仿照源碼用了構建者模式,保存了ID
和FieldViewBinding
的值:
final class ViewBinding {
private final ID id;
@Nullable
private final FieldViewBinding fieldBinding;
private ViewBinding(ID id, @Nullable FieldViewBinding fieldBinding) {
this.id = id;
this.fieldBinding = fieldBinding;
}
public ID getId() {
return id;
}
@Nullable
public FieldViewBinding getFieldBinding() {
return fieldBinding;
}
static final class Builder {
private final ID id;
@Nullable
private FieldViewBinding fieldBinding;
Builder(ID id) {
this.id = id;
}
public void setFieldBinding(FieldViewBinding fieldBinding) {
if (this.fieldBinding != null) {
throw new AssertionError();
}
this.fieldBinding = fieldBinding;
}
public ViewBinding build() {
return new ViewBinding(id, fieldBinding);
}
}
}
最後就是我們的核心類BindingSet
了,看看是怎麼來創建代碼的吧:
final class BindingSet{
private final TypeName targetTypeName; //示例值 MainActivity
private final ClassName bindingClassName; //示例值 MainActivity_ViewBinding
private final TypeElement enclosingElement; //這是註解元素的父類Element,用於獲取父類元素
private final ImmutableList<ViewBinding> viewBindings; //保存了每一個字段的元素
private BindingSet(
TypeName targetTypeName, ClassName bindingClassName, TypeElement enclosingElement,
ImmutableList<ViewBinding> viewBindings) {
this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
this.enclosingElement = enclosingElement;
this.viewBindings = viewBindings;
}
/**
* 從這個方法開始構建代碼,這裏只實現BindView的代碼邏輯
*
* @return JavaFile
*/
JavaFile brewJava() {
TypeSpec bindingConfiguration = createType();
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
private TypeSpec createType() {
//第一步 先創建類
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(Modifier.PUBLIC)
.addOriginatingElement(enclosingElement); //設置註解處理器的源元素
//添加解綁接口
result.addSuperinterface(ClassName.get("com.jay.bindviewlib", "Unbinder"));
//添加activity字段target
result.addField(targetTypeName, "target");
//添加構造方法
result.addMethod(createBindingConstructorForActivity());
//添加找id的方法
result.addMethod(createBindingConstructor());
//添加解綁的方法
result.addMethod(createBindingUnbindMethod());
return result.build();
}
/**
* 示例:MainActivity_BindView(MainActivity target){
* this(target, target.getWindow().getDecorView())
* }
*
* @return MethodSpec
*/
private MethodSpec createBindingConstructorForActivity() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target");
builder.addStatement("this(target, target.getWindow().getDecorView())");
return builder.build();
}
private static final ClassName VIEW = ClassName.get("android.view", "View");
/**
* 創建構造方法,這個方法裏包含找id的代碼
*
* @return MethodSpec
*/
private MethodSpec createBindingConstructor() {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addModifiers(PUBLIC);
constructor.addParameter(targetTypeName, "target");
constructor.addParameter(VIEW, "source");
constructor.addStatement("this.target = target");
constructor.addCode("\n");
//這裏循環創建控件賦值代碼
for (ViewBinding binding : viewBindings) {
addViewBinding(constructor, binding);
}
return constructor.build();
}
//創建一條賦值代碼
//示例:target.textview1 = (TextView)source.findViewById(id)
//這裏的source = target.getWindow().getDecorView() target是Activity
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {
FieldViewBinding fieldBinding = requireNonNull(binding.getFieldBinding());
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName()); //添加代碼 target.textview1 =
builder.add("($T) ", fieldBinding.getType()); //添加強轉代碼
builder.add("source.findViewById($L)", binding.getId().code); //找id
result.addStatement("$L", builder.build()); //將代碼添加到方法中
}
/**
* 創建解綁的方法
*
* @return MethodSpec
*/
private MethodSpec createBindingUnbindMethod() {
MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(PUBLIC);
result.addStatement("$T target = this.target", targetTypeName);
result.addStatement("if (target == null) throw new $T($S)", IllegalStateException.class,
"Bindings already cleared.");
result.addStatement("$N = null","this.target");
result.addCode("\n");
for (ViewBinding binding : viewBindings) {
if (binding.getFieldBinding() != null) {
result.addStatement("target.$L = null", binding.getFieldBinding().getName());
}
}
return result.build();
}
/**
* 生成代碼生成的類的類名
* @return Name 規則 ActivityName__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");
}
/**
* 創建一個Builder
* @param enclosingElement 父類元素,也就是那個Activity
* @return 這裏生成了類名稱與類target
*/
static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}
ClassName bindingClassName = getBindingClassName(enclosingElement);
return new Builder(targetType, bindingClassName, enclosingElement);
}
static final class Builder {
private final TypeName targetTypeName;
private final ClassName bindingClassName;
private final TypeElement enclosingElement;
//緩存ViewBinding實例,提升性能
private final Map<ID, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
private Builder(
TypeName targetTypeName, ClassName bindingClassName, TypeElement enclosingElement) {
this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
this.enclosingElement = enclosingElement;
}
void addField(ID id, FieldViewBinding binding) {
getOrCreateViewBindings(id).setFieldBinding(binding);
}
private ViewBinding.Builder getOrCreateViewBindings(ID id) {
ViewBinding.Builder viewId = viewIdMap.get(id);
if (viewId == null) {
viewId = new ViewBinding.Builder(id);
viewIdMap.put(id, viewId);
}
return viewId;
}
BindingSet build() {
ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder();
for (ViewBinding.Builder builder : viewIdMap.values()) {
viewBindings.add(builder.build());
}
return new BindingSet(targetTypeName, bindingClassName, enclosingElement, viewBindings.build());
}
}
}
這個類完全仿照源碼編寫,只保留了Activity
的賦值邏輯,先來看用到的四個參數的作用:
-這個是控件的父類的類型名稱,用於生成target的值
private final TypeName targetTypeName;
-這個是生成的文件名稱
private final ClassName bindingClassName;
-這個是註解的父註解元素
private final TypeElement enclosingElement;
-這個就是我們的字段信息的緩存集合了
private final ImmutableList<ViewBinding> viewBindings;
我們得先獲取到這4個參數的值,這是一個構建者模式,構建者的賦值邏輯在newBuilder
方法中:
/**
* 創建一個Builder
* @param enclosingElement 父類元素,也就是那個Activity
* @return 這裏生成了類名稱與類target
*/
static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}
ClassName bindingClassName = getBindingClassName(enclosingElement);
return new Builder(targetType, bindingClassName, enclosingElement);
}
看一下getBindingClassName
方法是如何獲取到名稱的:
/**
* 生成代碼生成的類的類名
* @return Name 規則 ActivityName__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");
}
可以看到是通過父元素的getQualifiedName
方法獲取到標準格式的類名後截取的,隨後手動添加了_ViewBinding後綴。
我們還有一個viewBindings
沒有賦值,這個值需要從註解器裏去拿到,這個隨後再說,下面我們看代碼生成邏輯,入口是brewJava
方法:
JavaFile brewJava() {
TypeSpec bindingConfiguration = createType();
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
可以看到通過createType
獲取到TypeSpec
就直接生成JavaFile
了,看一下createType
方法做了什麼:
private TypeSpec createType() {
//第一步 先創建類
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(Modifier.PUBLIC)
.addOriginatingElement(enclosingElement); //設置註解處理器的源元素
//添加解綁接口
result.addSuperinterface(ClassName.get("com.jay.bindviewlib", "Unbinder"));
//添加activity字段target
result.addField(targetTypeName, "target");
//添加構造方法
result.addMethod(createBindingConstructorForActivity());
//添加找id的方法
result.addMethod(createBindingConstructor());
//添加解綁的方法
result.addMethod(createBindingUnbindMethod());
return result.build();
}
通過TypeSpec.Builder
依次添加各個部分的代碼,邏輯還是比較清晰的,需要注意的是,添加Unbinder
類傳入包名的時候要填寫正確的路徑哦,不要直接把我的包名複製進去了。看不太懂的結合生成的代碼比較着看,相信很容易就能看懂,這裏與源碼是有差別的,源碼中是使用的Utils
來尋找id,我這裏爲了方便直接生成了findviewbyid
的代碼,注意區別!!
代碼生成的邏輯寫好了,接下來就到了我們的註解處理器上了,我們的BindingSet
需要從處理器中獲取到字段信息和控件的父元素信息才能創建代碼對吧,接下來請看:
我們新建一個類名叫BindViewProcessor
:
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private Filer mFiler;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//我們可以從這裏獲取一些工具類
mFiler = processingEnvironment.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//緩存BindingSet並給BindingSet賦值
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(roundEnvironment);
//第二步,循環獲取BindingSet並執行brewJava開始繪製代碼
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava();
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 給BindingSet賦值並生成一個map
* @param env 當前元素環境
* @return 元素集合
*/
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
//這裏循環生成了BindingSet.Builder並將值放入了builderMap中
Set<? extends Element> envs = env.getElementsAnnotatedWith(BindView.class);
for (Element element : envs) {
try {
parseBindView(element, builderMap);
} catch (Exception e) {
e.printStackTrace();
}
}
//從builderMap中取出值並生成BindingSet放入bindingMap中,源碼是用的while,並有處理父類的super邏輯,這裏直接用for
Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
for (Map.Entry<TypeElement, BindingSet.Builder> entry:builderMap.entrySet()) {
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
bindingMap.put(type, builder.build());
}
return bindingMap;
}
/**
* 爲BindingSet賦值,從Element元素中獲取Activity與控件信息,並保存到BindingSet中
*/
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap) {
//獲取父類的Element
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
builder.addField(new ID(id), new FieldViewBinding(name, type));
}
/**
* 創建BindingSet 並且將BindingSet緩存到builderMap中
*/
private BindingSet.Builder getOrCreateBindingBuilder(
Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
builder = BindingSet.newBuilder(enclosingElement);
builderMap.put(enclosingElement, builder);
}
return builder;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName()); //將我們自定義的註解添加進去
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
其他3個方法基本都是樣板代碼,着重看process
方法,首先是通過findAndParseTargets
方法獲取到BindingSet
的緩存,BindingSe
t的賦值邏輯在parseBindView
方法中:
/**
* 爲BindingSet賦值,從Element元素中獲取Activity與控件信息,並保存到BindingSet中
*/
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap) {
//獲取父類的Element
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
builder.addField(new ID(id), new FieldViewBinding(name, type));
}
我們通過element.getEnclosingElement();
方法就能獲取到控件的父元素,這是BindingSet
需要的值得其中之一,隨後通過element.getAnnotation(BindView.class).value();
獲取到id
並將它保存到ID
類中,隨後通過getSimpleName
獲取到控件的名稱,也就是我們生成的代碼的那個textview1
名稱,控件類型,也就是我們的那個TextView
,可以通過element.asType()
先獲取到控件的信息類TypeMirror
,隨後通過TypeName.get
方法獲取到TypeName
的實例,知道了TypeName
,我們就相當於在代碼中持有了這個類的實例,我們就能直接把它作爲參數傳入到JavaPoet方法構建中去了,最後我們通過builder.addField(new ID(id), new FieldViewBinding(name, type));
方法傳入ID
和FieldViewBinding
類,保存到了viewBindings
中,需要的值也就都賦值完畢了。
既然值都獲取到了,我們回到process
方法,我們已經獲取到了所有用BindView
標記的控件的一個集合,接下來當然是循環調用brewJava
方法構建代碼啦:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//緩存BindingSet並給BindingSet賦值
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(roundEnvironment);
//第二步,循環獲取BindingSet並執行brewJava開始繪製代碼
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava();
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
核心代碼已經寫好了,接下來就剩下調用方法啦,我們在bindviewlib
中新建一個類BindViewHelper
,我們可以直接將ButterKnife
類的代碼搬過來,偷一波懶,最後,我們在申明一個解綁的接口:
public interface Unbinder {
@UiThread
void unbind();
Unbinder EMPTY = () -> { };
}
整個代碼就寫完了,接下來就在Activity中實驗一下吧,我們在app的build.gradle中引入這兩個包:
implementation project(':bindviewlib')
annotationProcessor project(':bindview-compiler')
在代碼中使用:
/**
* 自動生成的代碼位於build>generated>source>apt目錄下
*/
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv1)
public TextView textView1;
@BindView(R.id.tv2)
public TextView textView2;
private Unbinder unbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
unbinder = BindViewHelper.bind(this);
//試着運行一下吧
textView1.setText("這是一個賦值測試");
}
@Override
protected void onDestroy() {
super.onDestroy();
unbinder.unbind();
}
}
運行一下,我們在build>generated>source>apt
目錄下發現成功生成了文件MainActivity_ViewBinding
// Generated code from Butter Knife. Do not modify!
package com.jay.bindview;
import android.view.View;
import android.widget.TextView;
import com.jay.bindviewlib.Unbinder;
import java.lang.IllegalStateException;
import java.lang.Override;
public class MainActivity_ViewBinding implements Unbinder {
MainActivity target;
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
target.textView1 = (TextView) source.findViewById(2131165312);
target.textView2 = (TextView) source.findViewById(2131165313);
}
@Override
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;
}
}
大功告成!!
結尾
先給個demo地址,方便讀者查閱代碼:https://github.com/Jay-huangjie/BindView
希望讀者能參照我的代碼重新寫一遍,代碼並不複雜,相信對註解會有新的理解,然後你再去看ButterKnife的源碼,會發現出奇的熟悉,一下就看懂了,有任何的問題歡迎留言,關注我,不迷路