Annotation註解在Android的開發中的使用愈來愈廣泛,例如EventBus、ButterKnife、Dagger2等,以前使用註解的時候須要利用反射機制勢必影響到運行效率及性能,直到後來android-apt的出現經過註解根據反射機制動態編譯生成代碼的方式來解決在運行時再也不使用發射機制,不過隨着android-apt的退出再也不維護,咱們今天利用Android studio的官方插件annotationProcessor來實現一下本身的ButterKnife UI註解框架。html
Java學習之反射機制及應用場景android
整個項目分一個app、app.api、app.annotation、app.complierapi
app:整個項目的入口 用於測試註解框架app
app.annotation:主要用於申明app全部使用的UI註解框架
app.api:用於申明UI註解框架的apimaven
app.complier:用於在編譯期間經過反射機制自動生成代碼
這裏我聲明瞭一個BindView註解,聲明週期爲Class,做用域爲成員變量
@Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface BindView { int value(); }
由於這裏僅僅想要實現綁定View控件,這裏就聲明瞭一個BindView註解
注意:
app.annotation module爲java library,build.gradle配置以下
apply plugin: 'java'
sourceCompatibility =JavaVersion.VERSION_1_7
targetCompatibility =JavaVersion.VERSION_1_7
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
面向接口編程,定義一個綁定註解的接口
/** * UI綁定解綁接口 * * @param <T> */ public interface ViewBinder<T> { void bindView(T host, Object object, ViewFinder finder); void unBindView(T host); }
定義一個被綁定者查找view的接口
/** * ui提供者接口 */ public interface ViewFinder { View findView(Object object, int id); }
這裏聲明一個Activity 默認的View查找者
/** * Activity UI查找提供者 */ public class ActivityViewFinder implements ViewFinder { @Override public View findView(Object object, int id) { return ((Activity) object).findViewById(id); } }
註解框架向外提供綁定方法,這裏使用靜態類來管理
public class LCJViewBinder { private static final ActivityViewFinder activityFinder = new ActivityViewFinder();//默認聲明一個Activity View查找器 private static final Map<String, ViewBinder> binderMap = new LinkedHashMap<>();//管理保持管理者Map集合 /** * Activity註解綁定 ActivityViewFinder * * @param activity */ public static void bind(Activity activity) { bind(activity, activity, activityFinder); } /** * '註解綁定 * * @param host 表示註解 View 變量所在的類,也就是註解類 * @param object 表示查找 View 的地方,Activity & View 自身就能夠查找,Fragment 須要在本身的 itemView 中查找 * @param finder ui綁定提供者接口 */ private static void bind(Object host, Object object, ViewFinder finder) { String className = host.getClass().getName(); try { ViewBinder binder = binderMap.get(className); if (binder == null) { Class<?> aClass = Class.forName(className + "$$ViewBinder"); binder = (ViewBinder) aClass.newInstance(); binderMap.put(className, binder); } if (binder != null) { binder.bindView(host, object, finder); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } /** * 解除註解綁定 ActivityViewFinder * * @param host */ public static void unBind(Object host) { String className = host.getClass().getName(); ViewBinder binder = binderMap.get(className); if (binder != null) { binder.unBindView(host); } binderMap.remove(className); } }
優先須要自定義一個AbstractProcessor,而後Annotation生成代碼,完整的AbstractProcessor
public class LCJViewBinderProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment env){ } @Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { } @Override public Set<String> getSupportedAnnotationTypes() { } @Override public SourceVersion getSupportedSourceVersion() { } }
重要函數解說
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env)這至關於每一個處理器的主函數main()。 在這裏寫掃描、評估和處理註解的代碼,以及生成Java文件。輸入參數RoundEnviroment,可讓查詢出包含特定註解的被註解元素。
該module一樣是Java Library,build.gradle配置以下
apply plugin: 'java'
//apply plugin: 'com.github.dcendents.android-maven'
sourceCompatibility =JavaVersion.VERSION_1_7
targetCompatibility =JavaVersion.VERSION_1_7
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.7.0'
compile project(':app.annotation')
}
com.google.auto.service:auto-service:1.0-rc2 谷歌提供的Java 生成源代碼庫
com.squareup:javapoet:1.7.0 提供了各類 API 讓你用各類姿式去生成 Java 代碼文件
定義一個被註解類對象AnnotedClass,用於保存哪些被註解的對象
class AnnotatedClass { private static class TypeUtil { static final ClassName BINDER = ClassName.get("com.whoislcj.appapi", "ViewBinder"); static final ClassName PROVIDER = ClassName.get("com.whoislcj.appapi", "ViewFinder"); } private TypeElement mTypeElement; private ArrayList<BindViewField> mFields; private Elements mElements; AnnotatedClass(TypeElement typeElement, Elements elements) { mTypeElement = typeElement; mElements = elements; mFields = new ArrayList<>(); } void addField(BindViewField field) { mFields.add(field); } JavaFile generateFile() { //generateMethod MethodSpec.Builder bindViewMethod = MethodSpec.methodBuilder("bindView") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) .addParameter(TypeName.get(mTypeElement.asType()), "host") .addParameter(TypeName.OBJECT, "source") .addParameter(TypeUtil.PROVIDER, "finder"); for (BindViewField field : mFields) { // find views bindViewMethod.addStatement("host.$N = ($T)(finder.findView(source, $L))", field.getFieldName(), ClassName.get(field.getFieldType()), field.getResId()); } MethodSpec.Builder unBindViewMethod = MethodSpec.methodBuilder("unBindView") .addModifiers(Modifier.PUBLIC) .addParameter(TypeName.get(mTypeElement.asType()), "host") .addAnnotation(Override.class); for (BindViewField field : mFields) { unBindViewMethod.addStatement("host.$N = null", field.getFieldName()); } //generaClass TypeSpec injectClass = TypeSpec.classBuilder(mTypeElement.getSimpleName() + "$$ViewBinder") .addModifiers(Modifier.PUBLIC) .addSuperinterface(ParameterizedTypeName.get(TypeUtil.BINDER, TypeName.get(mTypeElement.asType()))) .addMethod(bindViewMethod.build()) .addMethod(unBindViewMethod.build()) .build(); String packageName = mElements.getPackageOf(mTypeElement).getQualifiedName().toString(); return JavaFile.builder(packageName, injectClass).build(); } }
而後再定義一個BindViewField對象用於被註解的成員變量
class BindViewField { private VariableElement mVariableElement; private int mResId; BindViewField(Element element) throws IllegalArgumentException { if (element.getKind() != ElementKind.FIELD) { throw new IllegalArgumentException(String.format("Only fields can be annotated with @%s", BindView.class.getSimpleName())); } mVariableElement = (VariableElement) element; BindView bindView = mVariableElement.getAnnotation(BindView.class); mResId = bindView.value(); if (mResId < 0) { throw new IllegalArgumentException( String.format("value() in %s for field %s is not valid !", BindView.class.getSimpleName(), mVariableElement.getSimpleName())); } } /** * 獲取變量名稱 * * @return */ Name getFieldName() { return mVariableElement.getSimpleName(); } /** * 獲取變量id * * @return */ int getResId() { return mResId; } /** * 獲取變量類型 * * @return */ TypeMirror getFieldType() { return mVariableElement.asType(); } }
上面兩個對象定義好了以後,就下來實現一下根據註解生成代碼過程
@AutoService(Processor.class) public class LCJViewBinderProcessor extends AbstractProcessor { private Filer mFiler; //文件相關的輔助類 private Elements mElementUtils; //元素相關的輔助類 private Messager mMessager; //日誌相關的輔助類 private Map<String, AnnotatedClass> mAnnotatedClassMap; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); mFiler = processingEnv.getFiler(); mElementUtils = processingEnv.getElementUtils(); mMessager = processingEnv.getMessager(); mAnnotatedClassMap = new TreeMap<>(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { mAnnotatedClassMap.clear(); try { processBindView(roundEnv); } catch (IllegalArgumentException e) { e.printStackTrace(); error(e.getMessage()); } for (AnnotatedClass annotatedClass : mAnnotatedClassMap.values()) { try { annotatedClass.generateFile().writeTo(mFiler); } catch (IOException e) { error("Generate file failed, reason: %s", e.getMessage()); } } return true; } private void processBindView(RoundEnvironment roundEnv) throws IllegalArgumentException { for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) { AnnotatedClass annotatedClass = getAnnotatedClass(element); BindViewField bindViewField = new BindViewField(element); annotatedClass.addField(bindViewField); } } private AnnotatedClass getAnnotatedClass(Element element) { TypeElement typeElement = (TypeElement) element.getEnclosingElement(); String fullName = typeElement.getQualifiedName().toString(); AnnotatedClass annotatedClass = mAnnotatedClassMap.get(fullName); if (annotatedClass == null) { annotatedClass = new AnnotatedClass(typeElement, mElementUtils); mAnnotatedClassMap.put(fullName, annotatedClass); } return annotatedClass; } private void error(String msg, Object... args) { mMessager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args)); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); types.add(BindView.class.getCanonicalName()); return types; } }
原理是現解析保存被註解的類,而後再根據註解解析被註解的成員變量,進行保存,最後根據生成java類進行寫文件
在build.gradle中添加以下配置
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:24.2.1' testCompile 'junit:junit:4.12' compile project(':app.api') compile project(':app.annotation') annotationProcessor project(':app.compiler') }
Activity中使用
public class MainActivity extends AppCompatActivity { @BindView(R.id.test) Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LCJViewBinder.bind(this); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "success", Toast.LENGTH_SHORT).show(); } }); } @Override protected void onDestroy() { super.onDestroy(); LCJViewBinder.unBind(this); } }
而後把項目從新build一下就會自動生成MainActivity$$ViewBinder類
public class MainActivity$$ViewBinder implements ViewBinder<MainActivity> { @Override public void bindView(MainActivity host, Object source, ViewFinder finder) { host.mButton = (Button)(finder.findView(source, 2131427413)); } @Override public void unBindView(MainActivity host) { host.mButton = null; } }
經過註解生成代碼在平時的開發過程當中可能不多接觸,由於目前不少開源框架幫咱們處理了這部分,若是咱們須要本身作一個使用註解的框架就須要這方面知識了,這個例子僅僅是我本身查找資源而後模仿作出來的,其實咱們項目中業務組件化之間能夠經過註解來聲明路由scheme地址,後期有時間實現一下。