Android DataSource 层实现

前言

在 Android 日益成熟和完善的阶段,我们的项目也日益的开始注重性能和可维护性.今天要和大家聊一聊Android 项目里面的 DataSource

首先我们可以理解 DataSource 是一个单独的 Module,可以对外提供很多的数据操作
- 网络请求
- 本地的一些数据的持久化
- 一些第三方的数据封装
- 项目中一些Runtime的数据(比如用户信息,是否登录了等等)

目标架构

在我们的项目中,我们希望我们在使用 DataSource 的时候是可以使用一个,全部的地方都使用这一个. 但是我们又希望在我们维护 DataSource 的时候是可以分成多个来写的

这里写图片描述

实现这个架构

因为这样子不仅使用方便,更便于维护,但是要实现这样子的架构,却不太好实现.
假设我们现在要提供一个 ShareadPrefrence 存储的 DataSource 和 城市列表的数据的 DataSource

你写了一个 SpDataSource 接口和一个 SpDataSourceImpl 实现类
又写了一个 CityDataSource 接口和一个 CityDataSourceImpl 实现类

但是我们希望我们使用的时候是使用一个总的 DataSource

那你现在如何让一个总的 DataSource 拥有各个DataSource 的功能呢?

使用动态代理

你可以写一个 DataSource 接口继承所有的其他的 DataSource 接口

class DataSource extends DataSource1,DataSource2..... {
}

然后使用动态代理代理这个 DataSource 接口,然后在代理的拦截方法中判断方法的归属的类,然后调用相应的 DataSourceImpl

使用动态代理的弊端(我开始采取的就是这个方案)

  • 调试的时候, DataSource 实现类的 代码调试不了
  • 每一次有一个新的 DataSource,我就得修改 DataSource 总接口和代理中的代码

其中最为致命的还是低一点调试不了,所以我就开始寻求另一个方式来实现

使用代码生成

还记得之前说的目标吗?我们要实现使用的时候是一个 DataSource ,维护的是时候是多个 DataSource

如果我们能扫描到所有的 DataSource,并且让每一个 DataSource 中的方法都生成到 一个总的 DataSource 接口和 DataSource 实现类,那么问题不就解决了吗?

还有一个小问题,每一个子 DataSource 中方法还可能有和其他子 DataSource 名字重复的方法,所以生成的时候应该支持给每一个子 DataSource 添加一个前缀

这里就可以使用注解驱动器来实现啦

这里写图片描述

类似于这种用注解标记这个 DataSource 接口,标明实现类

这里写图片描述

最终会生成这三个类,一个总的 DataSource 接口,一个总的 DataSource 实现类 和 一个 DataSource 的管理类

最终代码实现

先写一个注解类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface EHiDataSourceAnno {

    /**
     * 生成的方法的前缀,用于区分
     *
     * @return
     */
    String value() default "";

    /**
     * 用于内部区分,不能为相同的
     *
     * @return
     */
    String uniqueCode();

    /**
     * 实现类,会被加入到缓存中
     */
    String impl() default "";

    /**
     * 如果实现类不是直接创建的,那么传入调用的方式
     *
     * @return
     */
    String callPath() default "";

}

编写对应的 AnnotationProcessor

/**
 * 实现项目中的 retrofit 接口的代理接口和实现类生成,生成的类作为 datasource 层
 * time   : 2018/08/10
 *
 * @author : xiaojinzi 30212
 */
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({"com.ehi.api.EHiDataSourceAnno"})
public class EHiDatasourceProcessor extends AbstractProcessor {

    private TypeMirror typeString;
    private TypeMirror typeVoid;

    private Filer mFiler;
    private Messager mMessager;
    private Types mTypes;
    private Elements mElements;

    private String classNameApi = "com.ehi.datasource.DataSourceApi";
    private String classNameApiImpl = "com.ehi.datasource.DataSourceApiImpl";
    private String classNameApiManager = "com.ehi.datasource.DataSourceManager";
    ;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        mFiler = processingEnv.getFiler();
        mMessager = processingEnvironment.getMessager();
        mTypes = processingEnv.getTypeUtils();
        mElements = processingEnv.getElementUtils();

        typeString = mElements.getTypeElement("java.lang.String").asType();
        typeVoid = mElements.getTypeElement("java.lang.Void").asType();

    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        if (CollectionUtils.isNotEmpty(set)) {

            Set<? extends Element> routeElements = roundEnvironment.getElementsAnnotatedWith(EHiDataSourceAnno.class);

            parseAnno(routeElements);

            return true;
        }

