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 + ";
");
                    //        import com.android.butterknifearcstudy.IBinder;
                    writer.write("import " + packageName + ".IBinder;
");
                    //        public class MainActivity_ViewBinding implements IBinder<com.android.butterknifearcstudy.MainActivity>{
                    writer.write("public class " + activityName + "_ViewBinding implements IBinder<"
                            + packageName + "." + activityName + ">{
");
                    //            @Override
                    writer.write("@Override
");
                    //            public void bind(com.android.butterknifearcstudy.MainActivity target) {
                    writer.write("public void bind(" + packageName + "." + activityName + " target){
");

                    // 接下来则需要根据每个注解字段生成"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 + ");
");

                    }
                    writer.write("
}}");

                } 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的机械式的代码,下次再来研究。 

原文地址:https://www.cnblogs.com/webor2006/p/12392259.html