Android进阶之光学习记录——注解与依赖注入框架ButterKnife的尝试
Posted 哈特谢普苏特
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android进阶之光学习记录——注解与依赖注入框架ButterKnife的尝试相关的知识,希望对你有一定的参考价值。
⚠️创建的模块是java模块而非android Library,如果创建的是后者,则无法使用AbstractProcessor
按照书上讲述的,想要自己去仿写一下butterknife
最终的项目结构如上图所示。其中annotations是放不同注解的java libary,process是注解处理器,也是个java libary,butterKnife是个android libary是为了实现最终的onbind()方法
最终生成的文件在这个位置(一开始找错了,以为是resource下 检查了很多次都没有 最终全局搜了一下发现可能因为as 和gradle版本的问题,最终生成的文件在这个路径下)
ButterKinfe的实质是代替我们自己去写findViewById以及OnClick,解放了我们的双手,使用BindView等注解实现对应的方法,但是实质上它的内部仍然是需要通过findViewById()的方式去寻找资源id,并将其与控件进行绑定,如下图所示
因此,我们仿写ButterKnife的实质也就是自己去定义类似的注解,并生成对应的文件和功能。
1.在项目中新建一个Java Library来专门存放注解,这个Library名为annotations,类似于下图的做法
该java libary中的build.gradle配置如下
plugins
id 'java-library'
dependencies
implementation fileTree(dir: 'libs', include: ['*.jar'])
java
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
2.在项目中再新建一个Java Library来存放注解处理器,这个Library名为process(注意路径,最一开始写的时候我一不小心把部分路径丢失了,导致一直无法生成正确的结果)由于process是注解处理器,因此需要手动或自动生成Processor文件,如下图所示
我使用的是Google开源的AutoService,build.gradle配置如下
plugins
id 'java-library'
dependencies
implementation fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc3'
implementation project(path: ':annotations')
java
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
并在处理器中添加@AutoService(Processor.class)
举一个简单的例子BindString
在没有ButterKnife之前,使用string,类似于
public class MainActivity extends AppCompatActivity
@BindView(R.id.text_view)
TextView textView;
//@BindString(R.string.app_name)
String meg = null;
@OnClick(R.id.text_view)
public void onClick(View view)
switch (view.getId())
case R.id.text_view:
meg = getResources().getString(R.string.app_name);
Toast.makeText(MainActivity.this,meg,Toast.LENGTH_LONG).show();
break;
使用之后
public class MainActivity extends AppCompatActivity
@BindView(R.id.text_view)
TextView textView;
@BindString(R.string.app_name)
String meg;
@OnClick(R.id.text_view)
public void onClick(View view)
switch (view.getId())
case R.id.text_view:
Toast.makeText(MainActivity.this,meg,Toast.LENGTH_LONG).show();
break;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
TextView textView = findViewById(R.id.text_view);
生成的MainActivity$ViewBinder文件
package com.example.farmedemo;
import android.view.View;
public class MainActivity$ViewBinder
public MainActivity$ViewBinder(final com.example.farmedemo.MainActivity target)
target.textView =(android.widget.TextView)target.findViewById(2131230975);
(target.findViewById(2131230975)).setOnClickListener(new View.OnClickListener()
public void onClick(View p0)
target.onClick(p0);
);
target.meg =(java.lang.String)target.getBaseContext().getResources().getString(2131623963);
AnnotationCompiler
package com.example.process;
import com.example.annotations.BindString;
import com.example.annotations.BindView;
import com.example.annotations.OnClick;
import com.google.auto.service.AutoService;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
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.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor
//生成文件的对象
Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment)
super.init(processingEnvironment);
//初始化生成文件的对象
filer = processingEnvironment.getFiler();
/**
* 声明注解处理器要处理的注解
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes()
Set<String> types = new HashSet<>();
types.add(OnClick.class.getCanonicalName());
types.add(BindView.class.getCanonicalName());
types.add(BindString.class.getCanonicalName());
return types;
/**
* 声明注解处理器支持的java源版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion()
return processingEnv.getSourceVersion();
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
//获取到程序中的注解 并且和它所在的类对象一一对应起来
Map<TypeElement, ElementForType> andParseTargets = findAndParseTargets(roundEnvironment);
//写文件
if(andParseTargets.size()>0)
Iterator<TypeElement> iterator = andParseTargets.keySet().iterator();
Writer writer = null;
while (iterator.hasNext())
TypeElement typeElement = iterator.next();
ElementForType elementForType = andParseTargets.get(typeElement);
String activityName = typeElement.getSimpleName().toString();
//获取到新类名
String newClazzName = activityName+"$ViewBinder";
//获取到包名
String packageName = getPackageName(typeElement);
//创建java文件
try
JavaFileObject sourceFile = filer.createSourceFile(
packageName+ "." + newClazzName);
writer = sourceFile.openWriter();
StringBuffer stringBuffer = getStringBuffer(packageName, newClazzName, typeElement, elementForType);
writer.write(stringBuffer.toString());
catch (IOException e)
e.printStackTrace();
finally
if(writer != null)
try
writer.close();
catch (IOException e)
e.printStackTrace();
return false;
/**
* 获取到所有的注解 以及将注解和Activity一一对应起来
* @param roundEnvironment
*/
private Map<TypeElement, ElementForType> findAndParseTargets(RoundEnvironment roundEnvironment)
Map<TypeElement, ElementForType> map = new HashMap<>();
//获取到模块中所有用到了BindView的节点
Set<? extends Element> viewElelments = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//获取到模块中所有用到了OnClick的节点
Set<? extends Element> methodElements = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
//获取到模块中所有用到了BindString的节点
Set<?extends Element> stringElements = roundEnvironment.getElementsAnnotatedWith(BindString.class);
//遍历所有的成员变量节点 一一对应的封装到ElementForType对象中
for (Element viewElelment : viewElelments)
//转换
VariableElement variableElement = (VariableElement) viewElelment;
//获取到它的上一个节点 成员变量节点的上一个节点 就是类节点
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
//获取到类节点在map中所对应的value
ElementForType elementForType = map.get(typeElement);
List<VariableElement> viewElements;
//如果这个类节点在map中已经存在
if(elementForType !=null)
//获取到类节点所对应的值中的控件节点的集合
viewElements = elementForType.getViewElements();
//如果集合为空就创建一个新的集合 然后放置到类节点所对应的值中
if(viewElements == null)
viewElements = new ArrayList<>();
elementForType.setViewElements(viewElements);
else
//如果elementForType为空 就创建一个新的
elementForType = new ElementForType();
//同时创建一个新的控件的节点的集合
viewElements = new ArrayList<>();
elementForType.setViewElements(viewElements);
if(!map.containsKey(typeElement))
map.put(typeElement,elementForType);
//最后 将遍历到的这个控件的节点的对象放置到控件的节点封装类中
viewElements.add(variableElement);
//遍历所有的点击事件的方法的节点 并且封装在对象中
for (Element methodElement : methodElements)
ExecutableElement executableElement = (ExecutableElement) methodElement;
TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();
ElementForType elementForType = map.get(typeElement);
List<ExecutableElement> executableElements;
logUtil(elementForType+"");
if(elementForType !=null)
executableElements = elementForType.getMethodElements();
if(executableElements == null)
executableElements = new ArrayList<>();
elementForType.setMethodElements(executableElements);
else
elementForType = new ElementForType();
executableElements = new ArrayList<>();
elementForType.setMethodElements(executableElements);
if(!map.containsKey(typeElement))
map.put(typeElement,elementForType);
executableElements.add(executableElement);
//遍历成员变量节点并封装
for (Element viewElelment : stringElements)
//转换
VariableElement variableElement = (VariableElement) viewElelment;
//获取到它的上一个节点 成员变量节点的上一个节点 就是类节点
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
//获取到类节点在map中所对应的value
ElementForType elementForType = map.get(typeElement);
List<VariableElement> viewElements;
//如果这个类节点在map中已经存在
if(elementForType !=null)
//获取到类节点所对应的值中的控件节点的集合
viewElements = elementForType.getStringElements();
//如果集合为空就创建一个新的集合 然后放置到类节点所对应的值中
if(viewElements == null)
viewElements = new ArrayList<>();
elementForType.setStringElements(viewElements);
else
//如果elementForType为空 就创建一个新的
elementForType = new ElementForType();
//同时创建一个新的控件的节点的集合
viewElements = new ArrayList<>();
elementForType.setStringElements(viewElements);
if(!map.containsKey(typeElement))
map.put(typeElement,elementForType);
//最后 将遍历到的这个控件的节点的对象放置到控件的节点封装类中
viewElements.add(variableElement);
return map;
/**
* 获取包名的方法
* @param typeElement
*/
public String getPackageName(Element typeElement)
//获取包名
PackageElement packageOf = processingEnv.getElementUtils().getPackageOf(typeElement);
Name qualifiedName = packageOf.getQualifiedName();
return qualifiedName.toString();
public void logUtil(String message)
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE,message);
/**
* 获取到类的拼装语句的方法
* @param packageName
* @param newClazzName
* @param typeElement
* @param elementForType
* @return
*/
public StringBuffer getStringBuffer(String packageName,String newClazzName,
TypeElement typeElement,ElementForType elementForType )
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("package " + packageName + ";\\n");
stringBuffer.append("import android.view.View;\\n");
stringBuffer.append("public class " + newClazzName + "\\n");
stringBuffer.append("public " + newClazzName + "(final " + typeElement.getQualifiedName() + " target)\\n");
if (elementForType != null && elementForType.getViewElements() != null && elementForType.getViewElements().size() > 0)
List<VariableElement> viewElements = elementForType.getViewElements();
for (VariableElement viewElement : viewElements)
//获取到类型
TypeMirror typeMirror = viewElement.asType();
//获取到控件的名字
Name simpleName = viewElement.getSimpleName();
//获取到资源ID
int resId = viewElement.getAnnotation(BindView.class).value();
stringBuffer.append("target." + simpleName + " =(" + typeMirror + ")target.findViewById(" + resId + ");\\n");
if (elementForType != null && elementForType.getMethodElements() != null && elementForType.getMethodElements().size() > 0)
List<ExecutableElement> methodElements = elementForType.getMethodElements();
for (ExecutableElement methodElement : methodElements)
int[] resIds = methodElement.getAnnotation(OnClick.class).value();
String methodName = methodElement.getSimpleName().toString();
for (int resId : resIds)
stringBuffer.append("(target.findViewById(" + resId + ")).setOnClickListener(new View.OnClickListener() \\n");
stringBuffer.append("public void onClick(View p0) \\n");
stringBuffer.append("target." + methodName + "(p0);\\n");
stringBuffer.append("\\n);\\n");
if (elementForType != null && elementForType.getStringElements() != null && elementForType.getStringElements().size() > 0)
List<VariableElement> variableElements = elementForType.getStringElements();
for (VariableElement variableElement : variableElements)
//获取到类型
TypeMirror typeMirror = variableElement.asType();
//获取到控件的名字
Name simpleName = variableElement.getSimpleName();
//获取到资源ID
int resId = variableElement.getAnnotation(BindString.class).value();
stringBuffer.append("target." + simpleName + " =(" + typeMirror + ")target.getBaseContext().getResources().getString(" + resId + ");\\n");
stringBuffer.append("\\n\\n");
//
return stringBuffer;
return stringBuffer;
ElementForType
package com.example.process;
import java.util.List;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
public class ElementForType
//所有绑定View的成员变量的节点的集合
List<VariableElement> viewElements;
//所有点击时间方法的节点的集合
List<ExecutableElement> methodElements;
List<VariableElement> stringElements;
public List<VariableElement> getStringElements()
return stringElements;
public void setStringElements(List<VariableElement> stringElements)
this.stringElements = stringElements;
public List<VariableElement> getViewElements()
return viewElements;
public void setViewElements(List<VariableElement> viewElements)
this.viewElements = viewElements;
public List<ExecutableElement> getMethodElements()
return methodElements;
public void setMethodElements(List<ExecutableElement> methodElements)
this.methodElements = methodElements;
可以看出,butterKnife为我们做了很多事情,上面的代码距离真正的butterknife相差很大,butterknife一般不用于组件化开发,因为会导致控件Id的重复。
以上是关于Android进阶之光学习记录——注解与依赖注入框架ButterKnife的尝试的主要内容,如果未能解决你的问题,请参考以下文章
Android进阶之光学习记录——注解与依赖注入框架ButterKnife的尝试
《Android进阶之光》--Material Design
入门实战资料《Android进阶解密》+《Android进阶之光》+《重构改善既有的代码第2版》电子资料学习