Why are annotations under Android such a performance issue (slow)?
Solution 1:
Google has acknowledged the issue and fixed it "post-Honeycomb"
https://code.google.com/p/android/issues/detail?id=7811
So at least they know about it and have supposedly fixed it for some future version.
Solution 2:
Here's a generic version of Gray's & user931366's idea:
public class AnnotationElementsReader {
private static Field elementsField;
private static Field nameField;
private static Method validateValueMethod;
public static HashMap<String, Object> getElements(Annotation annotation)
throws Exception {
HashMap<String, Object> map = new HashMap<String, Object>();
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
if (elementsField == null) {
elementsField = handler.getClass().getDeclaredField("elements");
elementsField.setAccessible(true);
}
Object[] annotationMembers = (Object[]) elementsField.get(handler);
for (Object annotationMember : annotationMembers) {
if (nameField == null) {
Class<?> cl = annotationMember.getClass();
nameField = cl.getDeclaredField("name");
nameField.setAccessible(true);
validateValueMethod = cl.getDeclaredMethod("validateValue");
validateValueMethod.setAccessible(true);
}
String name = (String) nameField.get(annotationMember);
Object val = validateValueMethod.invoke(annotationMember);
map.put(name, val);
}
return map;
}
}
I've benchmarked an annotation with 4 elements.
Millisecond times for 10000 iterations of either getting values of all of them or calling the method above:
Device Default Hack
HTC Desire 2.3.7 11094 730
Emulator 4.0.4 3157 528
Galaxy Nexus 4.3 1248 392
Here's how I've integrated it into DroidParts: https://github.com/yanchenko/droidparts/commit/93fd1a1d6c76c2f4abf185f92c5c59e285f8bc69.
Solution 3:
To follow up on this, there's still a problem here when calling methods on annotations. The bug listed above by candrews fixes the getAnnotation() slowness, but calling a method on the annotation is still a problem due to the Method.equals() issues.
Couldn't find a bug report for Method.equals() so I created one here: https://code.google.com/p/android/issues/detail?id=37380
Edit: So my work around for this (thanks for the ideas @Gray), is actually pretty simple. (this is trunkcated code, some caching and such is omitted)
annotationFactory = Class.forName("org.apache.harmony.lang.annotation.AnnotationFactory");
getElementDesc = annotationFactory.getMethod("getElementsDescription", Class.class);
Object[] members = (Object[])getElementDesc.invoke(annotationFactory, clz); // these are AnnotationMember[]
Object element = null;
for (Object e:members){ // AnnotationMembers
Field f = e.getClass().getDeclaredField("name");
f.setAccessible(true);
String fname = (String) f.get(e);
if (methodName.equals(fname)){
element = e;
break;
}
}
if (element == null) throw new Exception("Element was not found");
Method m = element.getClass().getMethod("validateValue");
return m.invoke(element, args);
You mileage will vary based on use, but in may case this was about 15-20 times faster then doing it the "right way"
Solution 4:
I think if you manage to change the RUNTIME retention policy, it should not be that slow.
EDIT: I know, for your project that may not be an option. Perhaps it is more a problem of what you are doing with that annotation rather than bad performance in general.