我所知道的偷懒的方式

前言

在现今的软件开发过程当中,软件开发人员将更多的精力投入在了重复的类似劳动中。特别是在现在特别流行的 MVC 架构模式中,软件各个层次的功能更加独立,同时代码的类似度也更加高。因此咱们须要寻找一种来减小软件开发人员重复劳动的方法,让程序员将更多的精力放在业务逻辑以及其余更加具备创造力的工做上。java

服务端的技术发展了不少年,有不少值得能够借鉴的地方。有的时候,在跟后台沟通的时候,发现他们在设计好数据库表结构的时候,常常能够一键生成常见的功能(增、删、改、查)。后来知道,由于相识程度很是的高。因此他们常常以代码来生成代码。就这样完成了一站式的功能。android

更多时候,我在考虑,为什么他们不优先考虑封装呢?git

  1. 可能会常常发生变更。
  2. 人员因素吧

Android 的一种"偷懒"

Android 中有种模板编程的偷懒方式。但不是今天的主角。今天的主角是Annotation Processor(注解处理器)。 在最近查阅源码的时候。发现不少框架都会使用到Annotation Processor好比说,ButterKnifeDaggerARouter等等。因此理解Annotation Processor(注解处理器)的原理,是一个Android程序员必须具有的技能。程序员

1. Annotation Processor

Annotation Processor直译成中文就是(注解处理器),就是可以对注解进行处理。既然可以对注解进行处理,那么先定义一个简单的注解。github

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    int value();
}

复制代码
2. 使用注解

定义完注解后,咱们会想着在何时使用它呢? 还记得上述的核心是为了偷懒,那么 在Android存在有重复性很是高并且难度极地的代码,就是对控件的获取和处理。数据库

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView1=(TextView) findViewById(R.id.tvHello);
        textView1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            }
        });
    }
}
复制代码

这样的模板代码不只很浪费时间,并且代码的美观程度也大大的下降了。那么咱们如今尝试着用本身定义的注解去处理。编程

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tvHello)
    TextView tvHello;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @OnClick(R.id.tvHello)
    public void changeText2() {
        Toast.makeText(this, "2222", Toast.LENGTH_LONG).show();
    }
}

复制代码

上述的代码,就会比较清晰。不会有获取控件的过程的代码。声明即便用了。那么仅仅这样打上一个注解,可以自动的获取控件吗?答案是不可能的。咱们还缺乏一步编写注解处理器(其实就是编写遇到这个注解改怎么处理)。api

3. 编写注解处理器
xx01 最终生成的类

在编写注解处理器以前,先尝试着去编写一个类。这个类的职责就是获取控件。bash

public final class MainActivity$ViewBinding {
  public MainActivity$ViewBinding(MainActivity target, View source) {
    if(target == null)  return;
    if(source == null)  return;
    target.tvHello = (android.widget.TextView)source.findViewById(2131165275);
  }

  public MainActivity$ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }
}
复制代码

上面的这个类,就是须要注解处理器帮咱们自动生成的模板代码。咱们必须知道本身须要什么代码,才能控制注解处理器生成咱们想要的代码。那么观察一下最终生成的代码,其中有几个须要注意的:数据结构

  1. 须要遍历全部须要处理的注解。在本文中咱们只关心BindView
  2. 当咱们遍历出全部带有BindView的注解的时候,咱们须要为这个字段赋值。而赋值的操做,咱们是经过属性直接赋值(默认的访问权限是包访问权限)

target.tvHello = (android.widget.TextView)source.findViewById(2131165275);

因此这个由注解处理器帮我生成的类,须要跟目标类在同级包下。 3. 这个类的名字是按照必定格式生成的,在本文中它是由目标类+$ViewBinding。

到这里,须要注意的点都讲完了,接下开始来编写注解处理器的核心代码。

xx02 项目目录的划分

在知道了咱们最终须要生成什么代码以后,就须要对整个目录进行规划一下,由于这个工具不只仅是当前这个项目会被使用。颇有不少项目将对其引用。你可能会将其发布到JCenter上。

image.png

根据上面的图,咱们分别创建3module。其中2个是java libaryandroid libary

image.png

最终的目录结构是这样的

image.png

而后咱们将刚才编写的BindView注解放入inject-annotation模块中

xx03 编写注解处理器

在划分话目录结构后,咱们能够编写注解处理器的核心代码了,也就意味着,咱们须要把它的代码放置在inject-compiler中。

须要在inject-compiler下的build.gradle导入几个库

