天天看点

手写ButterKnife视图注入,解决findViewById

在上一篇手写IOC注解,解决findViewById和点击事件(http://blog.csdn.net/wangwo1991/article/details/78811831)

博客中就实现了IOC注解,不过那里采用的是运行时注解的方式实现的,这里的手写ButterKnife将采用编译时注解的方式实现;运行时注解的方式实现过程中使用了不少反射,会对运行效率有影响,而编译时注解的方式实现对运行效率没有什么影响,像ButterKnife、Dragger、Retrofit等第三方架构都是采用编译时注解的方式实现的。

编译时注解的核心原理是依赖APT(Annotation Processing Tools)实现,编译时Annotation解析的基本原理是,在某些代码元素上(如类型、函数、字段等)添加注解,在编译时javac编译器会检测AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员可以在编译器进行相应的处理。

APT(Annotation Processing Tools)是一种处理注解的工具,它对于源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理;Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编译者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。

public class MainActivity extends AppCompatActivity {
    TextView tv2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new ViewBind().bind(this);
        tv2.setText("TEXT");
    }
    class ViewBind{
        public void bind(MainActivity activity){
            activity.tv2= (TextView) activity.findViewById(R.id.t2);
        }
    }
}
           

看上面这段代码,在MainActivity类中新建一个内部类,在该内部类中的bind方法中通过findViewById方式实例化控件,并将实例化后的空间赋值给MainActivity中对应的成员变量,这样就达到了控件初始化的目的。

编译时注解大致也是采用这种方式,只不过是在通过javac编译时检测Annotation并生成相应的class文件以达到实例化的目的。

手写ButterKnife视图注入,解决findViewById

看上图会发现,编译后,会生成MainActivity.class文件和MainActivity$$ViewBinder.class文件,同时MainActivity.class中会产生ViewBind这个内部类,调用该内部类中的bind方法进行实例化。

下面来看下具体的实现过程:

一、新建所有需要的model文件

在项目工程中建立4个model文件,两个android model文件,两个java model文件;

手写ButterKnife视图注入,解决findViewById

二、配置环境

inject-compiler model文件中build.gradle文件

手写ButterKnife视图注入,解决findViewById

inject model文件中build.gradle文件

手写ButterKnife视图注入,解决findViewById

app model文件中build.gradle文件

手写ButterKnife视图注入,解决findViewById

项目中build.gradle文件和settings.gradle文件

手写ButterKnife视图注入,解决findViewById
手写ButterKnife视图注入,解决findViewById

上面图片红色方框就是要配置的地方

三、代码编写

inject model

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

    }
}
           

inject-annotion model

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}
           

inject-compiler model

public class FileUtils {
    public static void print(String text){
        String path="C:\\Users\\Administrator\\Desktop\\log.txt";
        File file=new File(path);
        if(!file.exists()){
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            FileWriter fileWriter=new FileWriter(file.getAbsoluteFile(),true);
            fileWriter.write(text+"\n");
            fileWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
           

FileUtils就是一个写文件的工具类,编译时注解使用Log或者System.out.print输出是无效的,使用FileUtils将其输出到桌面,方便调试;

public class FieldViewBinding {
    private String name;
    private TypeMirror type;
    private int resId;

    public FieldViewBinding(String name, TypeMirror type, int resId) {
        this.name = name;
        this.type = type;
        this.resId = resId;
    }

    public String getName() {
        return name;
    }

    public TypeMirror getType() {
        return type;
    }

    public int getResId() {
        return resId;
    }
}
           
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
    private Elements elementUtils;
    private Types typeUtils;
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        filer = processingEnvironment.getFiler();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Map<TypeElement, List<FieldViewBinding>> targetMap = new HashMap<>();
        FileUtils.print("--------------------------->     ");
        for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
            FileUtils.print("element  " + element.getSimpleName().toString());
            TypeElement enClosingElement = (TypeElement) element.getEnclosingElement();
            List<FieldViewBinding> list = targetMap.get(enClosingElement);
            if (list == null) {
                list = new ArrayList<>();
                targetMap.put(enClosingElement, list);
            }
            int id = element.getAnnotation(BindView.class).value();
            String fieldName = element.getSimpleName().toString();
            TypeMirror typeMirror = element.asType();
            FileUtils.print("    typeMirror      " + typeMirror.getKind());
            FieldViewBinding fieldViewBinding = new FieldViewBinding(fieldName, typeMirror, id);
            list.add(fieldViewBinding);
        }
        for (Map.Entry<TypeElement, List<FieldViewBinding>> item : targetMap.entrySet()) {
            List<FieldViewBinding> list = item.getValue();
            if (list == null || list.size() == ) {
                continue;
            }
            TypeElement enClosingElement = item.getKey();
            String packageName = getPackageName(enClosingElement);
            String complite = getClassName(enClosingElement, packageName);

            ClassName className = ClassName.bestGuess(complite);
            ClassName viewBinder = ClassName.get("com.example.mylibrary", "ViewBinder");

            TypeSpec.Builder result = TypeSpec.classBuilder(complite + "$$ViewBinder")
                    .addModifiers(Modifier.PUBLIC)
                    .addTypeVariable(TypeVariableName.get("T", className))
                    .addSuperinterface(ParameterizedTypeName.get(viewBinder, className));

            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(TypeName.VOID)
                    .addAnnotation(Override.class)
                    .addParameter(className, "target", Modifier.FINAL);

            for (int i = ; i < list.size(); i++) {
                FieldViewBinding fieldViewBinding = list.get(i);
                String pacckageNameString = fieldViewBinding.getType().toString();
                ClassName viewClass = ClassName.bestGuess(pacckageNameString);
                methodBuilder.addStatement("target.$L=($T)target.findViewById($L)", fieldViewBinding.getName()
                        , viewClass, fieldViewBinding.getResId());
            }
            result.addMethod(methodBuilder.build());
            try {
                JavaFile.builder(packageName, result.build())
                        .addFileComment("auto create make")
                        .build().writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    private String getClassName(TypeElement enClosingElement, String packageName) {
        int packageLength = packageName.length() + ;
        return enClosingElement.getQualifiedName().toString().substring(packageLength).replace(".", "$");
    }

    private String getPackageName(TypeElement enClosingElement) {
        return elementUtils.getPackageOf(enClosingElement).getQualifiedName().toString();
    }
}
           

接下来在相应的activity中进行初始化调用就可以了

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.tv)
    TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InjectView.bind(this);
        tv.setText("IOC注解");
    }
}
           

效果如下:

手写ButterKnife视图注入,解决findViewById

源码地址:

https://pan.baidu.com/s/1bYxvKa