三分鐘的介紹
相信很多人都在開發(fā)中都使用過(guò)ButterKnife吧窗宇!沒(méi)有用過(guò)的也都聽過(guò)。ButterKnife是一個(gè)專注于Android系統(tǒng)的View注入框架,以前總是要寫很多findViewById來(lái)找到View對(duì)象特纤,有了ButterKnife可以很輕松的省去這些步驟担映。
說(shuō)說(shuō)人家的優(yōu)點(diǎn)
簡(jiǎn)化代碼,提升開發(fā)效率
強(qiáng)大的View綁定和Click事件處理功能不會(huì)影響app運(yùn)行效率
ButterKnife采用編譯時(shí)注解的方式生成代碼叫潦,運(yùn)行是不會(huì)影響App效率
用法
GitHub地址(Star 25.1k):https://github.com/JakeWharton/butterknife
class ExampleActivity extends Activity {
@BindView(R.id.user) EditText username;
@BindView(R.id.pass) EditText password;
@BindString(R.string.login_error) String loginErrorMessage;
@OnClick(R.id.submit) void submit() {
// TODO call server...
}
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}
原理
僅僅一個(gè)注解加一行代碼可以實(shí)現(xiàn) findViewById
蝇完,它們都做了哪些事情呢?
1矗蕊、給元素加注解標(biāo)記
2短蜕、收集注解的元素生成Java類(編譯器執(zhí)行)
3、動(dòng)態(tài)注入
源碼走一波
第一步:加注解沒(méi)啥好說(shuō)的傻咖,過(guò)
第二步:收集注解生成Java類
在編譯時(shí)朋魔,通過(guò)處理注解元素,生成新的 Java 代碼類卿操,該Java代碼 里面包含了我們的 findViewById(R.id.xxx)警检、view.setonclickListener(new lis... )的這些動(dòng)作;
ButterKnifeProcessor.java(GitHub中的源碼)
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
print("process:");
print("env"+env.getRootElements());
Map<TypeElement, List<FieldBinding>> map = new HashMap<>();
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
//get the Activity
TypeElement activityElement = (TypeElement) element.getEnclosingElement();
print(" activityElement:"+ activityElement.toString());
List<FieldBinding> list = map.get(activityElement);
if (list == null) {
list = new ArrayList<>();
map.put(activityElement, list);
}
//get id
int id = element.getAnnotation(BindView.class).value();
//get fieldName
String fieldName = element.getSimpleName().toString();
//get mirror
TypeMirror typeMirror = element.asType();
print(" typeMirror:"+ typeMirror);
FieldBinding fieldBinding = new FieldBinding(fieldName, typeMirror, id);
list.add(fieldBinding);
}
for (Map.Entry<TypeElement, List<FieldBinding>> item :
map.entrySet()) {
TypeElement activityElement = item.getKey();
//get packageName
String packageName = elementUtils.getPackageOf(activityElement).getQualifiedName().toString();
//get activityName
String activityName = activityElement.getSimpleName().toString();
//transfrom type Activity with system can discern
ClassName activityClassName = ClassName.bestGuess(activityName);
ClassName viewBuild = ClassName.get(ViewBinder.class.getPackage().getName(), ViewBinder.class.getSimpleName()); //
TypeSpec.Builder result = TypeSpec.classBuilder(activityClassName + "$$ViewBinder")
.addModifiers(Modifier.PUBLIC)
.addTypeVariable(TypeVariableName.get("T", activityClassName))
.addSuperinterface(ParameterizedTypeName.get(viewBuild,activityClassName));
MethodSpec.Builder method = methodBuilder("bind") //methodName
.addModifiers(Modifier.PUBLIC) // modifier
.returns(TypeName.VOID)
.addAnnotation(Override.class)
.addParameter(activityClassName, "target", Modifier.FINAL);
//
List<FieldBinding> list = item.getValue();
for (FieldBinding fieldBinding : list) {
//
String pacageName = fieldBinding.getType().toString();
ClassName viewClass = ClassName.bestGuess(pacageName);
method.addStatement("target.$L=($T)target.findViewById($L)", fieldBinding.getName(), viewClass, fieldBinding.getResId());
}
//
result.addMethod(method.build());
try {
JavaFile.builder(packageName, result.build()).build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
第三步:就是動(dòng)態(tài)注入害淤, ButterKnife.bind(this)扇雕;
源碼中最后會(huì)通過(guò)反射加載一個(gè)***_ViewBinding這個(gè)類
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
瞧一瞧看一看MainActivity_ViewBinding .java
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view7f070022;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.button, "field 'button' and method 'click'");
target.button = Utils.castView(view, R.id.button, "field 'button'", Button.class);
view7f070022 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.click();
}
});
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.button = null;
view7f070022.setOnClickListener(null);
view7f070022 = null;
}
}
看到這里已經(jīng)完全明白了,為什么只需要短短的兩行代碼了窥摄。镶奉。。
開啟手?jǐn)]模式(三步走 模式)
第一步:我們需要?jiǎng)?chuàng)建注解崭放,在項(xiàng)目中New Module -- Java Library(Library name: injectAnnotations)
// 具體可以去了解一下注解的使用
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface InjectView {
// 我們這里也使用了android提供的一些注解
@IdRes
int value();
}
官方提供了很多特別好用的類或注解哨苛,這里說(shuō)的support annotation就是特別好的工具,多使用其中的注解币砂,需要在gradle中加入
implementation 'com.android.support:support-annotations:25.2.0'
第二步:注解生成器建峭,收集所有的注解,生成Java文件
在項(xiàng)目中New Module -- Java Library(Library name: injectCompiler)
思考一下决摧,我們的注解Module需要提供給app使用亿蒸,注解生成器Module也提供給app
那么我們需要在app的gradle中加入
implementation project(':injectAnnotations')
// annotationProcessor表示這是編譯時(shí)的注解處理器
annotationProcessor project(':injectCompiler')
注解生成器Module也需要知道我都需要處理哪些注解使碾,所以需要在gradle中引入inject-annotations
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':injectAnnotations')
implementation "com.google.auto.service:auto-service:1.0-rc4"http://自動(dòng)配置的
annotationProcessor "com.google.auto.service:auto-service:1.0-rc4" //這個(gè)在gradle5.0以上需要的
implementation 'com.squareup:javapoet:1.11.1'//方便編寫代碼的
}
// 解決build 錯(cuò)誤:編碼GBK的不可映射字符
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
注解生成器配置OK了,接下來(lái)我們創(chuàng)建 ButterKnifeProcessor.class
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
private Filer filer;
private Elements mElementUtils;
// 使用之前需要初始化三個(gè)動(dòng)作
// 1祝懂、支持的java版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
// 2票摇、當(dāng)前APT能用來(lái)處理哪些注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(InjectView.class.getCanonicalName());
supportTypes.add(InjectClick.class.getCanonicalName());
return supportTypes;
}
// 3、需要一個(gè)用來(lái)生產(chǎn)文件的對(duì)象
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 這里會(huì)把所有跟注解有關(guān)的field全部拿到,我們需要手動(dòng)進(jìn)行分類
Set<? extends Element> viewElements = roundEnvironment.getElementsAnnotatedWith(InjectView.class);
Set<? extends Element> clickElements = roundEnvironment.getElementsAnnotatedWith(InjectClick.class);
// 將所有注解集合分離出以activity為單元的注解集合
Map<Element, List<Element>> viewElementsMap = set2Map(viewElements);
// 將所有注解集合分離出以activity為單元砚蓬,再以控件ID為單元的注解
Map<Element, List<Element>> clickElementsMap = set2Map(clickElements);
//------------生成代碼,使用Java代碼生成框架-JavaPoet解析-----------
for (Map.Entry<Element, List<Element>> entry : viewElementsMap.entrySet()) {
Element activityElement = entry.getKey();
List<Element> viewFieldElementList = entry.getValue();
//得到類名的字符串
String activityName = activityElement.getSimpleName().toString();
ClassName activityClassName = ClassName.bestGuess(activityName);
// 拼裝這一行代碼:public final class xxx_ViewBinding implements IBinder
ClassName targetTypeName = ClassName.get("com.demo.james","IBinder");
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityName+"_ViewBinding")
//類名前添加public final
.addModifiers(Modifier.FINAL, Modifier.PUBLIC)
//添加類的實(shí)現(xiàn)接口矢门,并指定泛型的具體類型
.addSuperinterface(ParameterizedTypeName.get(targetTypeName, activityClassName))
//添加一個(gè)成員變量target
.addField(activityClassName, "target", Modifier.PRIVATE);
// 實(shí)現(xiàn)IBinder的方法
// 拼裝這一行代碼:public final void bind(ButterknifeActivity target)
MethodSpec.Builder bindMethod = MethodSpec.methodBuilder("bind")//和你創(chuàng)建的bind中的方法名保持一致
.addAnnotation(Override.class)
.addParameter(activityClassName, "activity")
.addStatement("this.target = activity")
.addModifiers(Modifier.FINAL, Modifier.PUBLIC);
// 存儲(chǔ)已findView的控件,為添加點(diǎn)擊事件的時(shí)候判斷是否需要重新findViewById
Map<Integer, String> findViewMap = new LinkedHashMap<>();
// 遍歷注解的字段生成findViewById
for (Element fieldElement : viewFieldElementList) {
String fieldName = fieldElement.getSimpleName().toString();
//在構(gòu)造方法中添加初始化代碼
// 在bind方法中添加
// target.btn = target.findViewById(2131230762);
// 獲取注解里面的值灰蛙,也就是id
InjectView annotation = fieldElement.getAnnotation(InjectView.class);
int resId = annotation.value();
findViewMap.put(resId, fieldName);
bindMethod.addStatement("target.$L = target.findViewById($L)",fieldName,resId);
}
List<Element> clickFieldElementList = clickElementsMap.get(activityElement);
if (clickFieldElementList != null){
for (Element fieldElement : clickFieldElementList) {
System.out.println("clickFieldElementList : "+ fieldElement.getSimpleName().toString());
// 添加onCLickListener
// target.btn.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View view) {
// target.test();
// }
// });
ClassName viewClass = ClassName.get("android.view","View");
TypeSpec onCLick = TypeSpec.anonymousClassBuilder("")
.superclass(ClassName.bestGuess("android.view.View.OnClickListener"))
.addMethod(MethodSpec.methodBuilder("onClick")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(viewClass, "view")
.returns(void.class)
.addStatement("target.$L()", fieldElement.getSimpleName().toString())
.build())
.build();
InjectClick annotation = fieldElement.getAnnotation(InjectClick.class);
int resId = annotation.value();
if (findViewMap.get(resId) == null){
bindMethod.addStatement("target.findViewById($L).setOnClickListener($L)",resId,onCLick);
}else{
bindMethod.addStatement("target.$L.setOnClickListener($L)",findViewMap.get(resId),onCLick);
}
}
}
classBuilder.addMethod(bindMethod.build());
//開始生成
try {
//得到包名
String packageName = mElementUtils.getPackageOf(activityElement)
.getQualifiedName().toString();
JavaFile.builder(packageName,classBuilder.build())
//添加類的注釋
.addFileComment("butterknife 自動(dòng)生成")
.build().writeTo(filer);
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
private Map<Element, List<Element>> set2Map(Set<? extends Element> viewElements) {
Map<Element, List<Element>> viewElementsMap = new LinkedHashMap<>();
for (Element fieldElement : viewElements) {
//element.getSimpleName()得到的是這個(gè)field注解名祟剔, Button btn; 輸出 btn
System.out.println("field name : "+fieldElement.getSimpleName());
Element activityElement = fieldElement.getEnclosingElement();
//得到的是這個(gè)field所在類的類名
System.out.println("activityElement name : "+activityElement.getSimpleName());
//以類對(duì)象為key值存儲(chǔ)一個(gè)類中所有的field到集合中
List<Element> elementList = viewElementsMap.get(activityElement);
if (elementList == null){
elementList = new ArrayList<>();
viewElementsMap.put(activityElement, elementList);
}
elementList.add(fieldElement);
}
return viewElementsMap;
}
}
第三步:動(dòng)態(tài)注入,需要提供一個(gè)給用戶使用的東東
public class JettButterKnife{
public static void bind(Activity activity){
String name = activity.getClass().getName() + "_ViewBinding" ;
try {
Class<?> clazz = Class.forName(name);
IBinder iBinder = (IBinder) clazz.newInstance();
iBinder.bind(activity);
}catch (Exception e){
e.printStackTrace();
}
}
}
public class ButterknifeActivity extends AppCompatActivity {
@InjectView(R.id.button6)
Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_butterknife);
JettButterKnife.bind(this);
btn.setText("我要賦值咯摩梧!物延。。仅父。叛薯。");
}
@InjectClick(R.id.button8)
public void test(){
Toast.makeText(this, "點(diǎn)的就是我", Toast.LENGTH_LONG).show();
}
}
基本上都已經(jīng)注釋了,自己手?jǐn)]一個(gè)BufferKnife笙纤,實(shí)現(xiàn)了findViewById與onClick的注解功能耗溜。
總結(jié)
一晃已經(jīng)凌晨?jī)牲c(diǎn)了,熬不牢J∪荨6端!
確實(shí)一個(gè)插件需要考慮的事情非常多腥椒,不動(dòng)手去做是想不到的阿宅。之前只是實(shí)現(xiàn)了findViewById,但是要正在加onClick的時(shí)候笼蛛,還需要考慮更多洒放。代碼中還有很多驗(yàn)證的地方?jīng)]有去做,比如:一個(gè)ID多個(gè)注解伐弹、ID的有效性等等拉馋,代碼還存在很多優(yōu)化的地方,今天就到這里了惨好,有問(wèn)題可以留言一起探討探討。宏悦。创淡。