自定义apt实战之一>Mapcreate

7 篇文章 1 订阅

前面文章写了开始自定义APT之前需要了解的知识,刚开始接触自定义APT的小伙伴,可以看一下我前面的几篇文章:
自定义APT之:调试
自定义APT基础之:Element
自定义APT之:javapoet

现在终于可以开始创建自己的apt了。先说一下开始之前的业务背景。
在我的日常Android开发中,随着版本的不断迭代,需要对接N多个协议,需要适配N多个界面。在项目初期,使用if-else来进行区分,每个协议里面的又有很多子项。协议的子项通过Map来保存key-value键值对,通过反射来实例化并处理。(这里不推荐大家使用反射,可以使用抽象工厂来替代)。那么随着业务协议的增多以及更多界面的适配,出现了以下几个问题:
if-else方式:
1.代码臃肿,几十个if-else,单个java文件代码量过大;
2.依赖严重,每增加一个协议,都要去增加else,并且都是强引用。
3.代码不够优雅(最重要一点);
Map键值对反射方式:
1.每次增加子项都需要在Map里增加键值对,烦琐;
2.反射,性能消耗;
3.还是代码不够优雅;
针对上面的问题,起初我是想通过dagger来解决依赖的问题,但发现dagger还是避免不了在创建对象时要使用if-else判断。之后我想到了通过自定义apt的方式,老共传统技能:三步走战略。
第一步:使用apt,自动生成键值对,解决使用反射时,每次都需要手动去添加键值对;
第二步:使用apt,自动生成抽象工厂类,解决if-else的问题,连带解决了Map的反射;
第三步:在第二步基础上,支持抽象工厂传参构造。

按照我的想法,最终使用一个注解,自动生成抽象工程,创建对象的实例时,只需要按照协议解析出来的参数,自动实例化具体的对象。自动生成抽象工厂,最大的好处:有新的协议或者界面需要适配时,不用改动之前的代码,只需要在新增协议的处理类上加一个注解,自动构建,,封装性更强。

我的想法是生成这样一个类:

import java.lang.String;
import java.util.HashMap;

public class KimMap {
  public static final HashMap MAP_CREATE = new HashMap<String, String>();

  public static final HashMap MyMap = new HashMap<String, String>();

  static {
    MAP_CREATE.put("222","com.yanantec.ynarc.MainActivity");
    MAP_CREATE.put("22","com.yanantec.ynarc.Test");
    MyMap.put("666","com.yanantec.ynarc.BaseActivity");
  }
}

首先由于apt只参与编译过程,并不会打包到apk中,所以定义注解和注解的实现需要分成两个模块,这里我们分成:annotation和complier模块,由于我们不需要Android的资源,只有java部分,因此annotation和complier模块使用java library。如下图:
在这里插入图片描述

首先在annotation模块新增注解:@MapCreate,该注解提供key和要加入到的Hashmap的变量名:

@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MapCreate
{
    /**
     * map添加的键值对的key
     * @return
     */
    String key();

    /**
     * map集合的 属性名
     * @return
     */
    String mapFiled() default "MAP_CREATE";

}

然后开始写complier模块:
1.这里需要依赖几个包:

	// 自动构建执行processor
    compileOnly  'com.google.auto.service:auto-service:1.0-rc6'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    implementation "com.yanantec:annotation:1.4.0"
    // javapoet 用于生成java文件
    implementation 'com.squareup:javapoet:1.11.1'

2.自定义processor,并加上@AutoService(Processor.class):

@AutoService(Processor.class)
public class MapProcessor extends AbstractProcessor
{
	
}

执行make project:
在这里插入图片描述
执行成功后,会自动创建下面文件:
在这里插入图片描述
auto-service其实也是一个apt,用于把@AutoService注解的类加入注册表(上图对应的文件),也可以不使用这个库,手动创建,并把对应自定义processor注册进去,如下图:
在这里插入图片描述

这里遇到我的第一个坑:@AutoService(Processor.class),里面固定用Processor.class,否则不会把当前MapProcessor 加入到注册表。

3.重写AbstractProcessor的以下方法:


@AutoService(Processor.class)
public class MapProcessor extends AbstractProcessor
{
	private Filer mFiler;
    private Messager mMessager;
    // 用于存储map属性名和map存储的
    private Map<String, List<TypeElement>> actionMaps = new HashMap<>();
    private static String PACKAGE_NAME = "com.kim.map";
    
 	@Override
    public synchronized void init(ProcessingEnvironment processingEnv)
    {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();
        mMessager = processingEnv.getMessager();
        Map<String, String> options = processingEnv.getOptions();
        if (options != null && options.size() > 0){
            for (Map.Entry<String, String> entry : options.entrySet())
            {
                if (entry.getKey().contains("kim.applicationId")){
                    PACKAGE_NAME = entry.getValue();
                }
            }
        }

    }
    
	 @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
    {
    	return false;
    }
    
