1. 程式人生 > >Android DataSource 層實現

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;

    }

}

原始碼下載