zoukankan      html  css  js  c++  java
  • 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的机械式的代码,下次再来研究。 

  • 相关阅读:
    CSS3实现轮播切换效果
    angularjs directive
    angularjs 迭代器
    anjularjs 路由
    sublime text3 快捷键设置
    如何使用git 跟进项目进程
    hdu 4842(NOIP 2005 过河)之 动态规划(距离压缩)
    叠箱子问题 之 动态规划
    华为oj 之 蜂窝小区最短距离
    华为oj 之 整数分隔
  • 原文地址:https://www.cnblogs.com/webor2006/p/12392259.html
Copyright © 2011-2022 走看看