IOC注入框架设计<二>-------ButterKnife框架再来审视和Android Studio插件技术入门
在上一次https://www.cnblogs.com/webor2006/p/12374975.html中对于通过实践的方式来对于IOC的思想有了一定的了解,接下来继续围绕IOC进行进一步的学习。
重撸ButterKnife框架:
关于ButterKnife的手写其实在之前https://www.cnblogs.com/webor2006/p/10582178.html已经详细剖析过了,由于它其实也是IOC思想的一种具体体现,所以准备重新手写一次它,继续温故知新,实现上跟上一次肯定会有不同的地方,但整体思路是一样的,另外这里不会像上一次从0来阐述其步骤了,主要是重挼一次整个框架的实现流程,话不多说正式进入撸码环节:
项目架子搭建:
在之前的实现中的工程结构是这样的,回忆一下:
我们知道对于lib-processor主要是做注解处理器的入口声明的,其实像这可以利用一个注解就可以代替,如之前https://www.cnblogs.com/webor2006/p/12275672.html在手写路由框架中对于注解处理的注册,只需要在我们的注解处理类中增加一个注解既可:
所以,咱们这次写的项目结构则为:
所以咱们新建一个工程:
然后再来新建lib-annotationprocessor,注意它是一个Java Library:
接下来再来新建lib_annotations,同样也是Java Library:
接下来添加好依赖关系,这里就不过多的解释:
关于api跟implementation的区别这里就不多说了,接下来还需要配置注解处理器的依赖,如下:
至此整个基础框架的配置则到这。
具体实现:
新建Annotation:
先新建一个注解,用来标注到View上的:
此时咱们就可以应用到我们的Activity中:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
注解处理器(Annotation Processor)编写:
前置条件准备:
接下来则新建个注解处理器类:
那这个注解处理器得要注册才能被IDE有识别,之前咱们用过手动的方式,也就是将这个文件声明到指定的目录下:
而更加简单的方式则是用注解的方式来使用,要想使用这个自动注解这里需要依赖于组件,如下:
此时背后就可以由注解处理器来动态进行注解处理器的注册了,接下来则来完成我们注解器处理的逻辑:
首先咱们初始化生成文件的对象:
然后再来声明咱们要处理的注解类型:
再来指定一下JDK的版本:
process()方法实现:
这里面则是生成我们想要的代码的地方了,在正式编写之前先简单挼一下要生成的文件长啥样?
生成之后,最终咱们再通过反射来调用这个bind方法既达到View的初始化注入了,所以先来定义一下这个IBinder接口:
接下来则一点点来实现代码的生成逻辑:
首先先来获取代码中标有BindView注解的所有元素,然后将其缓存到集合当中,如下:
@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //得到程序中所有写了BindView注解的元素的集合 //其中Element有多种类型,如:类元素(TypeElement)、可执行元素(ExecutableElement)、属性元素(VariableElement) //很明显这里得到的是VariableElement元素,因为BindView只能用在属性上面。 Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class); //定义一个MAP来将获得指定注解的元素全收集下来 Map<String, List<VariableElement>> map = new HashMap<>(); //开始搜集 for (Element element : elementsAnnotatedWith) { VariableElement variableElement = (VariableElement) element; //获取activity的名字 String activityName = variableElement.getEnclosingElement().getSimpleName().toString(); List<VariableElement> elementList = map.get(activityName); if (elementList == null) { elementList = new ArrayList<>(); map.put(activityName, elementList); } elementList.add(variableElement); } return false; }
其中有个API需要再解释一下:
拿代码来说明:
也就是说这个getEnclosingElement()获得的是包裹该元素的元素。好继续:
接下来则根据已经过滤出来的注解元素来生成对应的代码,一说到代码的生成就会想到javapoet这个框架,在之前的学习中也是这样弄的,这里为了巩固基础打算不采用三方框架来生成了,而是采用流的原始方式一行行手写:
@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //得到程序中所有写了BindView注解的元素的集合 //其中Element有多种类型,如:类元素(TypeElement)、可执行元素(ExecutableElement)、属性元素(VariableElement) //很明显这里得到的是VariableElement元素,因为BindView只能用在属性上面。 Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class); //定义一个MAP来将获得指定注解的元素全收集下来 Map<String, List<VariableElement>> map = new HashMap<>(); //开始搜集 for (Element element : elementsAnnotatedWith) { VariableElement variableElement = (VariableElement) element; //获取activity的名字 String activityName = variableElement.getEnclosingElement().getSimpleName().toString(); List<VariableElement> elementList = map.get(activityName); if (elementList == null) { elementList = new ArrayList<>(); map.put(activityName, elementList); } elementList.add(variableElement); } //开始遍历有效注解元素进行代码的生成 if (map.size() > 0) { //开始写入文件,每一个activity都要生成一个对应的文件 Iterator<String> iterator = map.keySet().iterator(); while (iterator.hasNext()) { String activityName = iterator.next(); List<VariableElement> elementList = map.get(activityName); //获取包名 TypeElement enclosingElement = (TypeElement) elementList.get(0).getEnclosingElement(); String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString(); //TODO:开始生成文件 } } return false; }
接下来则开始写生成源代码的逻辑:
接下来再来对元素进行遍历生成findViewById的代码了:
@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //得到程序中所有写了BindView注解的元素的集合 //其中Element有多种类型,如:类元素(TypeElement)、可执行元素(ExecutableElement)、属性元素(VariableElement) //很明显这里得到的是VariableElement元素,因为BindView只能用在属性上面。 Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class); //定义一个MAP来将获得指定注解的元素全收集下来 Map<String, List<VariableElement>> map = new HashMap<>(); //开始搜集 for (Element element : elementsAnnotatedWith) { VariableElement variableElement = (VariableElement) element; //获取activity的名字 String activityName = variableElement.getEnclosingElement().getSimpleName().toString(); List<VariableElement> elementList = map.get(activityName); if (elementList == null) { elementList = new ArrayList<>(); map.put(activityName, elementList); } elementList.add(variableElement); } //开始遍历有效注解元素进行代码的生成 if (map.size() > 0) { //开始写入文件,每一个activity都要生成一个对应的文件 Iterator<String> iterator = map.keySet().iterator(); while (iterator.hasNext()) { String activityName = iterator.next(); List<VariableElement> elementList = map.get(activityName); //获取包名 TypeElement enclosingElement = (TypeElement) elementList.get(0).getEnclosingElement(); String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString(); Writer writer = null; try { //生成文件 //先生成"包名.MainActivity_ViewBinding"规则的java文件 JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding"); writer = sourceFile.openWriter(); //开始要进行生成源代码的字符串拼接啦 // package com.android.butterknifearcstudy; writer.write("package " + packageName + ";\n"); // import com.android.butterknifearcstudy.IBinder; writer.write("import " + packageName + ".IBinder;\n"); // public class MainActivity_ViewBinding implements IBinder<com.android.butterknifearcstudy.MainActivity>{ writer.write("public class " + activityName + "_ViewBinding implements IBinder<" + packageName + "." + activityName + ">{\n"); // @Override writer.write("@Override\n"); // public void bind(com.android.butterknifearcstudy.MainActivity target) { writer.write("public void bind(" + packageName + "." + activityName + " target){\n"); // 接下来则需要根据每个注解字段生成"target.tvText=(android.widget.TextView)target.findViewById(2131165325);"的代码 for (VariableElement variableElement : elementList) { //获取控件的名字 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}}"); } catch (Exception e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } } } return false; }
好,整个注解处理器的代码就已经编写完了,接下来编译验证一下是否好使:
报错了。。貌似我们的View是一个私有访问:
变为public的再编译一下:
接下来看一下代码有木有生成:
注入绑定:
接下来则需要将注解处理器生成的类进行调用一下,如下:
接一下则通过反射来调用注解处理器生成的类,比较简单贴出代码:
就这么简单,接下来运行看一下:
一切正常,关于ButterKnife的手写先到这。
Android Studio插件技术入门:
对于Android Studio中的各个菜单功能,有没有思考过是怎么实现的呢?比如我们经常会用这个菜单项:
还有我们可以在Android Studio中装各种插件,比如:
其实都是接下来要进行探究的一门技术,也就是写Android Studio的插件,听起来非常的高大尚,其实也不是非常难的,所以接下来准备解锁一下这个技能。
编写插件:
我们知道Android Studio是属于IntelliJ公司出品的,而对于Java开发还有一款具有名的IDE,就叫做“IntelliJ IDEA”:
咱们不是要写Android Studio的插件么?为啥要提到这个IDE?因为插件的编写则要通过这个IDE来完成,所以咱们先来打开它,然后新建一个插件工程:
其中这个配置文件中会有插件的一些信息,咱们可以修改一下:
<idea-plugin> <id>com.your.company.unique.plugin.id</id> <name>Plugin display name here</name> <version>1.0</version> <vendor email="support@yourcompany.com" url="http://www.yourcompany.com">YourCompany</vendor> <description><![CDATA[ Enter short description for your plugin here.<br> <em>most HTML tags may be used</em> ]]></description> <change-notes><![CDATA[ Add change notes here.<br> <em>most HTML tags may be used</em> ]]> </change-notes> <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description --> <idea-version since-build="173.0"/> <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html on how to target different products --> <depends>com.intellij.modules.platform</depends> <extensions defaultExtensionNs="com.intellij"> <!-- Add your extensions here --> </extensions> <actions> <!-- Add your actions here --> </actions> </idea-plugin>
其实这些配置的信息最终都会在插件的安装介绍中体现出来,比如咱们拿一个已经安装在Android Studio的插件为例:
好,对于我们开发插件而言,主要实现的地方是在这个结点:
而它里面的内容则决定了咱们插件最终作用在哪个菜单项里面,这里不用手工去在这个结果里敲代码的,而是有向导帮我们生成这块的内容,下面咱们来定义一下:
然后我们编写插件的代码则在这里写:
其中actionPerformed()是当我们选择了插件中的选项时来执行的,咱们在这里简单弄一个对话框出来,至于插件的语法这里不必深究,重点是掌握自定义的一个流程:
好,就这么简单,既然是入门级别的,就不用学太多,这时咱们就可以点击运行看一下效果了:
此时则会再开一个窗口来预览效果:
如果点击的话,则会弹出一个对话框出来:
这就是一个简单的小插件。
发布插件并安装到Android Studio中:
接下来咱们试着将我们编写的这个小插件安装到我们的Android Studio当中,在打包之前需要有个注意点:
所以咱们加个包:
接下来则开始将我们的插件打包,怎么打包呢?
此时就生成了一个jar文件了,接下来将它安装到我们的Android Studio当中了,先将这个jar拷到桌面:
然后回到我们的Android Studio中的安装插件设置处:
点击一下看是否能弹出窗?
妥妥的!!!那学这个插件的技术跟我们学习IOC技术有啥关系呢?其实在上一次https://www.cnblogs.com/webor2006/p/12374975.html博客中埋了伏笔的,如下:
嗯,至于怎么用Android Studio的插件技术来实现自动生成ButterKnife的机械式的代码,下次再来研究。