ButterKnife的使用介绍及原理探究(六)

前面分析了ButterKnife的源码,了解其实现原理,那么就将原理运用于实践吧。

github地址:       点击打开链接

一、自定义注解

这里为了便于理解,只提供BindView注解。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}
二、添加注解处理器

添加ViewInjectProcessor注解处理器,看代码,

public class ViewInjectProcessor extends AbstractProcessor {
    // Storing all the annotation information under the same Class
    Map<String, BindingClass> classMap = new HashMap<>();
    private Filer mFiler;
    Elements elementUtils;
    private Messager mMessager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();
        elementUtils = processingEnv.getElementUtils();
        mMessager = processingEnv.getMessager();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

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

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // collect information
        gatherInformation(roundEnv);

        // generate java code
        try {
            for (BindingClass bindingClass : classMap.values()) {
                info("Generating file for %s", bindingClass.getFullClassName());
                bindingClass.brewJava().writeTo(mFiler);
            }
        } catch (IOException e) {
            e.printStackTrace();
            error("Generate file failed, reason: %s", e.getMessage());
        }
        return true;
    }

    ......

}

这里分别实现了init、getSupportedAnnotationTypes、getSupportedSourceVersion、process方法。

在process方法中主要实现了收集信息的gatherInformation方法和生成Java代码的brewJava方法。

具体来看gatherInformation方法;

private void gatherInformation(RoundEnvironment roundEnv) {
        classMap.clear();
        gatherBindView(roundEnv);
    }


    private void gatherBindView(RoundEnvironment roundEnv) {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
        for (Element element : elements) {
            BindingClass bindingClass = getBindingClass(element);
            BindViewField field = new BindViewField(element);
            bindingClass.addField(field);
        }
    }

这里定义了一个BindingClass类,看一下它的实现;

public class BindingClass {
    public TypeElement mClassElement; //类名
    public List<BindViewField> mFields;//成员变量
    public Elements mElementUtils;

    public BindingClass(TypeElement classElement, Elements elementUtils) {
        this.mClassElement = classElement;
        this.mElementUtils = elementUtils;
        mFields = new ArrayList<>();
    }

    public String getFullClassName() {
        return mClassElement.getQualifiedName().toString();
    }

    public void addField(BindViewField field) {
        mFields.add(field);
    }

    private String getPackageName(TypeElement type) {
        return mElementUtils.getPackageOf(type).getQualifiedName().toString();
    }

    private static String getClassName(TypeElement type, String packageName) {
        int packageLen = packageName.length() + 1;
        return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');
    }

    public JavaFile brewJava() {
        // method inject(final T host, Object source, Provider provider)
        ClassName FINDER = ClassName.get("com.muse.api.finder", "Finder");
        MethodSpec.Builder injectMethodBuilder = MethodSpec.methodBuilder("inject")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .addParameter(TypeName.get(mClassElement.asType()), "target", Modifier.FINAL)
                .addParameter(TypeName.OBJECT, "source")
                .addParameter(FINDER, "finder");


        //field
        for (BindViewField field : mFields) {
            injectMethodBuilder.addStatement("target.$N= ($T)(finder.findView(source,$L))", field.getFieldName()
                    , ClassName.get(field.getFieldType()), field.getResId());

        }


        String packageName = getPackageName(mClassElement);
        String className = getClassName(mClassElement, packageName);
        ClassName bindingClassName = ClassName.get(packageName, className);

        ClassName INJECTOR = ClassName.get("com.muse.api", "Injector");

        // generate whole class
        TypeSpec finderClass = TypeSpec.classBuilder(bindingClassName.simpleName() + "$$ViewInjector")
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(ParameterizedTypeName.get(INJECTOR, TypeName.get(mClassElement.asType())))
                .addMethod(injectMethodBuilder.build())
                .build();

        return JavaFile.builder(packageName, finderClass).build();
    }
}
显然,这个类的设计是参考了源码中的BindingSet的设计,只是没有使用设计模式。


三、提供API

对外提供InjectHelper.代码如下

public class InjectHelper {
    private static final String SUFFIX = "$$ViewInjector";
    private static final ActivityFinder ACTIVITY_FINDER = new ActivityFinder();
    private static final Map<String, Injector> FINDER_MAP = new HashMap<>();

    public static void inject(Activity host) {
        inject(host, host, ACTIVITY_FINDER);
    }

    public static void inject(Object host, Object source, Finder finder) {
        String className = host.getClass().getName();
        try {
            Injector injector = FINDER_MAP.get(className);
            if (injector == null) {
                String classFullName = host.getClass().getName() + SUFFIX;
                Class<?> finderClass = Class.forName(classFullName);
                injector = (Injector) finderClass.newInstance();
                FINDER_MAP.put(className, injector);
            }
            injector.inject(host, source, finder);
        } catch (Exception e) {
            throw new RuntimeException("Unable to inject for " + className, e);
        }
    }
}

至于API中涉及到的其他类,请参考源码。

四、使用框架

应用在使用时,需要做依赖

    implementation project(':ioc-api')
    annotationProcessor project(':ioc-compiler')
    implementation project(':ioc-annotation')

代码使用如下:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.test_btn)
    Button testBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InjectHelper.inject(this);

        testBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "Button is bind", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

Demo程序运行结果如下:



关于ButterKnife的使用及源理探究至此已经完结。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值