Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
425 lines (329 sloc) 15.5 KB
title tags date keywords
自定义注解,打造自己的框架-下下篇
Android
2019-12-09 15:21:08 -0800
自定义注解
Annotation
AutoService
AbstractProcessor
反射
javapoet

该系列介绍自定义注解,完成如下功能。

  • @BindView 代替 findViewById
  • @ClickResponder 代替 setOnClickListener
  • @LongClickResponder 代替 setOnLongClickListener
  • @IntentValue 代替 getIntent().getXXX
  • @UriValue 代替 getQueryParameter
  • @BroadcastResponder 代替 registerReceiver
  • @RouterModule、@RouterPath 来进行反依赖传递调用 该系列源码在https://github.com/huangyuanlove/AndroidAnnotation 前几篇介绍了@BindView@ClickResponder@LongClickResponder的实现,这一篇介绍一下@IntentValue@UriValue的实现。

前提

我们知道执行Intent.putExtra()方法时,实际上是把数据塞进Bundle中的,并且该方法有不同的重载类型,方便开发这存储不同的数据。当我们从中取数据时,需要调用不同的方法来获取不同类型的数据,这就需要我们在生成辅助代码时,根据注解字段的类型来调用不同的方法。

而Uri中的能传递的参数只有有限的几种类型,最常见的就是String和int

声明注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface IntentValue {

    int DEFAULT_TYPE = -1;
    int PARCELABLE_OBJECT = 1;
    int PARCELABLE_ARRAY_OBJECT = 2;
    int PARCELABLE_ARRAYLIST_OBJECT = 3;
    int SERIALIZABLE_OBJECT = 4;


    String key();
    int type() default DEFAULT_TYPE;
}

这里在注解类中声明了几种类型,来配合注解使用,主要是为了在生成辅助代码时简单,因为javax.lang.model.type.TypeKind只声明了有限的几种类型,其余的需要我们自己取判断。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface UriValue {
    String key();
}

处理注解逻辑

我们还是使用上一篇中的注解处理器,需要添加两个属性

private Map<TypeElement, List<Element>> intentValueMap = new HashMap<>();
private Map<TypeElement, List<Element>> uriValueMap = new HashMap<>();

getSupportedAnnotationTypes方法中添加支持

set.add(IntentValue.class.getCanonicalName());
set.add(UriValue.class.getCanonicalName());

process方法中加入我们的注解处理逻辑

intentValueMap.clear();
uriValueMap.clear();

Set<? extends Element> intentValueSet = roundEnvironment.getElementsAnnotatedWith(IntentValue.class);
Set<? extends Element> uriValueSet = roundEnvironment.getElementsAnnotatedWith(UriValue.class);

collectIntentValueInfo(intentValueSet);
collectUriValueMapInfo(uriValueSet);

private void collectIntentValueInfo(Set<? extends Element> elements) {
        for (Element element : elements) {
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            List<Element> elementList = intentValueMap.get(typeElement);
            if (elementList == null) {
                elementList = new ArrayList<>();
                intentValueMap.put(typeElement, elementList);
            }
            elementList.add(element);
        }
    }


private void collectUriValueMapInfo(Set<? extends Element> elements) {
    for (Element element : elements) {
        TypeElement typeElement = (TypeElement) element.getEnclosingElement();
        List<Element> elementList = uriValueMap.get(typeElement);
        if (elementList == null) {
            elementList = new ArrayList<>();
            uriValueMap.put(typeElement, elementList);
        }
        elementList.add(element);
    }
}

上面的这些收集信息的工作都差不多,把我们需要的数据存放在map中。

generateCode()方法中调用

generateIntentValueCode();
generateUriValueCode();
private void generateIntentValueCode() {
    for (TypeElement typeElement : intentValueMap.keySet()) {
        MethodSpec.Builder methodBuilder = generateParseBundleMethodCode(typeElement);

        List<Element> elements = intentValueMap.get(typeElement);
        for (Element element : elements) {
            processorIntentValue(element, methodBuilder);
        }
    }

}

private void generateUriValueCode() {
    for (TypeElement typeElement : uriValueMap.keySet()) {
        MethodSpec.Builder methodBuilder = generateParseUriMethodCode(typeElement);

        List<Element> elements = uriValueMap.get(typeElement);
        for (Element element : elements) {
            processorUriValue(element, methodBuilder);
        }
    }

}

我们先来看一下比较简单的处理Uri参数的方法

