假設(shè)已經(jīng)看完上一篇關(guān)于APT知識(shí)介紹了, 本文介紹下如何實(shí)現(xiàn)最簡(jiǎn)單的Butterknife的功能,主要分為以下幾個(gè)步驟漫萄。
Step1:
新建Java module創(chuàng)建注解:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value() ;
}
Step2:
再創(chuàng)建一個(gè)Java module編寫注解處理器焙格,引入需要的庫(kù)并引入注解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();
//獲取通過(guò)Gradle文件給處理器傳遞的參數(shù)
moduleName = processingEnvironment.getOptions().get("route_module_name");
types = processingEnvironment.getTypeUtils();
LoggerInfo("moduleName = " + moduleName);
}
/**
* 設(shè)置支持的注解類型
*
* @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 注解的對(duì)象
Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
// 把所有被注解的成員變量根據(jù)類搜集起來(lái)
Map<TypeElement, Set<Element>> routesMap = new HashMap<>();
// 存放注解成員變量
Set<Element> bindViews = null;
try {
if (bindViewElements != null && bindViewElements.size() > 0) {
//遍歷每個(gè) @BindView注解的元素
for (Element element : bindViewElements) {
//獲取注解元素所在的類對(duì)象
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);
}
}
//根據(jù)類,生成每個(gè)類對(duì)應(yīng)的文件
for (Map.Entry<TypeElement, Set<Element>> entry : routesMap.entrySet()) {
writeFile(entry.getKey(), entry.getValue());
}
}
} catch (Exception e) {
LoggerInfo(e.getMessage());
}
return true;
}
/**
* 根據(jù)注解元素及所在類的信息,生成文件并寫入
*
* @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參數(shù)
ParameterSpec sourceView = ParameterSpec.builder(TypeName.get(viewMirror), "sourceView")
.build();
//需要的Target參數(shù)
ParameterSpec target = ParameterSpec.builder(TypeName.get(targetMirror), "target")
.build();
LoggerInfo("start inject");
//是否是Activity
boolean isActivity = types.isSubtype(typeElement.asType(), activityMirror);
// 構(gòu)建構(gòu)造方法健田,處理Activity
MethodSpec injectForActivity = null;
if (isActivity) {
injectForActivity = createConstructorForActivity(target);
}
// 構(gòu)建構(gòu)造方法,處理指定的View
MethodSpec injectView = createConstructorForView(target, sourceView, routes);
//創(chuàng)建類
TypeSpec.Builder bindBuilder = TypeSpec.classBuilder(typeElement.getSimpleName().toString() + "$BindView")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(injectView);
//target是Activity類型的生成對(duì)應(yīng)的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, 然后分不同文件去存儲(chǔ)妓局,每個(gè)文件生成一個(gè)輔助類,完成賦值操作呈宇。
生成文件如下:
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);
}
}
這里區(qū)分Activity和Fragment好爬,Actvitiy不用傳遞View就可以,F(xiàn)ragment需要傳View對(duì)象甥啄。
Step3:
API調(diào)用賦值輔助類:
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();
}
}
}
這里直接利用反射調(diào)用相關(guān)生成輔助文件的構(gòu)造方法完成賦值操作存炮。
Step4
實(shí)際使用
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("我是通過(guò)@BindView(R.id.title)找到的");
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"我是通過(guò)@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里面添加注解獲取的");
}
}
運(yùn)行起來(lái),沒啥問(wèn)題型豁,可以直接用我們的view對(duì)象僵蛛。
這里只是演示了Butterknife的基本原理,其中還有很多細(xì)節(jié)需要更進(jìn)一步處理迎变,如:生成文件命名要能區(qū)分module充尉、加載的時(shí)候最好緩存起來(lái)避免每次加載等等。