使用APT实现Android中View的注入

使用APT实现Android中View的注入,第1张

概述个人博客http://www.milovetingting.cn使用APT实现Android中View的注入前言APT是AnnotationProcessingTool的简写,通过在Java编译时期,处理注解,生成代码。APT在ButterKnife、Dagger2等框架中都有应用。下面通过使用APT,实现一个类似ButterKnife的简单的View注入的框架。(参考

个人博客

http://www.milovetingting.cn

使用APT实现AndroID中VIEw的注入前言

APTAnnotation Processing Tool的简写,通过在Java编译时期,处理注解,生成代码。APT在ButterKnife、Dagger2等框架中都有应用。下面通过使用APT,实现一个类似ButterKnife的简单的VIEw注入的框架。(参考Jett老师的课程)

ButterKnife的实现原理

既然准备实现类似ButterKnife的框架,那么我们就需要了解ButterKnife的实现原理。

ButterKnife的使用是从ButterKnife.bind()开始的:

@NonNull @UiThread  public static Unbinder bind(@NonNull Activity target) {    VIEw sourceVIEw = target.getwindow().getDecorVIEw();    return bind(target, sourceVIEw);  }

可以看到,bind方法中又调用了内部的bind方法

@NonNull @UiThreadpublic static Unbinder bind(@NonNull Object target, @NonNull VIEw source) {    Class<?> targetClass = target.getClass();    if (deBUG) Log.d(TAG, "Looking up binding for " + targetClass.getname());    //获取构造函数    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);    if (constructor == null) {      return Unbinder.EMPTY;    }    //noinspection TryWithIDenticalCatches Resolves to API 19+ only type.    try {        //返回实例      return constructor.newInstance(target, source);    } catch (illegalaccessexception e) {      throw new RuntimeException("Unable to invoke " + constructor, e);    } catch (InstantiationException e) {      throw new RuntimeException("Unable to invoke " + constructor, e);    } catch (InvocationTargetException e) {      Throwable cause = e.getCause();      if (cause instanceof RuntimeException) {        throw (RuntimeException) cause;      }      if (cause instanceof Error) {        throw (Error) cause;      }      throw new RuntimeException("Unable to create binding instance.", cause);    }  }

在这个bind方法中,主要通过findBindingConstructorForClass方法获取到构造函数,然后返回具体的实例。

@Nullable @CheckResult @UiThreadprivate static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {    //从缓存中查找    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);    if (bindingCtor != null || BINDINGS.containsKey(cls)) {      if (deBUG) Log.d(TAG, "HIT: Cached in binding map.");      return bindingCtor;    }    //没有缓存过的,那么通过反射来获取    String clsname = cls.getname();    if (clsname.startsWith("androID.") || clsname.startsWith("java.")        || clsname.startsWith("androIDx.")) {      if (deBUG) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");      return null;    }    try {      Class<?> bindingClass = cls.getClassLoader().loadClass(clsname + "_VIEwBinding");      //noinspection unchecked      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, VIEw.class);      if (deBUG) Log.d(TAG, "HIT: Loaded binding class and constructor.");    } catch (ClassNotFoundException e) {      if (deBUG) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getname());      //没有Class,则递归调用,从父类中查找      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());    } catch (NoSuchMethodException e) {      throw new RuntimeException("Unable to find binding constructor for " + clsname, e);    }    //放入到缓存中    BINDINGS.put(cls, bindingCtor);    return bindingCtor;  }}

findBindingConstructorForClass方法中,首先查询缓存中是否有需要的构造函数,如果没有,那么会通过反射查找,最终返回了ButterKnife生成的辅助类XXX_VIEwBinding的构造函数。

Build工程后,在生成的XXX_VIEwBinding的Java文件的构造方法中,可以看到ButterKnife帮我们自己调用了findVIEwByID

//MainActivity_VIEwBinding类中的方法 @UiThread  public MainActivity_VIEwBinding(MainActivity target, VIEw source) {    this.target = target;    target.tv = Utils.findrequiredVIEwAsType(source, R.ID.tv, "fIEld 'tv'", TextVIEw.class);  }//Utils类中方法  public static <T> T findrequiredVIEwAsType(VIEw source, @IDRes int ID, String who,      Class<T> cls) {    VIEw vIEw = findrequiredVIEw(source, ID, who);    return castVIEw(vIEw, ID, who, cls);  }//Utils类中方法  public static VIEw findrequiredVIEw(VIEw source, @IDRes int ID, String who) {    VIEw vIEw = source.findVIEwByID(ID);    if (vIEw != null) {      return vIEw;    }    String name = getResourceEntryname(source, ID);    throw new IllegalStateException("required vIEw '"        + name        + "' with ID "        + ID        + " for "        + who        + " was not found. If this vIEw is optional add '@Nullable' (fIElds) or '@Optional'"        + " (methods) annotation.");  }

可以得出结论,ButterKnife就是利用APT解析注解,在编译时生成了辅助类,用来帮助我们去调用findVIEwByID方法,从而减少手动使用findVIEwByID

实现自已的VIEw注入框架

了解了原理后,就可以自己来实现简单的VIEw注入框架了。

新建annotation模块

新建Java library类型的Module,名称为annotation,用来定义注解

新建annotation_compiler模块

然后,同样的方法新建名为annotation_compiler的模块,用来处理注解

新建Binder模块

我们还需要新建一个名为Binder的模块,用来供用户直接调用

添加依赖

新建这三个Modeule后,需要为相应的Module添加依赖。app模块需要依赖上面的三个模块,annotation_compiler需要依赖annotation。

编写annotation模块代码

在annotation模块下新建BindVIEw注解

@Target(ElementType.FIELD)@Retention(RetentionPolicy.soURCE)public @interface BindVIEw {    int value();}
编写annotation_compiler模块代码

要使用APT,需要添加相关依赖,在annotation_compiler模块下的build.gradle文件中编辑

dependencIEs {    //注册APT功能    annotationProcessor 'com.Google.auto.service:auto-service:1.0-rc4'    compileOnly 'com.Google.auto.service:auto-service:1.0-rc4'}

同步后就可以使用APT了。

在annotation_compiler模块下新建AnnotationsCompiler类,继承自AbstractProcessor

@autoService(Processor.class)public class AnnotationsCompiler extends AbstractProcessor {  //...}

需要重写三个方法

/**     * 支持的Java版本     *     * @return     */    @OverrIDe    public SourceVersion getSupportedSourceVersion() {        return SourceVersion.latestSupported();    }    /**     * 支持的注解     *     * @return     */    @OverrIDe    public Set<String> getSupportedAnnotationTypes() {        Set<String> types = new HashSet<>();        types.add(BindVIEw.class.getCanonicalname());        return types;    }    @OverrIDe    public synchronized voID init(ProcessingEnvironment processingEnvironment) {        super.init(processingEnvironment);        filer = processingEnvironment.getfiler();    }

重写process方法,主要的逻辑都在这里实现

@OverrIDe    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {        Set<? extends Element> elements = roundEnvironment.getElementsAnnotateDWith(BindVIEw.class);        //类:TypeElement        //方法:ExecutableElement        //属性:VariableElement        Map<String, List<VariableElement>> map = new HashMap<>();        for (Element element : elements) {            VariableElement variableElement = (VariableElement) element;            String activityname = variableElement.getEnclosingElement().getSimplename().toString();            List<VariableElement> variableElements = map.get(activityname);            if (variableElements == null) {                variableElements = new ArrayList<>();                map.put(activityname, variableElements);            }            variableElements.add(variableElement);        }        if (map.size() > 0) {            Writer writer = null;            Iterator<String> iterator = map.keySet().iterator();            while (iterator.hasNext()) {                String activityname = iterator.next();                List<VariableElement> variableElements = map.get(activityname);                //获取包名                TypeElement typeElement =                        (TypeElement) variableElements.get(0).getEnclosingElement();                String packagename =                        processingEnv.getElementUtils().getPackageOf(typeElement).toString();                try {                    JavafileObject sourcefile =                            filer.createSourcefile(packagename + "." + activityname +                                    "_VIEwBinding");                    writer = sourcefile.openWriter();                    writer.write("package " + packagename + ";\n");                    writer.write("import " + PACKAGE_name_BINDER + ".IBinder;\n");                    writer.write("public class "+activityname+"_VIEwBinding implements IBinder<"+packagename+"."+activityname+">{\n");                    writer.write("@OverrIDe\n");                    writer.write("public voID bind("+packagename+"."+activityname+" target){\n");                    for(VariableElement variableElement:variableElements)                    {                        //获取名字                        String variablename = variableElement.getSimplename().toString();                        //获取ID                        int ID = variableElement.getAnnotation(BindVIEw.class).value();                        //得到类型                        TypeMirror typeMirror = variableElement.asType();                        writer.write("target."+variablename+"=("+typeMirror+")target.findVIEwByID("+ID+");\n");                    }                    writer.write("\n}\n}");                } catch (Exception e) {                    e.printstacktrace();                }                finally {                    if(writer!=null)                    {                        try {                            writer.close();                        } catch (IOException e) {                            e.printstacktrace();                        }                    }                }            }        }        return false;    }
编写Binder模块代码

在Binder模块下新建IBinder

public interface IBinder<T> {    /**     * 绑定activity     *     * @param t     */    voID bind(T t);}

新建VIEwBinder类,这个类是直接供用户调用的

public class VIEwBinder {    public static voID bind(Object activity) {        String name = activity.getClass().getname() + "_VIEwBinding";        try {            Class<?> clazz = Class.forname(name);            IBinder binder = (IBinder) clazz.newInstance();            binder.bind(activity);        } catch (Exception e) {            e.printstacktrace();        }    }}
在app模块调用

编写好上面的模块后,执行Build-Rebuild Project后,可以看到生成的java类文件

在app模块的MainActivity中使用

@BindVIEw(R.ID.tv)TextVIEw tv;@OverrIDeprotected voID onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentVIEw(R.layout.activity_main);    //调用自己定义的VIEwBinder    VIEwBinder.bind(this);    tv.setText("Hi,VIEwBinder!");}

运行应用后,可以看到已经更改了TextVIEw的显示,从而证明我们自己定义的VIEwBinder是可以正常运行的。

结束

使用APT实现AndroID中VIEw的注入,具体步骤就是上面描述的。当然,这个只是一个简单的示例,如果要开发出完善的框架,还有很多需要注意和优化的,这里只是记录开发的一般流程,以便后面需要时查找资料。

源码

源码地址:https://github.com/milovetingting/Samples/tree/master/VIEwBinder

总结

以上是内存溢出为你收集整理的使用APT实现Android中View的注入全部内容,希望文章能够帮你解决使用APT实现Android中View的注入所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1063622.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-26
下一篇 2022-05-26

发表评论

登录后才能评论

评论列表(0条)

保存