private void processorUriValue(Element element, MethodSpec.Builder methodBuilder) {
    VariableElement variableElement = (VariableElement) element;
    
    //注解的字段名字
    String varName = variableElement.getSimpleName().toString();

    UriValue uriValue = variableElement.getAnnotation(UriValue.class);

    //获取到注解的key,从Uri中获取对应的值
    methodBuilder.addStatement("temp = uri.getQueryParameter($S)", uriValue.key());
    
    //如果是String类型,直接赋值
    if (element.asType().toString().equals("java.lang.String")) {
        methodBuilder.addStatement("target.$L=temp",varName);
    }else {
        //其他类型需要转换一下,然后再赋值
        switch (element.asType().getKind()){
            case BOOLEAN:
                methodBuilder.addStatement("target.$L=Boolean.valueOf(temp)",varName);
                break;
            case INT:
                methodBuilder.addStatement("target.$L= Integer.valueOf(temp)",varName);
                break;
            case DOUBLE:
                methodBuilder.addStatement("target.$L= Double.valueOf(temp)",varName);
                break;
            case FLOAT:
                methodBuilder.addStatement("target.$L= Float.valueOf(temp)",varName);
                break;

            case LONG:
                methodBuilder.addStatement("target.$L= Long.valueOf(temp)",varName);
                break;

        }
    }
}

基于同样的方式,我们来处理一下IntentValue,因为Bundle中可以存储的数据类型较多,方法比较长,

private void processorIntentValue(Element element, MethodSpec.Builder methodBuilder) {
    VariableElement variableElement = (VariableElement) element;
    
    //注解字段的名字
    String varName = variableElement.getSimpleName().toString();
    //注解字段的类型名
    String varType = variableElement.asType().toString();


    IntentValue intentValue = variableElement.getAnnotation(IntentValue.class);


    methodBuilder.beginControlFlow("if(bundle.containsKey($S))", intentValue.key());

    if (intentValue.type() == IntentValue.DEFAULT_TYPE) {

//基本数据类型可以直接判断,不需要类型名
        switch (element.asType().getKind()) {
            case BOOLEAN:
                methodBuilder.addStatement("target.$L = bundle.getBoolean($S)", varName, intentValue.key());
                break;


            case SHORT:
                methodBuilder.addStatement("target.$L = bundle.getShort($S)", varName, intentValue.key());
                break;

            case BYTE:
                methodBuilder.addStatement("target.$L = bundle.getByte($S)", varName, intentValue.key());
                break;

            case INT:
                methodBuilder.addStatement("target.$L = bundle.getInt($S)", varName, intentValue.key());
                break;

            case CHAR:
                methodBuilder.addStatement("target.$L = bundle.getChar($S)", varName, intentValue.key());
                break;

            case LONG:
                methodBuilder.addStatement("target.$L = bundle.getLong($S)", varName, intentValue.key());
                break;

            case FLOAT:
                methodBuilder.addStatement("target.$L = bundle.getFloat($S)", varName, intentValue.key());
                break;

            case DOUBLE:
                methodBuilder.addStatement("target.$L = bundle.getDouble($S)", varName, intentValue.key());
                break;

                //ARRAY类型分好多中,需要类型的名字具体判断
            case ARRAY:
                switch (element.asType().toString()) {
                    case "byte[]":
                        methodBuilder.addStatement("target.$L = bundle.getByteArray($S)", varName, intentValue.key());
                        break;
                    case "short[]":

                        methodBuilder.addStatement("target.$L = bundle.getShortArray($S)", varName, intentValue.key());
                        break;
                    case "boolean[]":
                        methodBuilder.addStatement("target.$L = bundle.getBooleanArray($S)", varName, intentValue.key());
                        break;
                    case "int[]":
                        methodBuilder.addStatement("target.$L = bundle.getIntArray($S)", varName, intentValue.key());
                        break;
                    case "long[]":
                        methodBuilder.addStatement("target.$L = bundle.getLongArray($S)", varName, intentValue.key());
                        break;
                    case "char[]":
                        methodBuilder.addStatement("target.$L = bundle.getCharArray($S)", varName, intentValue.key());
                        break;
                    case "java.lang.CharSequence[]":
                        methodBuilder.addStatement("target.$L = bundle.getCharSequenceArray($S)", varName, intentValue.key());
                        break;
                    case "float[]":
                        methodBuilder.addStatement("target.$L = bundle.getFloatArray($S)", varName, intentValue.key());
                        break;
                    case "double[]":
                        methodBuilder.addStatement("target.$L = bundle.getDoubleArray($S)", varName, intentValue.key());
                        break;
                    case "java.lang.String[]":
                        methodBuilder.addStatement("target.$L = bundle.getStringArray($S)", varName, intentValue.key());
                        break;
                    default:
                        methodBuilder.addStatement("target.$L = ($L) bundle.getParcelableArray($S)", varName, varType, intentValue.key());
                        break;

                }
                break;

                //其余的会被当做`DECLARED`类型
            case DECLARED:
                switch (element.asType().toString()) {
                    case "java.util.ArrayList<java.lang.Integer>":
                        methodBuilder.addStatement("target.$L = bundle.getIntegerArrayList($S)", varName, intentValue.key());
                        break;
                    case "java.lang.CharSequence":

                        methodBuilder.addStatement("target.$L = bundle.getCharSequence($S)", varName, intentValue.key());
                        break;
                    case "java.util.ArrayList<java.lang.CharSequence>":
                        methodBuilder.addStatement("target.$L = bundle.getCharSequenceArrayList($S)", varName, intentValue.key());
                        break;
                    case "java.lang.String":
                        methodBuilder.addStatement("target.$L = bundle.getString($S)", varName, intentValue.key());
                        break;
                    case "java.util.ArrayList<java.lang.String>":
                        methodBuilder.addStatement("target.$L = bundle.getStringArrayList($S)", varName, intentValue.key());
                        break;
                    default:

                        break;
                }
                break;
        }
    } else {
        //这里的就是方便通过类型或者类型名来判断的,通过我们自己定义的类型来做判断
        switch (intentValue.type()) {
            case IntentValue.SERIALIZABLE_OBJECT:
                methodBuilder.addStatement("target.$L  = ($L) bundle.getSerializable($S)", varName, varType, intentValue.key());
                break;
            case IntentValue.PARCELABLE_OBJECT:
                methodBuilder.addStatement("target.$L  = bundle.getParcelable($S)", varName, intentValue.key());
                break;
            case IntentValue.PARCELABLE_ARRAY_OBJECT:

                methodBuilder.addStatement(" android.os.Parcelable[] temp = bundle.getParcelableArray($S)", intentValue.key());
                methodBuilder.beginControlFlow("if(temp!=null && temp.length>0)");
                methodBuilder.addStatement("target.$L = new $L[temp.length]", varName, varType.substring(0, varType.length() - 2));
                methodBuilder.beginControlFlow("for(int i = 0 ; i < temp.length;i++)");
                methodBuilder.addStatement("target.$L[i] = ($L) temp[i]", varName, varType.substring(0, varType.length() - 2));
                methodBuilder.endControlFlow();
                methodBuilder.endControlFlow();


                break;
            case IntentValue.PARCELABLE_ARRAYLIST_OBJECT:
                methodBuilder.addStatement("target.$L  = bundle.getParcelableArrayList($S)", varName, intentValue.key());

                break;
        }
    }
    methodBuilder.endControlFlow();

}

