假设已经看完上一篇关于APT知识介绍了, 本文介绍下如何实现最简单的Butterknife的功能,主要分为以下几个步骤。
Step1:
新建Java module创建注解:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value() ;
}
Step2:
再创建一个Java module编写注解处理器,引入需要的库并引入注解module:
apply plugin: 'java-library'
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.8.0'
implementation project(':annotationLib')
}
sourceCompatibility = "7"
targetCompatibility = "7"
编写注解处理器去处理注解:
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor {
//输出日志信息
private Messager messager;
//文件写入
private Filer filer;
//获取类型工具类
private Types types;
//模块名称
private String moduleName = null;
//Element的工具
private Elements elementUtils;
// View所在包名
private static final String ViewClassName = "android.view.View";
private static final String ActivityClassName = "android.app.Activity";
/**
* 初始化各种变量
*
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
elementUtils = processingEnvironment.getElementUtils();
//获取通过Gradle文件给处理器传递的参数
moduleName = processingEnvironment.getOptions().get("route_module_name");
types = processingEnvironment.getTypeUtils();
LoggerInfo("moduleName = " + moduleName);
}
/**
* 设置支持的注解类型
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new HashSet<String>();
annotataions.add(BindView.class.getCanonicalName());
return annotataions;
}
/**
* 日志输出信息
*
* @param msg
*/
public void LoggerInfo(String msg) {
messager.printMessage(Diagnostic.Kind.NOTE, ">> " + msg);
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 注解处理的核心方法
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (roundEnvironment.processingOver()) {
return false;
}
LoggerInfo("process start");
// 获取所有被 @BindView 注解的对象
Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
// 把所有被注解的成员变量根据类搜集起来
Map<TypeElement, Set<Element>> routesMap = new HashMap<>();
// 存放注解成员变量
Set<Element> bindViews = null;
try {
if (bindViewElements != null && bindViewElements.size() > 0) {
//遍历每个 @BindView注解的元素
for (Element element : bindViewElements) {
//获取注解元素所在的类对象
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
Set<Element> elements = routesMap.get(typeElement);
if (elements != null) {
elements.add(element);
} else {
bindViews = new HashSet<>();
bindViews.add(element);
routesMap.put(typeElement, bindViews);
}
}
//根据类,生成每个类对应的文件
for (Map.Entry<TypeElement, Set<Element>> entry : routesMap.entrySet()) {
writeFile(entry.getKey(), entry.getValue());
}
}
} catch (Exception e) {
LoggerInfo(e.getMessage());
}
return true;
}
/**
* 根据注解元素及所在类的信息,生成文件并写入
*
* @param typeElement
* @param routes
*/
public void writeFile(TypeElement typeElement, Set<Element> routes) {
//Activity的类型
TypeMirror activityMirror = elementUtils.getTypeElement(ActivityClassName).asType();
TypeMirror viewMirror = elementUtils.getTypeElement(ViewClassName).asType();
//获取所在类文件的类型
TypeMirror targetMirror = elementUtils.getTypeElement(typeElement.getQualifiedName()).asType();
//需要的View参数
ParameterSpec sourceView = ParameterSpec.builder(TypeName.get(viewMirror), "sourceView")
.build();
//需要的Target参数
ParameterSpec target = ParameterSpec.builder(TypeName.get(targetMirror), "target")
.build();
LoggerInfo("start inject");
//是否是Activity
boolean isActivity = types.isSubtype(typeElement.asType(), activityMirror);
// 构建构造方法,处理Activity
MethodSpec injectForActivity = null;
if (isActivity) {
injectForActivity = createConstructorForActivity(target);
}
// 构建构造方法,处理指定的View
MethodSpec injectView = createConstructorForView(target, sourceView, routes);
//创建类
TypeSpec.Builder bindBuilder = TypeSpec.classBuilder(typeElement.getSimpleName().toString() + "$BindView")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(injectView);
//target是Activity类型的生成对应的bind方法
if (isActivity) {
bindBuilder.addMethod(injectForActivity);
}
TypeSpec bindHelper = bindBuilder.build();
JavaFile javaFile = JavaFile.builder("com.wzh.annotation", bindHelper)
.build();
//写入文件
try {
javaFile.writeTo(filer);
} catch (IOException e) {
LoggerInfo(e.toString());
e.printStackTrace();
}
}
/**
* 真正处理逻辑
*
* @param target
* @param sourceView
* @param routes
* @return
*/
private MethodSpec createConstructorForView(ParameterSpec target, ParameterSpec sourceView, Set<Element> routes) {
MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(target)
.addParameter(sourceView);
// 在方法里插入代码
CodeBlock.Builder codeBlock = CodeBlock.builder();
codeBlock.addStatement("if(target == null) { return; }");
codeBlock.addStatement("if(sourceView == null) { return; }");
methodBuilder.addCode(codeBlock.build());
for (Element element : routes) {
//变量名
String fieldName = element.getSimpleName().toString();
//变量类型
String fieldType = element.asType().toString();
//控件ID
int resId = element.getAnnotation(BindView.class).value();
methodBuilder.addStatement("target.$L = ($N)sourceView.findViewById($L)", fieldName, fieldType, resId);
}
return methodBuilder.build();
}
/**
* 处理Activity注入,省去传递View的操作
*
* @param target
* @return
*/
private MethodSpec createConstructorForActivity(ParameterSpec target) {
MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addStatement("this(target, target.getWindow().getDecorView())")
.addParameter(target);
return methodBuilder.build();
}
}
主要流程就是扫描到被注解的View, 然后分不同文件去存储,每个文件生成一个辅助类,完成赋值操作。
生成文件如下:
public final class MainActivity$BindView {
public MainActivity$BindView(MainActivity target, View sourceView) {
if(target == null) { return; };
if(sourceView == null) { return; };
target.mButton = (android.widget.Button)sourceView.findViewById(2131230800);
target.mTextView = (android.widget.TextView)sourceView.findViewById(2131230904);
target.mImageView = (android.widget.ImageView)sourceView.findViewById(2131230806);
}
public MainActivity$BindView(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
}
public final class MainFragment$BindView {
public MainFragment$BindView(MainFragment target, View sourceView) {
if(target == null) { return; };
if(sourceView == null) { return; };
target.fragmentText = (android.widget.TextView)sourceView.findViewById(2131230795);
}
}
这里区分Activity和Fragment,Actvitiy不用传递View就可以,Fragment需要传View对象。
Step3:
API调用赋值辅助类:
public class BindHelper {
public static void bind(Activity context){
String canonicalName = context.getClass().getCanonicalName();
try {
Class<?> clz = context.getClassLoader().loadClass(canonicalName+"$BindView");
Constructor constructor = clz.getConstructor(context.getClass());
constructor.newInstance(context);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* 注入View
* @param context
* @param view
*/
public static void bind(Object context, View view){
String canonicalName = context.getClass().getCanonicalName();
try {
Class<?> clz = context.getClass().getClassLoader().loadClass(canonicalName+"$BindView");
Constructor constructor = clz.getConstructor(context.getClass(),View.class);
constructor.newInstance(context, view);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
这里直接利用反射调用相关生成辅助文件的构造方法完成赋值操作。
Step4
实际使用
public class MainActivity extends AppCompatActivity {
@BindView(R.id.hello)
protected Button mButton;
@BindView(R.id.title)
protected TextView mTextView;
@BindView(R.id.image)
protected ImageView mImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BindHelper.bind(this);
inflateView();
addFragment();
}
private void inflateView() {
mImageView.setImageResource(R.drawable.icon_test);
mTextView.setText("我是通过@BindView(R.id.title)找到的");
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"我是通过@BindView找到的", Toast.LENGTH_SHORT).show();
}
});
}
private void addFragment() {
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragmentContainer,new MainFragment())
.commit();
}
}
public class MainFragment extends Fragment {
@BindView(R.id.fragmentText)
TextView fragmentText;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_main, container, false);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
BindHelper.bind(this,view);
fragmentText.setText("我是Fragment里面添加注解获取的");
}
}
运行起来,没啥问题,可以直接用我们的view对象。
运行图
这里只是演示了Butterknife的基本原理,其中还有很多细节需要更进一步处理,如:生成文件命名要能区分module、加载的时候最好缓存起来避免每次加载等等。
网友评论