	@Override
    public Set<String> getSupportedAnnotationTypes()
    {
		// 设置我们要用的注解
        return Collections.singleton(MapCreate.class.getCanonicalName());
    }
    
 	@Override
    public SourceVersion getSupportedSourceVersion()
    {
        return SourceVersion.latestSupported();
    }
}

4.开始业务逻辑部分的代码:
业务思想:我们先把注解筛选成我们要使用的数据,这里我们使用Map<String, List>来存储Map的属性名和类元素,在生成java代码时,我们按照要生成几个Map,各自往Map里面添加<key, className>的键值对。
第一步,筛选:


 	actionMaps.clear();
 	// 获取所有MapCreate注解的元素
        Set<? extends Element>  elements = roundEnv.getElementsAnnotatedWith(MapCreate.class);
        // 遍历注解的类元素
        Iterator<? extends  Element> iterator = elements.iterator();
        while (iterator.hasNext()){
            Element element = iterator.next();
            // MapCreate注解修饰的是类元素
            if (element.getKind() == ElementKind.CLASS){
            // 获取当前注解的类,要保存到的Map集合属性名
                String filedName = element.getAnnotation(MapCreate.class).mapFiled();
                List<TypeElement> elementList;
                // 把当前类元素,加入到指定的Map中
                if (actionMaps.containsKey(filedName)){
                    elementList = actionMaps.get(filedName);
                }else {
                    elementList = new ArrayList<>();
                }
                elementList.add((TypeElement) element);
                actionMaps.put(filedName, elementList);
            }
        }

第二步:生成.java文件:

// 生成MAP类
        if (actionMaps.size() > 0){
            // 创建一个类
            TypeSpec.Builder typeBuilder = TypeSpec.classBuilder("KimMap").addModifiers(PUBLIC);
            CodeBlock.Builder codeBuilder =CodeBlock.builder();
            // 用于提示统一个集合是否存在重复的key
            Map<String, String> allrRepeatKey = new HashMap<>();
            for (Map.Entry<String, List<TypeElement>> entry : actionMaps.entrySet())
            {
                List<TypeElement> fileds = entry.getValue();
                // 判断当前map属性名所包含的键值对是否为空
                if (fileds == null || fileds.size() < 0) continue;
                // map对应的属性名称
                String mapName = entry.getKey();
                // HashMap
                ClassName hashMapClasssName = ClassName.get("java.util","HashMap");
                // String
                ClassName stringClassName = ClassName.get("java.lang", "String");
                // HashMap<String, String>
                TypeName hashMapStringClassName = ParameterizedTypeName.get(hashMapClasssName, stringClassName, stringClassName);
                // 生成Map属性,并初始化,生成代码:P  public static final HashMap MAP_CREATE = new HashMap<String, String>();
                FieldSpec fieldSpec = FieldSpec.builder(HashMap.class, mapName)
                        .addModifiers(PUBLIC, FINAL, STATIC)
                        .initializer("new $T()", hashMapStringClassName)
                        .build();
                typeBuilder.addField(fieldSpec);

                // 添加类名和key的对应关系
                Iterator<TypeElement> iterator1 = fileds.iterator();
                // 找出类中重复的元素:<key值, 类名>,
                Map<String, String> repeatKeys = new HashMap<>();
                while (iterator1.hasNext()){
                    // 静态代码块中存入key-类名
                    TypeElement element = iterator1.next();
                    String value = element.getEnclosingElement().toString() + "." + element.getSimpleName().toString();
                    String key = element.getAnnotation(MapCreate.class).key();
                    codeBuilder.addStatement("$L.put($S,$S)", mapName, key, value);
                    if (repeatKeys.containsKey(key)){
                        // 保存重复的
                        String oldValue = repeatKeys.get(key);
                        if (!allrRepeatKey.containsKey(oldValue)){
                            allrRepeatKey.put(oldValue, key);
                        }
                        allrRepeatKey.put(value, key);
                    }else {
                        repeatKeys.put(key, value);
                    }
                }

                if (allrRepeatKey.size() > 0){
                    for (Map.Entry<String, String> stringEntry : allrRepeatKey.entrySet())
                    {
                        mMessager.printMessage(Diagnostic.Kind.ERROR, stringEntry.getKey() + "中的key:" + stringEntry.getValue() + ",已在其他类中存在");
                    }
                    return false;
                }
            }
            // 构建类文件
            TypeSpec typeSpec = typeBuilder.addStaticBlock(codeBuilder.build()).build();
            // 生成.java文件
            JavaFile javaFile = JavaFile.builder(PACKAGE_NAME, typeSpec).build();
            try
            {
                javaFile.writeTo(mFiler);
            } catch (Exception e)
            {
                e.printStackTrace();
                mMessager.printMessage(Diagnostic.Kind.ERROR, "写文件错误:" + e);
            }

最后,在项目中加入注解,并编译,编译成功后则可以找到生成的KimMap类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值