[转自:https://www.zhangningning.com.cn/blog/Android/android_rentention_sample.html]

使用编译时注解方式实现View注入(Android Studio)

ButterKnife是一个强大的View注入,事件注入的框架,现模仿ButterKnife的方式,使用编译时注解实现View的注入的Demo,做个精简版的ButterKnife。

基本的原理在上一篇文章中(https://www.zhangningning.com.cn/blog/Android/android_rentention.html已经做了说明,这篇主要是实现一个在Activity中实现Bind View的注解。

先整体说明一下: 实例一共分为四个部分:

  • injectview-annotations: 这是个Java Library,,主要来定义注解。
  • injectview-compiler: 这个也是一个Java Library,一定不能为Android Library ,也不能被Android模块的dependencies中使用compile引用,不然会找不到javax相关的类。主要用来处理注解,并生成相关的代码。
  • injectview: 这个是Android Library,被android模块调用实现View的Bind.
  • app: Android Application模块,应用的主模块。

使用的库有:
auto-common:注解处理辅助类
auto-service:使用它就不需要把processor在META-INF配置了,编译时配置的Processor能够自动被发现并处理。
javapoet:辅助生成代码,能够更简单的生成.java源文件
这三个库用在injectview-compiler中,不会存在于最终的apk包中。

android-apt:用来编译injectview-compiler生成需要的代码。

四个模块的关系:
app引用injectview,
injectview引用injectview-annotations,
injectview-compiler引用injectview-annotations,
app中使用APT执行injectview-compiler

injectview-annotations

新建一个Java Library(File->New->New Module 选择Java Library) 首先定义一个注解:

package com.znn.injectview.annotations;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.FIELD)@Retention(RetentionPolicy.CLASS)public @interface BindView {    int value();}

injectview-compiler

再新建一个名为injectview-compiler的Java Library。 修改build.gradle:

apply plugin: 'java'dependencies {    compile fileTree(include: ['*.jar'], dir: 'libs')    compile 'com.google.auto:auto-common:0.6'    compile 'com.google.auto.service:auto-service:1.0-rc2'    compile 'com.squareup:javapoet:1.7.0'    compile project(':injectview-annotations')    sourceCompatibility = 1.7    targetCompatibility = 1.7}

新建一个继承AbstractProcessor的BindViewProcessor。并且使用AutoService进行注解,这样系统就能够找到这个 Processor ,并在编译时对注解进行预处理。

重写getSupportedAnnotationTypes方法,将BindView添加到支持处理的注解中。

在process函数中对注解进行处理,并生成对应辅助class文件,这个class名是当前Activity名字拼接上$$ViewBinder,后面bind的时候会使用反射找到这个类,并执行它的bind方法。相应的代码如下: BindViewProcessor.java

@AutoService(Processor.class)public final class BindViewProcessor extends AbstractProcessor{    private Elements elementUtils;    private Types typeUtils;    private Filer filer;    private static final ClassName VIEW_BINDER = ClassName.get("com.znn.injectview", "ViewBinder");    private static final String BINDING_CLASS_SUFFIX = "$$ViewBinder";//生成类的后缀 以后会用反射去取    @Override public synchronized void init(ProcessingEnvironment env) {        super.init(env);        elementUtils = env.getElementUtils();        typeUtils = env.getTypeUtils();        filer = env.getFiler();    }    @Override    public Set getSupportedAnnotationTypes() {        Set types = new LinkedHashSet<>();        types.add(BindView.class.getCanonicalName());        return types;    }    @Override public SourceVersion getSupportedSourceVersion() {        return SourceVersion.latestSupported();    }    @Override    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {        Map> targetClassMap = new LinkedHashMap<>();        for (Element element: roundEnv.getElementsAnnotatedWith(BindView.class)){            if (!SuperficialValidation.validateElement(element))                continue;            // Start by verifying common generated code restrictions.            TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();            boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)                        || isBindingInWrongPackage(BindView.class, element);            // Verify that the target type extends from View.            TypeMirror elementType = element.asType();            if (elementType.getKind() == TypeKind.TYPEVAR) {                TypeVariable typeVariable = (TypeVariable) elementType;                elementType = typeVariable.getUpperBound();            }            if (!isSubtypeOfType(elementType, "android.view.View") && !isInterface(elementType)) {                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s fields must extend from View or be an interface. (%s.%s)",                        BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),                        element.getSimpleName()));                hasError = true;            }            if (hasError) {                continue;            }            // Assemble information on the field.            List fieldViewBindingList = targetClassMap.get(enclosingElement);            if (fieldViewBindingList == null) {                fieldViewBindingList = new ArrayList<>();                targetClassMap.put(enclosingElement, fieldViewBindingList);            }            String packageName = getPackageName(enclosingElement);            TypeName targetType = TypeName.get(enclosingElement.asType());            int id = element.getAnnotation(BindView.class).value();            String fieldName = element.getSimpleName().toString();            TypeMirror fieldType = element.asType();            FieldViewBinding fieldViewBinding = new FieldViewBinding(fieldName, fieldType, id);            fieldViewBindingList.add(fieldViewBinding);        }        for (Map.Entry> item : targetClassMap.entrySet()){            List list = item.getValue();            if (list == null || list.size() == 0){                continue;            }            TypeElement enclosingElement = item.getKey();            String packageName = getPackageName(enclosingElement);            ClassName typeClassName = ClassName.bestGuess(getClassName(enclosingElement, packageName));            TypeSpec.Builder result = TypeSpec.classBuilder(getClassName(enclosingElement, packageName) + BINDING_CLASS_SUFFIX)                    .addModifiers(Modifier.PUBLIC)                    .addTypeVariable(TypeVariableName.get("T", typeClassName))                    .addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, typeClassName));            result.addMethod(createBindMethod(list, typeClassName));            try {                JavaFile.builder(packageName, result.build())                        .addFileComment(" This codes are generated automatically. Do not modify!")                        .build().writeTo(filer);            } catch (IOException e) {                e.printStackTrace();            }        }        return true;    }    private MethodSpec createBindMethod(List list, ClassName typeClassName) {        MethodSpec.Builder result = MethodSpec.methodBuilder("bind")                .addModifiers(Modifier.PUBLIC)                .returns(TypeName.VOID)                .addAnnotation(Override.class)                .addParameter(typeClassName, "target", Modifier.FINAL);        for (int i = 0; i < list.size(); i++) {            FieldViewBinding fieldViewBinding = list.get(i);            String packageString = fieldViewBinding.getType().toString();//            String className = fieldViewBinding.getType().getClass().getSimpleName();            ClassName viewClass = bestGuess(packageString);            result.addStatement("target.$L=($T)target.findViewById($L)", fieldViewBinding.getName(), viewClass, fieldViewBinding.getResId());        }        return result.build();    }    private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,                                                   String targetThing, Element element) {        boolean hasError = false;        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();        // Verify method modifiers.        Set modifiers = element.getModifiers();        if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.STATIC)) {            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s %s must not be private or static. (%s.%s)",                    annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),                    element.getSimpleName()));            hasError = true;        }        // Verify containing type.        if (enclosingElement.getKind() != ElementKind.CLASS) {            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s %s may only be contained in classes. (%s.%s)",                    annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),                    element.getSimpleName()));            hasError = true;        }        // Verify containing class visibility is not private.        if (enclosingElement.getModifiers().contains(Modifier.PRIVATE)) {            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s %s may not be contained in private classes. (%s.%s)",                    annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),                    element.getSimpleName()));            hasError = true;        }        return hasError;    }    private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,                                            Element element) {        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();        String qualifiedName = enclosingElement.getQualifiedName().toString();        if (qualifiedName.startsWith("android.")) {            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s-annotated class incorrectly in Android framework package. (%s)",                    annotationClass.getSimpleName(), qualifiedName));            return true;        }        if (qualifiedName.startsWith("java.")) {            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s-annotated class incorrectly in Java framework package. (%s)",                    annotationClass.getSimpleName(), qualifiedName));            return true;        }        return false;    }    private boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) {        if (otherType.equals(typeMirror.toString())) {            return true;        }        if (typeMirror.getKind() != TypeKind.DECLARED) {            return false;        }        DeclaredType declaredType = (DeclaredType) typeMirror;        List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();        if (typeArguments.size() > 0) {            StringBuilder typeString = new StringBuilder(declaredType.asElement().toString());            typeString.append('<');            for (int i = 0; i < typeArguments.size(); i++) {                if (i > 0) {                    typeString.append(',');                }                typeString.append('?');            }            typeString.append('>');            if (typeString.toString().equals(otherType)) {                return true;            }        }        Element element = declaredType.asElement();        if (!(element instanceof TypeElement)) {            return false;        }        TypeElement typeElement = (TypeElement) element;        TypeMirror superType = typeElement.getSuperclass();        if (isSubtypeOfType(superType, otherType)) {            return true;        }        for (TypeMirror interfaceType : typeElement.getInterfaces()) {            if (isSubtypeOfType(interfaceType, otherType)) {                return true;            }        }        return false;    }    private boolean isInterface(TypeMirror typeMirror) {        return typeMirror instanceof DeclaredType                && ((DeclaredType) typeMirror).asElement().getKind() == ElementKind.INTERFACE;    }    private String getPackageName(TypeElement type) {        return elementUtils.getPackageOf(type).getQualifiedName().toString();    }    private static String getClassName(TypeElement type, String packageName) {        int packageLen = packageName.length() + 1;        return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');    }}

生成的文件在app->build->generated->source->apt->debug下,如果没有实时显示出来 ,可以尝试clean,然后菜单栏->Build->Make Project就可以了

injectview

生成的class类会实现接口ViewBinder :

package com.znn.injectview;public interface ViewBinder<T> {  void bind(T target);}

在InjectView的bind函数在Activity中调用,这里会先找到该Activity对应的ViewBinder类,并执行它的bind方法,来对该Activity中添加注解的View进行"注入"。

InjectView.java

public class InjectView {    public static void bind(Activity activity){        String clsName = activity.getClass().getName();        try {            Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");            ViewBinder viewBinder = (ViewBinder)viewBindingClass.newInstance();            viewBinder.bind(activity);        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }    }}

app

在项目工程跟build.gradle添加APT支持:

buildscript {    repositories {        jcenter()        mavenCentral()    }    dependencies {        classpath 'com.android.tools.build:gradle:2.1.0'        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'    }}allprojects {    repositories {        jcenter()        mavenCentral()    }}

在app的模块的build.gradle加载apt,并使用apt对injectview-compiler进行处理。 另外还要讲模块injectview添加到 依赖中,以此可以访问到注解的定义和进行注入的入口。

apply plugin: 'com.android.application'apply plugin: 'com.neenbedankt.android-apt'android {    compileSdkVersion 23    buildToolsVersion "23.0.2"    defaultConfig {        applicationId "com.znn.androidrentation"        minSdkVersion 15        targetSdkVersion 22        versionCode 1        versionName "1.0"    }    compileOptions {        sourceCompatibility JavaVersion.VERSION_1_7        targetCompatibility JavaVersion.VERSION_1_7    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }}dependencies {    compile fileTree(include: ['*.jar'], dir: 'libs')    testCompile 'junit:junit:4.12'    compile 'com.android.support:appcompat-v7:23.4.0'    compile project(':injectview')    apt project(':injectview-compiler')}

在Activity中对View变量添加BindView注解,在setContentView后,使用View前添加InjectView.bind(this);进行注入。

public class MainActivity extends FragmentActivity {    @BindView(R.id.text)    TextView textView;    @BindView(R.id.text2)    TextView textView2;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        InjectView.bind(this);        textView.setText("text changged!");    }}

OK,完成

只做了Activity的注入,内部类,Fragment的都没做,仅作学习编译时注解用,正式用还是用ButterKnife吧~~~~

全部代码 https://github.com/qduningning/AndroidRentation

更多相关文章

  1. Android(安卓)的 dex2jar 和 jd-gui 反编译 apk 源代码
  2. android client随机验证码生成函数
  3. Android(安卓)Support Annotation的使用
  4. Android(安卓)Support Annotations 使用详解
  5. android OTA差分包的生成方法
  6. 网络连接之——xUtils 介绍(三)
  7. Android(安卓)studio 打包自定义apk名称
  8. 丧心病狂的Android混淆文件生成器
  9. Android(安卓)apk反编译java代码

随机推荐

  1. Android(安卓)中的图形图像的渲染
  2. Android消息处理机制
  3. android 选择本地图片并截剪图片保存到,sd
  4. Android(安卓)DataBinding的简单使用
  5. 如何隐藏滚动条在ScrollView中
  6. Android(安卓)EditText 禁止软键盘弹出
  7. Android(安卓)本地代码中的LIKELY和UNLIK
  8. 如果获取android源码
  9. Android与Java AIO实现简单Echo服务器与
  10. windows8.1下android开发环境搭建(Eclips