implementation 'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    api 'com.squareup:javapoet:1.10.0'
    implementation project(':inject-annotation')
复制代码

介绍一下这几个库,

  1. auto-service 是帮助生成META-INF/services/javax.annotation.processing文件中的内容
  2. javapoet 更加面向对象的输出代码
  3. inject-annotation 须要处理的注解。

在添加完这些东西以后,咱们就须要建立一个注解处理器了

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        HashMap<TypeElement, List<Element>> datas = new HashMap<>();
        for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
            TypeElement originalType = (TypeElement) element.getEnclosingElement();
            if (datas.get(originalType) == null) {
                List<Element> elements = new ArrayList<>();
                datas.put(originalType, elements);
            }
            List<Element> dd = datas.get(originalType);
            dd.add(element);
        }

        for (Map.Entry<TypeElement, List<Element>> entry : datas.entrySet()) {
            TypeElement key = entry.getKey();
            List<Element> value = entry.getValue();
            createFile(key, value);
        }
        return false;
    }

复制代码

核心代码大概是上面的,就是遍历类中的注解元素,包装成一个Map数据的数据结构。

image.png

而后将构建后的数据结构,进行处理,按照之间MainActivity$ViewBinding的所预想的规则进行输出。

private MethodSpec createCustomerConstructor2View(TypeElement originalType, List<Element> elements) {
        ParameterSpec targetParamSpec = ParameterSpec.builder(TypeName.get(originalType.asType()), "target").build();
        MethodSpec.Builder constructor1 = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(targetParamSpec)
                .addParameter(mViewParameterSpec)
                .addStatement("if(target == null) return")
                .addStatement("if(source == null) return");

        for (Element element : elements) {
            String variateName = element.getSimpleName().toString();
            String variateType = element.asType().toString();
            int resId = element.getAnnotation(BindView.class).value();
            constructor1.addStatement("target.$L = ($N)source.findViewById($L)", variateName, variateType, resId);
        }
        return constructor1.build();
    }
复制代码

这样咱们就完成了注解处理器的编写,当你编写后从新Rebuild Project后,若是不出意外的话,你能够找到下面这个文件。

image.png

xx03 调试Annotation Processor(注解处理器)

若是你在编写注解处理器可能不是你预想,那么断点调试就变得很是的重要了。那么接下来介绍如何调试注解处理器.

  1. 打开Edit Configurations新建一个Remote Configurations

image.png

  1. Terminal中运行下面命令

./gradlew --no-daemon -Dorg.gradle.debug=true :app:clean :app:compileDebugJavaWithJavac

image.png

  1. 须要断点的地方,下断点。

  2. Debug 刚才添加的Configurations

image.png

5.它就能够正常的断点了

image.png

xx04 调用生成的代码

当咱们完成了注解处理器的编写,也正常的生成了MainActivity$ViewBinding文件后,咱们就须要调用所生成的类,让它来帮咱们完成控件的注入。由于其中会涉及到对Activity的引用,因此这个包是android libary。也就意味着,接下来的代码须要在inject-core模块中编写。

咱们在进入一个Activity或者Fragment的时候,可以调用inject方法,而inject的实现以下:

public static void inject(Activity activity) {
        String name = activity.getClass().getSimpleName();
        String packName = activity.getPackageName();
        String fullName = name + "$ViewBinding";
        try {
            Class targetClass = activity.getClassLoader().loadClass(packName + "." + fullName);
            Constructor constructor = targetClass.getConstructor(activity.getClass());
            constructor.newInstance(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

复制代码

就是经过反射,而后实例化刚才的对象。这样的话,咱们就完成了控件的注入了。

4. 发布到JCenter

由于咱们项目中存在有jaraar格式的包,因此须要分别上传。在编写上传任务以前,须要先在最外层的build.gradle添加plugin,请注意插件的版本。否则会出现一些奇奇怪怪的问题。

buildscript {
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'

    }
}

allprojects {
    repositories {
        google()
        jcenter()
        
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

复制代码
  1. 编写jar格式的gradle,在最外层新建jar_publish.gradle文件,而后在:inject-compilerbuild.gradle中 apply。

image.png

  1. 编写aar格式的gradle,在最外层新建aar_publish.gradle文件,而后在:inject-corebuild.gradle中 apply。

image.png

而后依次上传便可。

本文中的代码

  1. github.com/BelongsH/AP…

相关连接

  1. github.com/google/auto…
相关文章
相关标签/搜索