手把手教你实现Android编译期注解

今天给大家介绍Android编译期注解sdk的步骤以及注意事项,并简要分析了运行时注解以及字节码技术在生成代码上与编译期注解的不同与优劣,感兴趣的朋友一起看看吧

详细阐述了实现一个Android编译期注解sdk的步骤以及注意事项,并简要分析了运行时注解以及字节码技术在生成代码上与编译期注解的不同与优劣

一、编译期注解在开发中的重要性

从早期令人惊艳的ButterKnife,到后来的以ARouter为首的各种路由框架,再到现在谷歌大力推行的Jetpack组件,越来越多的第三方框架都在使用编译期注解这门技术,可以说不管你是想要深入研究这些第三方框架的原理 还是要成为一个Android高级开发工程师,编译期注解都是你不得不好好掌握的一门基础技术。

本文从基础的运行期注解用法开始,逐步演进到编译期注解的用法,让你真正明白编译期注解到底应该在什么场景下使用,怎么用,用了有哪些好处。

二、手写运行期注解

类似下面这种写法,当View一多得不停的findViewById 写很多行,手写起来很麻烦,我们首先尝试用运行期注解来解决这个问题,看看能不能自动处理这些findViewById的操作。

首先是工程结构,肯定要定义一个lib module。

其次定义我们的注解类:

有了这个注解的类,我们就可以在我们的MainAcitivity先用起来,虽然此时这个注解还并未起到什么作用。

到这里要稍微想一下,此时我们要做的是 通过注解来将R.id.xx 赋值给对应的field,也就是你定义的那些view对象(例如红框中的tv),对于我们的lib工程来说,因为是MainActivity 要依赖lib,自然你lib不可以依赖Main所属的app工程了,这里有2个原因:

  • A依赖B ,B依赖A的循环依赖是肯定会报错的;
  • 既然你要做一个lib 那你肯定不能依赖使用者的宿主 否则怎么能叫lib呢?

所以这个问题就变成了,lib工程 只能拿到Acitivty,拿不到宿主的MainActivity , 既然拿不到宿主的MainActivity,那我怎么知道这个activity有多少个field?这里就要用到反射了。

 public class BindingView { public static void init(Activity activity) { Field[] fields = activity.getClass().getDeclaredFields(); for (Field field : fields) { //获取 被注解 BindView annotation = field.getAnnotation(BindView.class); if (annotation != null) { int viewId = annotation.value(); field.setAccessible(true); try { field.set(activity, activity.findViewById(viewId)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } }

最后我们在宿主的MainActivity中调用一下这个方法 即可:

到这里其实有人就要问了,这个运行时注解看起来也不难啊,为啥好像用的人不是很多?问题就出在刚才反射的那堆方法里,反射大家都知道 会对Android运行时带来一些性能损耗,而这里的代码是一段循环, 也就是说这里的代码会随着你使用lib的Activity的界面复杂程度的提高 而变得越来越慢,这是一个会随着你界面复杂度提高而逐步劣化的过程, 单次反射对于今天的手机来说几乎已经不存在什么性能消耗了,但是这种for循环中使用反射还是尽量少用。

三、手写编译期注解

为了解决这个问题,就要使用编译期注解。现在我们来尝试用编译期注解来解决上述的问题。前面我们说过,运行期注解可以用反射来拿到宿主的field 从而完成需求,为了解决反射的性能问题,我们其实想要的代码是这样的:

我们可以在app 的module 中新建一个MainActivityViewBinding的类:

然后在我们的BindingView(注意我们的BindingView是在lib module下的)中来调用这个方法不就解决这个反射的问题了吗?

但是这里会有个问题 就是你既然是一个lib 你不能依赖宿主 ,所以在lib Module 中你其实拿不到 MainActivityViewBinding 这个类的,还是得利用反射。

可以看一下上面注释掉的代码,为啥不直接字符串写死?因为你是lib库你当然得是动态的,不然怎么给别人用?其实就是获取宿主的class名称然后加上一个固定的后缀ViewBinding 即可。这个时候 我们就拿到这个Binding的class了,对吧,剩下就是调用构造方法即可。

 public class BindingView { public static void init(Activity activity) { try { Class bindingClass = Class.forName(activity.getClass().getCanonicalName() + "ViewBinding"); Constructor constructor = bindingClass.getDeclaredConstructor(activity.getClass()); constructor.newInstance(activity); } catch (ClassNotFoundException | NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }

看下此时的代码结构:

有人这里要问,这里你不还是用了反射么,对! 这里虽然用了反射,但是我这里的反射只会调用一次,不管你的activity有都少field,在我这里反射方法都只会执行一次。所以性能肯定是比之前的方案要快很多倍的。接着看,虽然此刻代码可以正常运行,但是还有一个问题, 虽然我可以在lib中调用到我们app宿主的类的构造方法,但是,宿主的这个类依旧是我们手写的啊?那你这个lib库 还是没有起到任何可以让我们少写代码的作用。

这个时候就需要我们的apt 出场了,也就是编译期注解的核心部分了。我们创建一个Java Library,注意是Java lib不是android lib,然后在app module中引入他。

注意 引入的方式 不是imp了,是annotation processor ;

然后我们来修改一下lib_processor,首先创建一个 注解处理类:

再创建文件resources/META-INF/services/javax.annotation.processing.Processor ,这里要注意 文件夹创建不要写错了。

赞(0) 打赏

未经允许不得转载:0133技术站首页 » 移动