给使用者提供调用方法

还是在api模块中的ViewInjector类中添加如下方法

static final Map<Class<?>, Method> BUNDLES = new LinkedHashMap<>();

public static void parseBundle(Activity activity) {
    try {
        Method method = findParseBundleMethodForClass(activity.getClass(),activity.getClass());
        method.invoke(null,activity,activity.getIntent()==null?null:activity.getIntent().getExtras());
    }catch (Exception e){
        e.printStackTrace();
    }
}

public static void parseBundle(Fragment fragment) {
    try {
        Method method =findParseBundleMethodForClass(fragment.getClass(),fragment.getClass());
        method .invoke(null,fragment,fragment.getArguments());

    }catch (Exception e){
        e.printStackTrace();
    }
}

private static Method findParseBundleMethodForClass(Class<?> cls,Class clazz) {
    Method parseBundle = BUNDLES.get(cls);
    if (parseBundle == null) {
        try {
            Class<?> bindingClass = Class.forName(cls.getName() + "$ViewInjector");
            parseBundle=   bindingClass.getDeclaredMethod("parseBundle",clazz, Bundle.class);
            BUNDLES.put(cls, parseBundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return parseBundle;
}


static final Map<Class<?>, Method> URIS = new LinkedHashMap<>();

public static void parseUri(Activity activity) {
    try {
        Method method = findParseUriMethodForClass(activity.getClass(),activity.getClass());
        method.invoke(null,activity,activity.getIntent()==null?null:activity.getIntent().getData());
    }catch (Exception e){
        e.printStackTrace();
    }
}


private static Method findParseUriMethodForClass(Class<?> cls,Class clazz) {
    Method parseUri = URIS.get(cls);
    if (parseUri == null) {
        try {
            Class<?> bindingClass = Class.forName(cls.getName() + "$ViewInjector");
            parseUri=  bindingClass.getDeclaredMethod("parseUri",clazz, Uri.class);
            URIS.put(cls, parseUri);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return parseUri;
}

使用

只需要在对应的类中调用ViewInjector.parseBundle(this);ViewInjector.parseUri(this);方法就好了

其实应该把UriValue当成练手让大家自己写的。。。。


以上

You can’t perform that action at this time.