        return false;

    }

    /**
     * 解析注解
     *
     * @param currElements
     */
    private void parseAnno(Set<? extends Element> currElements) {

        List<TypeElement> list = new ArrayList<>();

        Set<String> prefixSet = new HashSet<>();
        Set<String> uniqueSet = new HashSet<>();

        for (Element element : currElements) {

            TypeMirror tm = element.asType();

            if (!(element instanceof TypeElement)) {

                mMessager.printMessage(Diagnostic.Kind.ERROR, element + " is not a 'TypeElement' ");

                continue;

            }

            EHiDataSourceAnno anno = element.getAnnotation(EHiDataSourceAnno.class);

            if (anno.impl().isEmpty() && anno.callPath().isEmpty()) {

                mMessager.printMessage(Diagnostic.Kind.ERROR, element.toString() + ": EHiDataSourceAnno's impl and EHiDataSourceAnno's callPath are both empty");

                continue;
            }

            if (!anno.value().isEmpty()) {

                if (prefixSet.contains(anno.value())) {
                    mMessager.printMessage(Diagnostic.Kind.ERROR, element.toString() + ": EHiDataSourceAnno's value is already exist");
                    continue;
                }
                prefixSet.add(anno.value());

            }

            if (uniqueSet.contains(anno.uniqueCode())) {

                mMessager.printMessage(Diagnostic.Kind.ERROR, element.toString() + ": EHiDataSourceAnno's uniqueCode is not unique");

                continue;
            }

            uniqueSet.add(anno.uniqueCode());

            list.add((TypeElement) element);

        }

        try {
            createDataSourceApi(list);
            createDataSourceApiManager(list);
            createDataSourceApiImpl(list);
        } catch (Exception e) {
            mMessager.printMessage(Diagnostic.Kind.ERROR, "createDataSource fail: " + e.getMessage());
            e.printStackTrace();
        }

    }

    private void createDataSourceApi(List<TypeElement> apiClassList) throws IOException {

        // pkg
        String pkgApi = classNameApi.substring(0, classNameApi.lastIndexOf("."));

        // simpleName
        String cnApi = classNameApi.substring(classNameApi.lastIndexOf(".") + 1);

        TypeSpec.Builder typeSpecBuilder = TypeSpec.interfaceBuilder(cnApi)
                .addModifiers(Modifier.PUBLIC);

        typeSpecBuilder.addJavadoc("所有用注解标记的DataSource都会被整合到这里\n\n");

        for (TypeElement typeElement : apiClassList) {

            EHiDataSourceAnno remoteAnno = typeElement.getAnnotation(EHiDataSourceAnno.class);

            if (remoteAnno == null) {
                continue;
            }

            typeSpecBuilder.addJavadoc("@see " + typeElement.toString() + "\n");
            generateMethods(typeElement.getEnclosedElements(), typeSpecBuilder, remoteAnno, false);

        }

        TypeSpec typeSpec = typeSpecBuilder.build();

        JavaFile.builder(pkgApi, typeSpec)
                .indent("    ")
                .build()
                .writeTo(mFiler);

    }

    private void createDataSourceApiManager(List<TypeElement> apiClassList) throws IOException {

        // pkg
        String pkgApi = classNameApiManager.substring(0, classNameApiManager.lastIndexOf("."));

        // simpleName
        String cnApi = classNameApiManager.substring(classNameApiManager.lastIndexOf(".") + 1);

        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(cnApi)
                .addModifiers(Modifier.PUBLIC);

        ClassName mapClassName = ClassName.get("java.util", "Map");

        TypeName typeNameMap = ParameterizedTypeName.get(mapClassName, TypeName.get(typeString), TypeName.get(mElements.getTypeElement("java.lang.Object").asType()));

        FieldSpec.Builder mapFieldSpecBuilder = FieldSpec.builder(typeNameMap, "map", Modifier.PRIVATE)
                .initializer("java.util.Collections.synchronizedMap(new java.util.HashMap<String,Object>())");

        typeSpecBuilder
                .addField(mapFieldSpecBuilder.build());

        for (TypeElement typeElement : apiClassList) {

            EHiDataSourceAnno anno = typeElement.getAnnotation(EHiDataSourceAnno.class);

            if (anno == null || anno.impl().isEmpty()) {
                continue;
            }

            TypeMirror returnType = typeElement.asType();

            MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder(anno.uniqueCode() + "DataSource")
                    .returns(TypeName.get(returnType));

            String implClassName = anno.impl();

            methodSpecBuilder
                    .addStatement(implClassName + " value = (" + implClassName + ") map.get(\"" + anno.uniqueCode() + "\");")
                    .beginControlFlow("if (value == null)")
                    .addStatement("value = " + " new " + implClassName + "()")
                    .addStatement("map.put(\"" + anno.uniqueCode() + "\", value);")
                    .endControlFlow()
                    .addStatement("return value")
                    .addModifiers(Modifier.PUBLIC);

            MethodSpec methodSpec = methodSpecBuilder.build();

            typeSpecBuilder.addMethod(methodSpec);


        }


        // 内部的Holder 静态类
        TypeSpec.Builder typeSpecHolderBuilder = TypeSpec.classBuilder("Holder")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC);

        typeSpecHolderBuilder.addField(
                FieldSpec.builder(
                        ClassName.get(classNameApiManager.substring(0, classNameApiManager.lastIndexOf(".")), classNameApiManager.substring(classNameApiManager.lastIndexOf(".") + 1)), "instance"
                )
                        .initializer("new " + classNameApiManager + "()")
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                        .build()
        );

        TypeSpec apiTypeSpec = typeSpecBuilder
                // 添加一个私有的构造函数
                .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())
                .addType(typeSpecHolderBuilder.build())
                .build();

        JavaFile.builder(pkgApi, apiTypeSpec)
                .indent("    ")
                .build()
                .writeTo(mFiler);

    }

    private void createDataSourceApiImpl(List<TypeElement> apiClassList) throws IOException {

        // pkg
        String pkg = classNameApiImpl.substring(0, classNameApiImpl.lastIndexOf("."));

        // simpleName
        String cn = classNameApiImpl.substring(classNameApiImpl.lastIndexOf(".") + 1);

        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(cn)
                .addModifiers(Modifier.PUBLIC);

        for (TypeElement typeElement : apiClassList) {

            EHiDataSourceAnno remoteAnno = typeElement.getAnnotation(EHiDataSourceAnno.class);

            if (remoteAnno == null) {
                continue;
            }

            List<? extends Element> enclosedElements = typeElement.getEnclosedElements();
            generateMethods(enclosedElements, typeSpecBuilder, remoteAnno, true);

        }

        ClassName superInterface = ClassName.get(classNameApi.substring(0, classNameApi.lastIndexOf(".")), classNameApi.substring(classNameApi.lastIndexOf(".") + 1));

        typeSpecBuilder.addSuperinterface(superInterface);

        TypeSpec typeSpec = typeSpecBuilder.build();

        JavaFile.builder(pkg, typeSpec)
                .indent("    ")
                .build()
                .writeTo(mFiler);

    }

    private void generateMethods(List<? extends Element> enclosedElements, TypeSpec.Builder typeSpecBuilder, EHiDataSourceAnno anno, boolean isAddStatement) {

        for (Element elementItem : enclosedElements) {

            if (!(elementItem instanceof ExecutableElement)) {
                continue;
            }

            // 可执行的方法
            ExecutableElement executableElement = (ExecutableElement) elementItem;

            String methodName = "";

            if (anno.value().isEmpty()) {
                methodName = executableElement.getSimpleName().toString();
            } else {
                methodName = anno.value() + firstCharToUp(executableElement.getSimpleName().toString());
            }

            MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder(methodName);

            String parameterStr = generateParameters(executableElement, methodSpecBuilder);

            methodSpecBuilder.returns(TypeName.get(executableElement.getReturnType()));

            if (isAddStatement) {

                methodSpecBuilder.addModifiers(Modifier.PUBLIC);

                String returnStr = null;

                if (anno.callPath().isEmpty()) {
                    returnStr = classNameApiManager + ".Holder.instance." + anno.uniqueCode() + "DataSource()." + executableElement.getSimpleName().toString() + "(" + parameterStr + ")";
                } else {
                    returnStr = anno.callPath() + "." + executableElement.getSimpleName().toString() + "(" + parameterStr + ")";
                }

                if ("void".equals(executableElement.getReturnType().toString())) {
                    methodSpecBuilder.addStatement(returnStr);
                } else {
                    methodSpecBuilder.addStatement("return " + returnStr);
                }


            } else {
                methodSpecBuilder
                        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
            }

            MethodSpec methodSpec = methodSpecBuilder.build();

            typeSpecBuilder.addMethod(methodSpec);

        }

    }

    private String generateParameters(ExecutableElement executableElement, MethodSpec.Builder methodSpecBuilder) {

        List<? extends VariableElement> typeParameters = executableElement.getParameters();

        StringBuffer sb = new StringBuffer();

        for (VariableElement typeParameter : typeParameters) {

            //mMessager.printMessage(Diagnostic.Kind.NOTE, "typeParameter ====== " + typeParameter.asType().toString());

            //TypeName.get(mElements.getTypeElement(""))

            TypeName typeName = TypeName.get(typeParameter.asType());

            String parameterName = typeParameter.getSimpleName().toString();

            ParameterSpec parameterSpec = ParameterSpec.builder(typeName, parameterName)
                    .build();

            if (sb.length() == 0) {
                sb.append(parameterName);
            } else {
                sb.append(",").append(parameterName);
            }

            methodSpecBuilder.addParameter(parameterSpec);

        }

        return sb.toString();

    }

    private String firstCharToUp(String str) {
        if (str == null || str.length() == 0) {
            return "";
        }

        String str1 = str.substring(0, 1).toUpperCase();
        String str2 = str.substring(1);

        return str1 + str2;

    }

}

源码下载

DataSourceImpl

发布了119 篇原创文章 · 获赞 275 · 访问量 38万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览