2019-12-09 23:21:08
该系列介绍自定义注解,完成如下功能。
- @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当成练手让大家自己写的。。。。
以上